pax_global_header00006660000000000000000000000064131762202550014515gustar00rootroot0000000000000052 comment=98754b7a6902d7fbc4652968bcafdaeefba47cf8 gnome-shell-extensions-mediaplayer-3.5/000077500000000000000000000000001317622025500202655ustar00rootroot00000000000000gnome-shell-extensions-mediaplayer-3.5/.gitignore000066400000000000000000000000201317622025500222450ustar00rootroot00000000000000builddir/ *.zip gnome-shell-extensions-mediaplayer-3.5/CODE_OF_CONDUCT.md000066400000000000000000000006171317622025500230700ustar00rootroot00000000000000# Community Conduct Guidlines Much like a good many other opensource projects this is not a democracy. At best it's a meritocracy, at worst it's a dictatorship. At any point you are unhappy with the direction that it's going you are free to fork it and go your own way. Oher than that basically just be cool. If you're cool I'll be cool and we're all cool. Cool? Do we really need any other rules? gnome-shell-extensions-mediaplayer-3.5/CONTRIBUTING.md000066400000000000000000000006071317622025500225210ustar00rootroot00000000000000# Contributing Before filing a bug report or feature request please read the [Bug Reports and Feature Requests](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Bug-Reports-and-Feature-Requests) wiki page. Before you put in a Pull Request please read the [Contributing](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Contributing) wiki page. gnome-shell-extensions-mediaplayer-3.5/LICENSE000066400000000000000000000432541317622025500213020ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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 Lesser 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 Street, 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 Lesser General Public License instead of this License. gnome-shell-extensions-mediaplayer-3.5/README.md000066400000000000000000000052761317622025500215560ustar00rootroot00000000000000# gnome-shell-extensions-mediaplayer gnome-shell-extensions-mediaplayer is a gnome-shell extension for controlling any MPRIS v2.1 capable media player. ## Please see the Wiki for: * [Contributing](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Contributing) * [Bug Reports and Feature Requests](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Bug-Reports-and-Feature-Requests) * [Installation](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Installation) * [Settings](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Settings) * [Compatible Players](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Compatible-players) * [Ratings Support](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Ratings-Support) * [Known Player Bugs](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Known-Player-Bugs) ## Screenshot ![Screenshot](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/raw/master/data/screenshot.png) ## Features - 4 positions: left, center or right in its own menu, or in the system menu - interactive indicator icon: scroll (next/previous), middle click (play/pause) - playlist support (org.mpris.MediaPlayer2.Playlists interface) - tracklist support (org.mpris.MediaPlayer2.TrackList interface) - Shuffle and Repeat support(not well supported by players. Lollypop has full support) - rating support (Not a part of the MPRIS spec, limited support with player specifc code, see [Ratings Support](https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Ratings-Support). Currently you can set ratings in Rhythmbox, Lollypop, Nuvola Player, Quod Libet and Pithos.) - and more... ## Authors * eonpatapon (Jean-Philippe Braun) * grawity (Mantas Mikulėnas) * JasonLG1979 (Jason Gray) Based on the work of horazont (Jonas Wielicki). [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) ## Like this Extension? Then maybe consider donating to help continue it's development, otherwise known as buying me a RedBull. You don't have to, but it would be cool if you did. [![Flattr this git repo](https://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=JasonLG1979&url=https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer) And/or consider donating to one of these other projects I believe in. [GNOME](https://www.gnome.org/support-gnome/donate/) [The Free Software Foundation](https://www.fsf.org/about/ways-to-donate/) [The Electronic Frontier Foundation](https://supporters.eff.org/donate/) gnome-shell-extensions-mediaplayer-3.5/build000077500000000000000000000022611317622025500213130ustar00rootroot00000000000000#!/usr/bin/env bash # Possible arguments #./build --install #To build and install locally. #./build --zip-file #To build and the zip file. BUILD_DIR="builddir" LOCAL_PREFIX="$HOME/.local" UUID="mediaplayer@patapon.info" TMP_DIR="/tmp/share/gnome-shell/extensions/$UUID" build_type=$1 if [ -z $build_type ]; then build_type="--install" fi if [ -d "$PWD/$BUILD_DIR" ]; then echo "A current build directory already exists. Would you like to remove it?" select yn in "Yes" "No"; do case $yn in Yes ) rm -rf "$PWD/$BUILD_DIR" echo "Build directory was removed succesufly" break;; No ) echo "The old build directory must be removed first. Exiting" exit;; esac done fi function build { prefix=$1 meson $BUILD_DIR --prefix=$prefix ninja -C $BUILD_DIR install } case $build_type in "--install"*) build $LOCAL_PREFIX ;; "--zip-file"*) build "/tmp" dest=$PWD cd $TMP_DIR || exit zip -rq $dest/$UUID.zip . if [ -d $TMP_DIR ]; then rm -rf $TMP_DIR fi cd $dest ;; esac if [ -d "$PWD/$BUILD_DIR" ]; then rm -rf $PWD/$BUILD_DIR fi echo "Done" gnome-shell-extensions-mediaplayer-3.5/config/000077500000000000000000000000001317622025500215325ustar00rootroot00000000000000gnome-shell-extensions-mediaplayer-3.5/config/.gitignore000066400000000000000000000000411317622025500235150ustar00rootroot00000000000000install-sh mkinstalldirs missing gnome-shell-extensions-mediaplayer-3.5/data/000077500000000000000000000000001317622025500211765ustar00rootroot00000000000000gnome-shell-extensions-mediaplayer-3.5/data/screenshot.png000066400000000000000000005554671317622025500241070ustar00rootroot00000000000000PNG  IHDR|$l pHYs   IDATx}w|TU{dIOH$ AD]k}]Qb/+mk*(H " HK{~gL @Vw;99{*q1x9s566666͙3ggN+WnذABW[o~芰ZofKK'|r5deeeee]{su\jU+"nذASzdddDDotFEEUR8Mo'''gBWYVZcǎt!gff;w\r/gF?jjjvul6o{뭷B_4_=&&vDܸq>boӧOk+bܸq{>ӏ9t~Shpԩ{7wSlZ)*y޳gϬ &L4iҤIǏ6lXJJ)x⠋K.ɹ馛NcǮ\Ο?͚5]tљ7ւP꫱Ǐ/..W7saa_~3}v>n ,߿W^yG޽ Ez믿^zf;\iC̼+GգG]u]w:W^9`BɶbŊݻC|tڴi-߼y|}˖-ͻꪫNV'ҙ!O>&L4hPbbbXXi9!CL8W^7|311Q>tMO=x'{< ̙+_߳g|/bͳ\yqQh_~9L4i޼y.KAkǪUo?{XWWwcXx'N0p\'oOA<4iҌ3f̘q;""tW0*4ͬիW#w駟''!NHHP:5ZPJgG2eʔ)SFp8(RѣG'~w0*6-[ddd999'%Ʈ^:''箻3A4NsrrV^}bՂ:v:ny8pرc+V6l͛cK._[nM]|;vOu֞G9rȑ#jrj͚5kٲe}n^d g}rʺÇs=~qqq]]_| IIIh"3<]v!n۳g㩬\n];xb/1N^;|p}}ƍ'N(ի ~\.ڵk~۷mٲ+))Y~}KKKߜ/z/^wu+Vqs)S}ݱ˖-{卜7>{Ku]VIJQSvmVWWwM7}ׯ_n'|?rǎ'66 p…)))ɓ'O|3yFFFov(7}M6EFF^}j;سgOk g2]{A* \62͜93$󰰰 D\dɢE|M~eʕ⯆a,Xwf_C;+Ÿq׬YsQ0˥K"⣏>WK"_OWTT0F_,__M*<<fDTT&C(E3?|y7F=z7x#H?|:իrrr6l`7qƜ~"""rrrBWﻧ4Bl6[DDD#edd4 #""fyӦM;rHqqԩSOg|>2dƠA6l؀'\WWm:v=ܾ(e*ZN!dHQL .Xxŋq>}999ӦMk֭iAxwt:E?#111Ns׮]S? YYY۷oonn>p/W3f߿rݻ\)..^js:oFaaa]]ݺu븋?/bʕw>Y7bvOCm,<<c` &XV6~oGsZhB)*E,^i6m7x rrr/_vR ^ve[nyW-jηQ8uX֕+Wܹ3##_5jԈ#ڵkŊ]$An| Kq 7444zթW׍tj_lYNNΩQ/_~}uԎ۵^{r}^{-w>3}U/IL:.G ڵkO9@=^z={0nw^^޳>N׍c.^?M :sW_}u񫯾=233644444+mqAΩ_oՐ+:p()΢s* ™:e]l3bkQ^01LR_O. {$uW^M4MMӂr_$ge|{E?A2I+(hqow'4K5Aʿw!A?G\H70_;R@_Df(,TƇC2D$!4B2D9O&@ 1d0"`#EO-T( ED`4B3&ic(d& AdKL#)g֕ g1k#"i['/R*FA4u2 ]cCKY`*?[~J)LA*$?Xz'2!9QJsܪ2 Cg6/iNAǻ<⟺k4yxA_* @4(ץ 9 i>ZZ+h塔+n*n*nvAn򍟿6dhHHnE+G0t'X<^"3B~M񫠍rH_o].B v " gњ}Cǯ-GDfXGS5:(N .Ƃ˝ lƛZ) hLX@FH%cBa@P CM _/n2$DEQшig,µઘL%@ތ8w- G0m+n*n*nvEn3:dBب.ZF)%m E% L,iH^@MɅ&DDtYTq<4$Q%]߯d!IOf*AJQ@yo-jJQW JЩBE>[JB}O8@BDf"c~M$xi$Q=X4.߇ #T;vxm;vЀ`HŽ`?(A'N(j:tPJf3QO"D0:3M@b@! :F.^.L}dC^#+JĄGO1'q[ _7;wr]s`"C@d:?s)!iڭ,%ɔ?R~!@q'nq6l!Y3;PIۚ@{_ uRI3kq:2& TR@mU8_,@ lneI V(>o R )',A=#46>[rK! H(pݔ_I)֯mLL+ )(р0Ds na%@|{T9FJĠȐ)3QOQ!D$3\tPBFİn}.XMAomitBcoa=Vd(nv 75 oJo  2fhiCeۉlƃS'(kǂ'B䲽$PӶBIK7B Ǽ1lb%UBĖ0ye0\EG!=Fx#YE(k"RztB}݅KùqqaaaV (G-1H'JDET1cz?d>C !@5(,6' d ZaoG/dZqP \MZTUͥKW|bna5qKZ T+!RwkWwl !V YbBW7C)OVg{ˋhf ۾mGCCcvP_z00=f򓌤Y Sw .g,_>'5嶥 P%ߢD.oeHß8I/+nv,7_8wAv YY~(Wyfp3ToAF!v7)XqF)!@-UXu:Xƶ=CYq4JHnf(P$ȫS&P' NlXתKuR~QiylcH hka b '2oe:eC qZ,]?3QB_ֿVǝaa_8jTJ @<@ a׀h+IMEuJPB5MGx}!)I G?oBQ3djLq-X䛃V.銈 MD|]1*y#\uIWh *y M"h&h7zS?܈"oy"@T-%xr$iEᅡiG% I_a*og_\eE W΄k ?_h #49(!'x_$@F04"($9zlj:w 䮉Z%@5!C@B4͗yQ1y^Tt=Ǝ=O9{b #6ʘI / AV>f~I5s|P=PƁ=σSşePiI("vd| "]p 芛M4Fnޞ[x?ZwN> ZqԹ)EBH-hujPY헶XZF<KXNܔf$9ށO6M0E Y+Rm%pPL7B}:v~cRsr{ 2SK=ЪZʂ*rX,Tw/Ap=O=Ș !c[\0|]3ZND +PyrgRB# x~B`bɊ#Y(t<'ҖS2 Bg7㛢?)n>7[\-=r~a:o+ۖGy)b qS";#P ^Nvm7cڴx$ٱ5@C;7}ן5&4H!pxfƪؾ@9 R?D;+ºֺr*ZB>APb*R[e|BIP8666ΚlYEudtV.^[S~æ .kY im8{\A^\W$/>/o aY*40T͋$:yI3 J$m .d&!fccO>_Qy[S]?{bOf^c_QANُG+n7^xjq?ଲnL36HSB^7Ob|[&i~K{ IDAT /*T W u9 ^Amz"EPFl"H6검@C[chQ#9SWsXQ7(+a[ GzMp:d`Z^cٲY"#UXe$)@Y'lF[\x^[(P GZm+<#t Jx:Uy 3:I+yr5J}A g@Gm 4l­͸ ɿv0|P,mr|!j1/" y ܧ: MOC}_!oCÆ )QA!Gz'#-h<5Jn{)HVoC?0#"NV-_98s`||_/%mm\8: PB rH @b_n+PE``hjMY{&z7$A"ba/ z@*n7B۷TVP?M{ـVk w4&hE 0 ]1SAqhik $A/.?cLZA)Eg_ಿEex. ]`&8o^$[0CԶy3:iW;jFkQ RxBD#1gk!zqJPVlfP؛i&#!Ճ\lJVPҠ46m@~efE\/1 bc:HY)Htw ]VMђurlMII ןX&&4鳂Bi&Pa ,4&$nDScN Mh#/c:"LTOB?'oD(Y9!%'H!ܔ+L5  :tA`eMw ]i 9ű\VV_Aй[*jUPڐNElNXTU>TP$n S$,!|CVB>f!?rI^ ""#U*(t8}{@0R!|!BKA)0((t>7IW ?USA4?" "k`HcKAs=QpRBu=ф~UPP4 *8 \TZA)UȡR gZJ Е! mj{i#0 C80s"%rG̝/7,dX6Buެ@*(t67D輲5RJu 4M%FWP\01aR}~T:rļiu"y5-ZDL41_=]M#Βd/ޭ[7(++۾c+ P*@ c 06]ʨ.b(3f>SAA>1ACs/X,;oe7l%%2oe_^W ¯f 08]4E=%; <޷[TBŢ_yŴW_z/? /jX:= #&d\O 8~߸0L׻~_w3$s3O<~F;oZ}+/}VPX,YCD|=7^NLHxfqO?Xݏ=(BD|\ܫ/x-7OOlv-#=nNj/\Y/ +s +/?F3[ot:{pfEE%2n̘?WT"o&5~%\D]$qCDJ;\\tT / ib1A,>6^iiM_t8oÇC:|?x63/4)ij.) @1MjMQ^яMV,cnC[%ajN(t,f\>oS.h}(cPi)8(m3멓/]aѣ%;w74ںzHKMݶ}[m]]Mm6Mr}Q 7?zTTd?;/x7?#}bmtl>0s7^xIZj}V^=/VW49w Udf f:d_]V,%LHu \toptjQC^9Y9Kw>If̛7ĝ6tÌ:3GfgocQQѴ˧gyʺhD?zt嚵~a܅7UYYdzeMȥ!i+QO C]\:x9һW3g=vkGCQ5!:}O|CZϞP.(s#\e5:'ug %%v}v8NQ?LG$U$5|6,Bjpiۭ4b-[yn,kLse7^?Q#l5'ZML#W^iiq7vي?ݛ5|Xĝs/ݹyǍ랿Sgޞ~;=/(*c_WaO7O??K=#GXE4دo{+N^`mGξ; K@Γ=LaQeg=`S9m޲ 8pشy+s^kqKO)cd SϽݢ pU;ZRr߃.(z&66F 9w@ng槞{>7o o~ő.?;{ b%}y*6  W_w<$$$<ԓgѽ{~AOM E:y$koXbeg l2*a:q0ƸyCRg[,O=[]QQw\}hO?Q#Еϻ}ǙIhoZe!IKUpxg=m7maӦ?+Ǐs.Z?:;.;6"<|޲A 'M/Nz5"o*"IVqdPJ? LFHxNd٨6zz,:f\'Jm۱{zZ)BnYAGijhaؙ444M5 = f۾b\lѺ .6gMY ]IcƥdBQ_P Qw/q+G 3M)5tizrP;..M "I"2uUP8k]j0D4ϊXyQ)(te dk,b rq!T6],i>ZOܧi>}ONsE*(1n+ Q>ߤ ]H@B`OAu?{YɎpqR*FKZcv.JM6J%*S?#J)u#Dī *E)AX@8 KeѪ1SP"8k:enG^4R T\Y`!0%+(tQ"+DTEq(nxй傸1G"Di@k%+(tY0&1,eL "cHxй ن 0L mWA+C(RB2cP UzW3MCR!IJe`Ҷ"$'2O Ù2r6mcp2SW[F[&A>y0i8tχI_Wf^fӱf7_=hF-ܴixy;Ϋi(s54^rL?5ZL 4J "^Hh^Ȉȿ#GLg]yoi4{aЭ#8U)NBmCWK?1r]p+=V60cm߳+׮);DiUه1 &0=WY?PKBWe& ΊJ/oS/^v{Y 7e1KzM(m=ϴ7 5?mq^4yZu} u5x%v COJIXߎnaԻ=PQSg=VZscvXWӀ?}ҷM@qC3w={Fyad@P~xɖJ @ ;s?7UVm/ֽk7Qp䨵oƹMp/BEh/?fOjUPYX_ʾ 7sy O1Kv2.a- ]\z΅a.ЄҚu5G5m5piR$7xaB!ih[-CgkX{jvkkA5::'g757?l*tAX,)S.0>-5 ֮[dr1fRM  g2p8}5LM.EX5mD JiKՍ# m%ޱ},>4ܹfՓ,ٴ[<,1rp XQc4z8Է%|two~~aT#ϋx޳pmIM[*.Gu+9111ʱbsiI{:4͒~n8nk Rl-gR=Ui>C#.cRta<)9> zw~.MFZ"!nƅ6>+2PzwUj\IfWK?YPPZ^CMj#XM5p٢%zZsg$3 B*%Ё!B1%U1 HKM}{!T,?iEE(cK\HOHOKf?lUuuXΊ9u]JN?]5 xˊuLѯ'd~nnS1nX,z-yr>?exZUeNX߭΅%T=nc?:[请4fA'7">6nQtS+g#ꩽv2~x\A5'J+Og59eU#`ƏHSU[R576)b,iuCDY~n_ȺW4z 7.0۵UQ6oZjı i]pG3x}JVU2,=nuwѺ[~ze=Vjdu{^@hln!DSJ4kSXLd  !zM`BDFVivvybhus 峂B9}z=f?2 )IN>$Dvs<À,][[&Y5 >D;FN^}: FMU{bcF4uxS;].a6+2s-&8p^ᶱ+]ռhY ԖP3~H]uV MLBJ+nC1>[u %:|X?tv$JlVZuiQΎJˣqG+- FcU5: gLN{kW3z JLInvZµhTTLٍqfw1d$D%: ^=ᒦ+.X_Td^MDnw={h[p$}q#1n!24)ᖰ#Fߝ=]Zr(X 5EK\&3 |GNa4|0xw= tIߤP{%"A 'wjjgڈ?@44Vv!u_v{\Mw2ӘtuEaۃnx~ <v$ltu.4#pifwTFӨEꣻ{l4*XFzmtjiq>7xVa.7!>zPZؿKxKDax{vK\=[={Sɘ4.|ۡ\Glᮣ`USs^f QJ1#@F5l1 RiOvp{mJ_|'l۱N#QFY]džrj=@cqvpPxHHs-\,R *䢺2haznxރn1~1w0S؁ {1:i0꭪b͚fW7%i7b*KdtbR^awb68=}51.G5[4L=na oDE60%&4~uT_`QUESWGXCנVllB7JhL]m CE-EeWNf{ˋM&ٽPic:G+;| V5}gtH{?^HMLOAIc @<;"PSs^qqVhhQa3 "t5Mh2"Vxng{~4|#G=~>?650R{ HX&vMWuwCL,gZ3=NJ(8w{rk#2 b_ {;ohٞ{r6p _.ܓLХyvGCy3r$SR0y!J#zIׇȨ&cCKLDM V%7hr$fðc߭8S+(jl ںԔ(fmtbPJ;J+{_:G  rHNp#ڑ^y4ZYd]-+]J+%k$a$2@7:wWW/PhVq.n};8$@z4]YՓ5i Xo?u2yBBn-䵺%Ԙ֕Šah*:o.6H?֧jXaEzwSkewơ]I8xl~% Aצwn?GDHb:P5OʑǾ)6X,Yn]zήTo riI)=5#Ƙ$DdR-#loϴ ^@ى; Q(~?qvn^SOb-. 2Z\[,JN ~ëS_<}d9-=WI!=Y>#\?^TчzU_8'Ր޻qNAzcsǙʾ"5lLqSܛaXTX$lTyۅFޔ7B #(en'.5 U)\ O6lO iE8S7 tSVV3/xΝN|blƢJպ_?8t}1EZXh,=]3cf5[ j A %@-B4ˌ1!Dk]+]~7/_tpcy|Q+^Ϲ^=޼1eTu0dR3|f-Vk=pTD}8~o&SعcW"o[0*>.]b==N,gb O,,[K|ӻ^?O}hBQYXr-V5Cw%#/4eht%bu|֮J 3d(9x |D(ݟ>yl,b般Z3QM+n$H׋]nY0q*69ǵ ?˯yi6kK;9Ǟg8Q}8ę !T:G?F IZ 8AnpA?mvT;jm-s=tՂ8F)_2Kuw(IXDR|L\Igfry=}˖o:eK`&FݽF6g:T:cڰ% }Z*gA'79Ai:0&KERb9H'՟m1Ig~i1B v0# k4nrk 1*DUWwBi~֊#˅JC3w rBxm`0&&_پ{H4R*9?{[e 9Y95r|~߮'\G::ERiߕO~\̕kRɴW߈cZ*BzX`#+4cp C JV"!$~M7Ry q9'i+cC_?b.~S&?g}]>tϽ]]]XZ/^{3wL|⽛FxS?gեwuf;9c @9HW- 1:tqtp[6kphsTSU %ADp=g?g3a{ltθ?4`\ޛydߪ,==M}z@bK%uz]Fc;u˲9-(~O*/ϕ<֨;nUR̹7X&٦횎R@$ i <$BO\'}<<ֱ|#atD*Y-$bN #!zu%doúؕi_<F8N |o6|u":$#NO<>]R=[r_ᤴ+T8LPNzɪgPAI%]Q'EQ0"ShWT "T7/ǦlW47g Oͣ%r{{.VMݶ; ks*mOEJ̼utry|he@H&IJ%>Nՙr젃k$خv6q1ۖn 4]i&{|\9HN/A܄R2΃\h,+1ӵ"V`Ý}Dgpx(m¬OINoiu]w쩼z.gA@?^^7Tj,bX3FL)Uņ'8Ll4ҟIK\2Ƙs=_ŕںkn4BgZ5WjMV_ ر_A|W}/|=@:ސݽk1[ s{*>𮁅R$j G4EWjcS5SX$ BnˬRx,=g'_:vv.шg9aC{'k_xl:" kў2.~`@{mɸJ\8c;'3αs}wn챁D\Zu]^DH$%A˹hX:zZdmKuW T.L;ҽH`x|(O 9ӧL v`XR䫍R"ߵbq-ٲyW:sK:RRQmKXsg ^?uuRa7s=Ȼ޻_z|[//)ݺ\FT%'w\ܷ//_.έYrzC@\(J7Rвw"2$H"H4t;tCTju=R.c>]Kl~pk1N j芜ql[ĺ3'O!P?<"ĵ@<9=>&lѱxktqQCt2= ^+?/Rʽ^t*= "+N/zWՏ}#GWNoJESG߈jd45W: D z3'_Y)] m.f= tc;өzm{:mxdޔ.5V .88`DPudEm3kH|$cL ZžNynRrD-J905]#l\zWuab$Ӆys|@+Vo(r*=&Y t,{+Wo"հ$xdDT=ȚΙvOՐ]EaK˘FѕPwSeVwmth,66ڧ)t ImܱwǑ;WszjF8@()#z&Z~1NF̗ $Olޜ_ +n!HHDXvJ4G#6ofODUlVCOjʊ%x0|G3MWg/+>ktu!"9?[Ȼd;IR802:RnUgj5C⵹B/X':঵֠2@Tnzngdwkvh\ݹeą†VǷ1kEKtqb/ }s/qqQQC[&~F  S@PI(2"$1= cdB1t#| %Z$}Ր'uuW=!}wƱu- @dIO4;Y,H "AB =wlŶ<)6m[!8c@$@%Q@ ,8$$v?ZTJS.iEٹwɽ۶/.͗&x=;=u>0o>\qן0g=̷sIbXo_X-bwyC(Sھc[<~o/6=rp\,MLjqkxdJS 5! H]}b<՞HKVOON*6]u_OۙL#pD*R.erA:RcYki= ,ph։GƷ+!^xuMZe$ EQ$PA \9,6:sp!Phc6t[r-O/-|cl|jvDž@Ͻ$J)$*^}hSƙSd2\ b㱸\~;O9u#BUeDBCK'$w]wkdGG]zLq E5%FFe2itصWsA"Ҵ5}±jkb,ӧp}b󖋧: "yjK߫9(Z$RMM{+/S&3 @t͉ֆ. nZmU z\*c>ѵ~JV?Er `syκ̦7PkJRwF"zYHY..:m h\QzWg,L8I$% <.I3Q -I"0zv/)Ce֛G/5:m EmW J 5@D3aNsHQu)}>5}~*1TDQc|w%_Ͼ125caѪ35W !C#!j2eeA7?ړA",#@2pa%mFg7NuNtjbv 4V2GGWw00x-PX[ozz8"Ab0T;I hINs~ჼPx/Tt< kH=OP բB2 d#zl5ܳCÇkӄj[M_k]joJk$H֜Z߭2T`CrVv;AUІ|@D0t}4BpCM LV9u>xw Vsb<Ȼ]pO^ \!%H$@Rx )|j Bfmz *uj@$$0򮐮S)Ut d! GO?=\^Z{7R[GGz ^d= / n6' 9+̝P̐Rr`-Vhe5.yG>D =έfog_}cճBH3'<(FЁ[f}e%ۢ6@ uI)W)WCS7$:s# qMa 2'fV8^)ԙ3Cl]|,2_xC׎=D=6yLbq n40Vy9đ)x@old젃}{3 w !",m Ɍ{gΜ{[F >{%>}U}~Dhw I{^IȘdH"|Hŕ3ŀj]w`(T@ ҟlϻP̋ sY`tU~o`mq)}}[ƫR>"Ft["!VeADTE!蠃B-zcmaݶ# kx]"AU~ݲ>q$m%)c D_C #S4"FPJ ~vR_O$9]$0 s=vxg_}y^Tj1~^zmQC= r (_(/Ӆ Hxkf*`3p9_X O8 b`n WYP@d:[m{ndrCZqk_jG^dt%I AWމyFg5y.?G8)E|}/AGT8JezrM2 Wf0No⵷RV}ўl]xVYNuo[4y>zR{>;f++0 0ʵzZD4M;cl6Hu!gU1c'zqeL#V}Fq !;o8*ZK:$@|i|mgp6<9h[?0fu  ҆hڪ?)ds5!ZKNu׻z6m.-1B`.ے_gtMߗ*C *Z\k-jw} ׻mkTT(4Wr[M.PZck.+U+HN.2|oR kjYnݬO 7%{p"g;=pb F$=*W]ݪ"/9=kˈ[M!aDV'뙜wf͛2"-R- t`Z<D3^L) |:~\jX;"QYu:Ѫ#l&0M&H$%*A EJ׶&Ȏv[jne"Bp7]KښWGMە~zM7|͆JeJ~MQkJ9BlW,J7&#vԙɮtT UٹRHײ+9YuBjqnR1OLY*m5̕қuhj8ޝ_wdPP4n-d\ < E hc GJ@31%ί,r9md2Uה$z4m\hlm'!`AWmĆ`ZYJb1Ƒ$_"P^-N\A? l#lް]}p8jVm(CDW՛ J 1!e2g_oKwriinjj*ͩ`GUYtW o&f>k[+ MNUڹ1D$IA@D*(lXCA7]^FF@EwmC**VMQ8s~YJIkz _D1tPj Y*uROJҳS9ə}o\KDEҥs󳫮McvpֱT4k j\,$"K՚/ Drk'$IH4#1UZfsUƲMjxD.o[M EQ:n#h+8msFGvLZpA[YDtk-^w|mTӱ\ɮVJB6eral rdS+zTvLwhdWZxW17s%+NFB?=kBBeA ? 9ܛY ']UȺ}ADH ts;~ۧ^־R4hPBG$@"l'N0Ds8 RV\\ogzn^þj["vȍP i 5Rp/}MN@q+eUU{{fV$!)QN oeVh ۈ-W+9[.Vw__^p=@HNuPݧRRjE0"FApB)Aw5?5]~h #ls=ESIIDa{t;&.a]V^+LKA|}vZ=wϿT. ѻR"/Fl7L 6y |')*]qC6msHDB=T< eV˵=rxkGOc*P3hDT^U^QW4QM]5-J k6[XLGsF"LDuKz> CFF$IȐ$ƸAP!!\P2% ڢ}{A7|  ȁskrm P\~W@TLt, V ses"O7rM(.MW3Ee y< -UUy7J>ھWYyeS@(FÆ0 zNXQӮzo9fV*UU;zޚ[(ܺbڲ|E6I`0&[S$¡wxKӕ{okrG $2kN/B:ሦe]Prf7*3ƂU ^!-?﹭b1tuk9щ1<ά@Tm?vPOX,T| jүRhɟ<3(Ud<)(Dki+ؖk!]3MDa˱DDS0 Lm_T *D|xVmj (Uj T| KIY}>Q.n(lZg#)^}""Hfn`J_!6A ת\`?6XVk ENuV9 gsRR֕m'} 3|Z|Z5G~#ړdu/{ M# 놵Tf[$$)p$MjwE?'3$Dh( Ew=_z{m}j n4{Fg JULf28kׇNN>lSf_s_C$6.@DZ:Ttp"VױW1UuNڞ$]{Tjxs[49{BDGcN{RRFUR8g <:?Jf+:hBLQ8c|CUp}ٞ IDATmyBT5t" q"z7}ϯǤھcm{vS'߾4 IRk'^. 0ui9Fe6'ӳ\v:։dTOn JJ<#1@A"Zt;F<[[Mm\"Bl';Pr|vGa*HO4~/C{֮d*ʦ/L_7^@g}?߮⊩G 1\U! K t 5l[ڦU3mŪXPgi9_t%sQϾΞ}k9üIoإ`YG;9{rqwυBn;|xfk1 B' )@)$cM+ !:vA7ao7HF2~ιtE9yѿaR:ׄ-spێ}˥rh%4iTĐwm_?ddK3p\$1G <_Hddn60) jԴnbmƷof IVO&C&3? Gofp<}ҍƪ|ִʎ].([zn۵[vo[\n0 zma!Z/ ODk2Tܼ9!EI$)PkglB+è褑N_T{z7K^cP r |7ݓ,  ߷ܓ@@ YHvFr ]HBxB_V6_|>.<#8@P4 Iz Z@( j驹xt+Uesi=;X_SG}=v _|ێ`^^sBg_}^p8|h' fW6i}or/͝r4hq,D '` @4IE*1,Tި\RwgN, ݛ7疗{V5k!]YX7j˷LlJu=x/3b~̥Rypn4o9W/UߘZ9)|΄OϤF ٩O_hl+zؖ e|fR({…م\H|.˔Vrrҹمs/>k'S5Aww]AsFT,+$]zCf+ (8!(/_y`0蠃nL ji\v @@($ x:mR5%iYUWPX? qe|p}{u-ypaf91]SHATMyBo$zΰ2ߗ Or; Jjqy3s\X!_(s b!$0sbԕܾu3 ,۬-׭ !2#ٟ?3;[Ro!:)6mUO*d!"#`"ǀ?uas/OQsT{tq :ga} ԅ 'h ߳ww$q̆f說*:M};'G 56҇0I3P #sFg*c2k³#H[\ڦ`To4N]bF}5ba^0FsyHM-.ܷG8R\\xqz.҆*|Qź1vp6kT񄦻ݍNruۥ=l]-1FY4[}-UNDA7a34(M]uz]8m nD m)lԢ8mITεX'EUx29|{}D<¥Ȭa1bS5#ULQq-B(n@MR9乷{~n!N#2RJJBgjaKs@K%@9۹4?/? ?wiGb#jR]` /NDs5qs~;'ԖDlŶ'db`(m'"BD Qi4M+;蠃赒r{j o@ōۏZBxY!hq A-=! EQ*?򑏽#]IP|-gL*r\16]0مcF$sm_mţ!]\EKhT<8\v5!Yk%ZU뵊mJ=[.j'{סi/  ku2 Y\uL)IЕq?=3 pHRQ#eFͫJJ,Pi%unh];¨mvNy8w 9 5mlLǣqǧt_ז[GU[vT7S*̃>@fҙ;-Lg.*./ѣg=ntVLy^ O|&\Em|OH:IQBo:}ZɖP{s;1#q\W~*''4-)@XS+FFvX/n/v#lXu9`:I8Jh Aħf1ƕZZZ !-ss;qܱ"cOuBɜD MV# "17[ SdSGYQ =waB]!>sdblB Uf]*2k M 66*e2~X(λ̱ ++yBJQ o?|I#ɗ I%*)x V'Z :z3w\a$)lh[i$q:Vkmخɉ{<vn4&j6b2c+4RBtOOI5J>/ObZVk8xW*&žݻFG/~7EQWk$v}`h^~Kf$qi+SULlDUCN;:^M5͉֚NJ1GV 2*}ON6"fS'ɆA&v҃NX-W7Nxjb$ q VJF\v[fN9$̩Q9/=qreyތDĆDZtC|b`+6!411XJ D fb!)_/Q]wW*ݢ%/Y6'>{k_cϽܫ_uӮ+k|7w~'y?7A"? m] "f1ܼ8'aSg&}gƗl;./"N,L*S;zH$lgd=CCgB1`4}BqfMyxzLf|pn5#h*2s")cyfm]h4Ca8v۽#؄GTr'6L걞v;63s]z0/$_5{4kglxj(ݚĂ"ֲR$MY:埯}~/Wjfgfg~/?O=/BБ#q~{N~۷R<9~oo~k_?y]}o=D윟;nڳ{[8}І(3ʺ&G;GD6Rm @V>vơ# `hSJ w~=GOHfk ;H "k5hz+ZYD1uu ֩ZFklb^JiGMZrY'OFq+aCU4-|}wFGJg= J6V^ӖlVF/Eۜo)`#U*Lݐm " )o@RJ50c`J/ܿpǝw~w v|K_|W_G ijw?O_|m^=_W^?=5ym^|= .I)}TfZ__җ%7ͯ7<wc`T9.%fhhu94Yi?Xl=7 H 1;=3R*ژF|biy$ZS ^So;qBǝFh)+qA…*yN!JQdzV܊b'=h5+!]77淿㱼S)?x~E%/޿o_&PJu:~=K.}ڿqt8;1 +F4|eJ$~4_lr9];槧}ߨV8N XYa 0BrzhURIX*),h'f9lX&V[ Q(vAy H,x:jNGeW3ٱ IlX$Zn[h@r jͩq"A7ei^ܕ8H Q]QeD!_ w<ȬX}ױǫ&$3tVˏ5@w.>21wuo7o_~%_zI Əڢ(Z[+߷iid>xw}xRtCo|xӮKsˇ}4[|.C~o>|ߥ/L̀%"͙^̦`MC?:{Rsވn6 svL1Ŕ#u\T=DnM̌JY++0V2A a툑`(HL`RҼ}9zty嗌gszb;QZ]_[B5̶7TO{OH>B!e[~+ZS FuݻɃcrֹ"lc%,E{={ٿWx׿ys0.[8} _M{ \s٥?GRib|uC"#C ]rX}T4]?sHW^|hޑ݉;F%-j & T laõø%!&%d#e6*=Swٱ gkJyюp둣Gկ6ju-Y6 m$iD, >Ř-즕B1g0X H9ﺫ('U7F wM66IǑ&A$XMeF54yީzh\Xl2؆R%#2a@!CjiAi?L;GK3y˭hI96*^1 f)$P_g?[ge |![s/KOJZ[Kt.p?C|Wjp|>'>ӱǭ߾;._|~D+88xG l S==]7yGb3&zPdy*W\:9^x\Y+Zv WڣzfJX!G`iDZ HFb&,4:L@fwP56 A [aLX2{;]~Ѿ +\{gi|Ka M(IQ]tGkӎ" J~P͛U^tpj _h)⦋#$mYkj {o_a{/S뮻㮻>CVhTN7~͈`d:;DDH%x M1 c'=\Vnz!Dx͙3+Kgzj'On|ꉉk+P2#ܶv"0Q30KKкXa[_iw1 A<꺓Jj 5kNwKS).0W~u$J8<$ꂠ%| u3[fmnw[H2tY-{K|g?0v@nI٘vgTnSz,?3sr%?cnvqe©ӋgZFQXxVld8JbO9$sG`$Xm0[MwsƱVvWTDq vO8yz豑};'pme=(11. %I٨b> !퇾,T6x^8v#nǜ)q:zºŹvq文66p!E: GZԱpC/ٻl3'O&cqҿS IDATP`9b{z-P"cٲθ՜qd-2,)0s9a'HJ$`)48\eDIriI N/߿Ɔ_XDm! sJC`~X׭_d'+ak-6'o`R1D*/ypۉ'_W.Y > lB "Ԉ92?W:{}5j5L41[k^S4AQ; ;鑍*gp @Fd% /a>LmplA(WfL1q\!ܱ#n iitkm̘TI">`B$TJX76:QhG@Өn627;a'LV+$ qɾ=#L+EK"ExgE5[o)O F,`Bw %^ƘazyhC`MrK .t  X؍L&Nb bAۤ٪olT* t>8e( )H\nB (139{zd,8,S򚱖 rJ `JD"C3\6:[7DR+H;GG=߫GmD:^7X{ZҎ4z@Ĩ2p\wZf,u&eA g}j.dzpg0hf-Ng2=?T7\7ڃN3f/[,\8qVZ+a eM#_@@\mhu|):̤pHBРc*u ^F a@"JIwFrDT4s'ifLeӳ&̠s,$ј(oY6)&7 3\Ck:v HKf8&H!}zr|eF ٜlc|?`!$IA!9%!8O1+Az JB$@mK28@ Z1ZlKB C]vq#A R 2[YB+If0 f$ƞ^Zٿ{T'[0J[ 8)wɫ -6垔T І6 Ҥ^*r˛&$3nA+ I!stֳ9]k.xX X61-u vpIa]% [\d6X%w#fvF 0I )DE "("!H38=; O| J20&NShH-6fi]O_iAw86 3W;MaƼuxz>IyoK/zJL1=V(ΌNMNLhk[Z'FFS)"6V H%IH(=G"6a  &$@1R0HpS8 5! sb82H94(~e+Z& }u'M^L]2sXؑm~lwT3J õ9]WS}/V_i] & Huq(rF],3$ ;w6QdG Dcrr|vԮwn]1-G16f$ K"aBh)Xica4LBbDe}֍ - @Mz X"A$@`B,!B6LH9rX>+$ yE~粙ƑF}{L3 ff!ML q)G<] ;u"Zv9mde]DQѽ\3:g$a$>ϐd)'g'Bartd箝zL0{AT"{BPdFr8jViF,N,c@~FVȂh  yNF@ $0 zjFVύO@$D H νaXĩ-6'&밦;]抭5m߿ϻdbsѺۋD ͒g{:Toovzګ&\1IM"BJ%앗]gz99%2!*QX-wP$b9!:@3F[_.@UHkl#3'!NӉ4(6XNe @h4 ɌeLnNr2;A6MxӮM# `N%zCbp#l EUd̬z]]G mhzn}LW_ n-{! T?D7zX`EtB6R fyEHڍ~ ?6;j7GGKNsTֱtԎy+rmc}V ;E֊cEJm4زEX1,(W@I,P #F0-`+Zn0 0; 1 ;v{~(| d3/B|jȉ!$<3,3g=k·][!z)J]!bk-Tr8]f C6]wYMZ Qg #7FP T`O'U@ONLKb^Bq! Je]]wE&rxn60jc8Q|N3MJL( Pemv[9QQ}/ǥs)qvgbĖI9g x3rL 'H 8Dc٠(בJLM_owݭжC$ ~+(S'Yx|zEYk "AZ^ p_ {+]mhe0\@҆X1-sg,]!"ˏ!4.ۍbul+Im`(;ƲTalڋ/~e}7[ZEKSżJk}6 Ōқr <#?#Gj}ahC{7&ҞȳH|Nf+ZQҏ|cm=@%$)啒V8CT.xr6Pa-t}WX7[j&$]v@׮-_羿t,̖ A섚A ` H@峱1[0'h[(cSs0[B's[ Nq㻾866IbW1S/.mT4Nuλ 36V+`™ ~HJg!᳟;YPGR ؞$_4{{omahCdg RB.kC=, !|?DR<|nZi,.fGʒ?1Ph+%)(GD\Gd+O=O'4Q);O}kw,TL!bF>1w[nl@}G.HW94Wwc z"`NGrCHhv>M\)\:nn5V[gliH6qi6˴A HXN4/OLHٶ2bc (oĩ[F̖:jb$Dll:rm33zTKD/} {굻|qe6(gʤc~fPH)P1L/ˣ܂I$[f͏)B\>P"0"q3R5w zv-> }]w&hSbxB1 2DDg躜Q FHa kd#p L:p|JvβeC\=wյN60qs2m[ 㲉$,hfmRE]!܉[>0sU[hmZueIfl$ )[=~o333RʷΛ&'&>?qbvf%/|zh w=F{ЇOW=jcLZ%]WCL_||H$Y3,#1ZG榲`8r]HY$$D8VfT0(sp\6hLMܿg#wS![JtGHhszLfh6#˨L %,YDNw"*"uŮ4gBz#3wwgoķF6$!GO;|D$HAR hd_- !½\lwKA h9'Nٽ])7ywoOX^ٙV `qi;ݷWƗx"o#@IpP+;3p}l~1ƶcǑtb!qfdu\`),4Y;idGD +BÁrѺ$nW8Y(8cg:R^]%(SKN{l2 cRѸʖ|V ` 8)"1.i+mo)$c^yԧRMM}]|LJ?v~c392Ē]kWj!\3ՅllA:eP9}:pb0 @߹{ ꫮxIU cc>}k[;yNyidcx!D;]Ȯ3K;MHQ 峹'g<mǖ=%!x,Ɔ%:.S-L<Ɏ1qk2GV iSMxZb24؀b$]ǩJiCBDZwȗd,eA‚ )웙h}ϟo_! +BZPs0PBUH)x `0Wwٔߥ^kُV ʚfώkȡcL}2F^*e|IfUwkv2QXYfXʕvr޾b=J-k;+;n'Qh-ZV,b$X֚:@ё>8V6` #Ik1 j`raN..ڎ,L XSg>_Y\j4Z;MjQ1$k6I(")# Uj7RyLىɉqƲV6*Sp-7o|BvnʉaeDB X8YPf$U[V0:H+l)AAw%{ǑѨc;QRjuVި4]!6#Z ֖%I)Yc`-&&E:nxruTq>.OXrp2R~H3v~\wc>RY^ɖJŵolTRCڣ8IV +`Hi laƹyDM˜@3<R N3Kn@쑌Fa,)s3|hEa.,.h҉=w?>92v1\n-9.ym}ĸ?0>ۦN}#]#0 Ca( 4{33,N䁗B6bT7n fN,PZ'TRKhztV|k4Lt4?Rё~Z}MF*bk[}JbwؽjOؿ\ݽkR3Cڏ>^cw`Wp=%mB @H5MQ,o9qTO?%\q'l_-4,x:эŶrrtddjIcDas\Ң ˯)0:<]FUN#g*2p6B&\Png7u?Vn M6+eӕ…G]*lxթ' Ѐ\%ڄZ[f"5YV{DJɆN-uҳz_t [&ρoKi#Տ{_S;>я8urfzK^驩':x!nh'Z>ҎJԖC <5RPDVnq틕7יּ4ά.$} h"FcGY\/e#׿mQ ޙ95L;ڬizsۅ^Nə b6;em4YodG\%``Y$QPeS5("%:2mguRġfI$QZ('+ZNjS7Z% IDATU[J\ "V33%DMws'NwT?eZ뷼ůzwX^^{~; !Z qIHIHNtN[s:z[\ȤO]n0 0nxc'j{ƈUP i''fbifrll~3}п䕯>p _! ㉸U_)A&PD`{nk ڀA "~ڦÀ/|{%mDF )(J )cKe7v#g}᫧,Zm̍䗪15:ic b̵Ϙ޹;|񝃍?oue"LUE7 2@ aANb ZQ=e.da*+vLgWOkKucR( lD(Wj3™ZEVKFPĒHX+2i4CJ3 A DR jRKWroZ|~7)6seuc&"@ 订}ȭm\=W/OOr/Ew(7EH"rrv'"`Xv=9`Rh>M76bGȑR01_?dGYg RbۤV5۱Ilgk˷߻뷿/?U՗_M/P[kdڔvL)8.8XQlS  -DTD#юTv9])r֍ҿvU<{>VG\CsjmP5s*nd F)G\ӣSSޕWO0z[Ƒ3=iׇs{_RO/N+~B'2-yl,ڴCnD&dE?{&U{νU9LQ#Jl,K\`aaYy.K46hl6rre+rgzsWW?5KQ>}toթ|N0 g,]۪!_mm 3blOE{ƞN$VwخN@# pȓc~5-vw`<-LU &#TWM߿t,dlU@ȽNH:jM.\UQTSqgwriq'8U`NꝔO*-SΕ A4ueK(l f}J{Y>9̎q kغUw=][^|h5WN[fׁtπ>FRL I%"DAQPEtU]eгuͿ'TWj=9.h?{ןߌ *yhSRGi3ɐ!0@ȧz<+Le(,KZ6g1vJm( +xbD7+L jkO+<" J)d Hum.\`9ˎZu!)Cs 9m]/2$ϕcI8^.Ŗ*2XWfY HW֐4΄5"~?qb=%@3c%r<c#IqVdaUHΒ84:6 U"ӑ|Uek[wwꣶ3~a G`\:>Io2! O$C^uŒDB端,X6h2DXfxvdVk`Xw$o&={>wvspvK`Y5Kﻲow(9:e6نsfʶ{ۛ|EM?i"IXnۮo|bgP`T(2&4U) 82!WwEfWpMHTpg=CBDUS…S9VʹJD@X*sBʥRlID{j\yu<#EhZB &HflX0dmi10l4gG:M[z4niWeϲMs1S95 mҮ {Cn 9?j̒R`FMl6@9kl]]:3W^P74l>sBH<1TZoެS6MhXH(*S3 Cre$чqu68ˉ TVYR2H(L!t8ϐ2s~\8!,Oeq)2%Aԏ-\^?% @XrBq}S,%Ii5 À_U=ȢL$x.y"!qg-{'Ʋ&H@@LHfg⌹K5 f|c$%dq gbŠjo8D|hؙ,Kr1 m ,yY`JJ)'"iDМ`m#/-(n1X\ N+IJιBΘ"m[AD$PU]][Ț.IvmC18iw Eg +鉒JfEJ%&rY!`DCW\xުq0۶!HH@}^Gߒ3|iĄD1VxHf$$ BuK7Vozg̔l\"#H@MERH`#+b̐,Ї$dJsѸd̡yl:)ISXќis"%4]ʩ{[ mcroXҾS((GH $f')Mc2{SyUd ;;Tݛ ?.`O>g;M7Kϯc}1@pV`˗UXP􋃿ޛFsbc K*Y-C,6>Y&P6'7- };KddXyO$N7z.NqS<,D"9)]1\;`NM;0 *:m!i>TnMWpNahK@-a(aIO9@\ʅW]}}RmTR@u40ـAyY(kʒtBȃP(ҲGbB G c.4 M4D`8Ya1/U@!"愔>ΫU^Y*i#"!$c\QU![B$frpqsEeB.2[S(sVNg75Jb(T"X4UH^dM3G8dGvf9eQӔ"z=i. Wo>`ddHd#"Ȁ4 3Ђ01= mB/ND*~H4IbfDHCFNeJW @\HTfҹiO/ a ƙRQ)Mƀq(y-Xi5cLH>\8U6R01۶hs1+"1@D̶%ۄzk JJ0\2%$ I @:{) Jz+T5-qׂ8ldzcs0Y '*qhy q{1M Fv%'#X-Bdcp4KMʥj c\scI,>-Q [M$Q$BH|KXq :&S\ /pq <9#d,d\NN J*ےP(ŲPA1RL QCn@Y!NV4[HN)4z^cS?448 6#T*ڠe08TW f!$Mz7]HE#C&95ǔ&&Xb:l"<NBIDi@zsU.\V|^(Ee ˓λta5'jj/T߶pJ&/% p#D "s8F  R|e4 N#jhL,].\, 2 YX HrT7ұߜ6\|Һ:ٱs#?^N.Ʋ@_ ! m$5 V%#Wpqbe bDaEBBJE] 2ƅߜ~!UU?]as?Ɔ5Vc[^T.4#B,(62ƈq2&H;'ۅVJ>&DIHJ,'QtW,?Ͻm ˲{o}O.^vWMU?xO?ri?c.N@@"11΀!GTTO"ILTV9.\Xٔ0:aʳ"pgW/~}[޽+-Woܺ}BnӟƒÏL3kV*~oN"z4ֻ?R $8T(P<` dK`Tdn' '\6턙#aތ)Rq! d哀˗+}GK7l ]ʽo|'y˗.}_/\}G;:css}sf~<+b=̦~D^׻?s9 s+kG*/S4pDʦI&"9GDDVF]UŤbf\.0I-\`Em%A%A'Wn۾cͪ{oz}n\K6l5Vn߹8krgSϛz{;@1b^~eg^r|~-~o_k}kw׷uTIиEtssȡav5ƺ5kعmu?P"8c HHzfkj+#_zw=}U+MJy~obdt׿z䑵W<'~:ϗ>pYǏ;%QGzfwOEDL!Jy!,%MѬqyڮ}Q[SS__ xe/F> ;]o."!C"*fzԧl5UM3la O¯L&~<薗_ ;j}wl_xU#+Mom-d2/l~i~K-9G¶˖.y',z~hxxÅCN\N u&mw~wFx L:7z"gN:c>z%;O\&h6R>ohz 5g֖im۹3NV e 0RH2Y[!Zd[o߼f`mT䉭>L&_yUWy][~[e۶h[xb4U ]dhxs_[>IX: C "RkP)+%1CsNn\Nw=|;~{wO[kP68rgd#3TA:)U IDATe}[~/~}6¹s}ϗomo[6$3y).@! G#R[eP6;SH8&'$IFāAӵ,ǷO}&L\{o|x{'Ow*h\쒋ϋrpFYZȟ5|& Ab7Q %≀ލ̜19l[eD6+HNlPdt]d [Xu`}Yp464,Y˽\UuuxBʬnP.h@+JBc,Y},ܻom[^]ϖMqssrVJ1nU;CCjI4?:S׻p11SL!49`jEUqwD`}k7ᾍ_.":^UVeSxRJB 9ZG* *c sކ&w]80<0pאַ Df Tr00_qծ\hl_p7M|>߅睻uѷJl_IgЗ3>DP* s٤f0bIM1)ScZ]L͛@7w%ʼn1%d9YX2M D֮SCOx͵7߹'<~\wJV-aYl޼tGtζzUEha}՞e8gAw$A@`OoB7ٺ93 !e;slʌg3CHHh}_K ޞ7\>J[ހg586=G|晍^kںelԯ)Ø X<3V4bڒݛg]XgG?޻7qΜcw;j`?^w/NtJ5dzWb[Gr|Jkuu66kioz}OsE@5Ͻ(xHDE*ǚиiݐtwυ*NѲmD( HIȯL^grL)95ur I!%=mV>Ki]H@ tgK@!pƊTA]ʐ3MnR 8k|7 _3# N"ZwW^֝=,57\2rƄT{eNlƆ?gႁёA +B|q[^to6ɣt6/|#kCa^PǪd@d(;sՙ=%=;NhD|$ >H3 B3"! KV.Nw8=7-[pŌu+jL,!OڻI-, ٜ`>g5 ypc~ElK6͟d}T 5kϚ6]L2K[^98K.}ҿ>=H pt>Q!n 2pm%*=9ؗȨ_,] =:ǠZExFMPw>ԕ7emz 1i9 LW g;g#)GUcUٔW.8y"7FOl3;b0\8{t6z'Vλسk_f<ӺK,qc~}E S6'DGvc̸(z$nX^h\@Rhܮu3SO?sZub~}fwYV\BRP䯒.gtq\BW 劵3[L I}cT3F)1tn뢙T]R j14dmw!W_@ s|>v{GMrM5- L3iCS:3P"Z6p: T uVm[[ݔ?wIϿHOv pDzYݡ}pQזO:z1KνzSyO~|[I$couرz U @:E:ȔRJˀn9mp"bm;տs튃cCBQE[R2#QDH ; ְn!AE 7,|>@W$^6%j4k`S!+XB~twWN*'i{$׵g̙ZG8دe@U=|Mi QURT{h mN Ēbc}j@,~_DB/jDh{楁ϪP튽#^@d \rjywOF&tݰI/a,jOuq'}!ZXUhnI͹/]8fryX1r}R8bxCC(=w7/^6K/ޱs/|+=xOoz":oΜT:}gN~e~mۿQYQ~m_n\޸m6\1zzzo≄{ Nl: Eʒs~U1ep_慝< Dᱱps 7:Fٸ26쑙6=oz7ԑ'ۏ,xe(^081 [j OgHtAR]]U USHEA@<=w1 y~Mkn_|^8o>Uњu+Bw۶ud̟U9ž#܆+VU9cյT\~:T%%g|KzfiMoήRN׿OJj߻/}YM=_[SHۂ/nyy,;_p|]ۂ~Ï>&Ii*,z Fp*NYJ K:]UaI)fF2JR00uK/̙Ijy-M^l11LtaЄrF$a 9gSTɬHag&R19o<˰2hndӠyu+7jBʶ(2Ç*{{z`-v{պڣbbm=uK#`ۊX"5zŷo=62{c*mͲ oCR[cD̾\7>3A0M5-{ֶ6nXT;#YsGYmlZ3iV8/O@UCMʘ )%HtLFD!,wN>n/u]-^n\ U8pЍ7}jjjZ߿K|3ZMg_xNl1G@dT(%Iƙ09it{ziec=v%.ZӢpW/Md|mMoʜs?tIw$hTl[LX4`@aAE4/9WL4Lys*ᨔBG;z#izfDŽnQV`TDŽP9Vt`ʽdˉ|™z?f\W4V[sZl0 bF0Б5UUNjNd,W .379$BϿN8 +;22go?^ 4C'$#a=MdΒBy;[+@(PO@V1@㢙ų-niZ6{ X5ų=mne̪0[,ᐟsu8f8{,N&aA7>>80$*=GUMQ%[T^٣GJ_gxR<"W|PU`oyГٴ>_U< ڻH2oKUH6 HV4*+*fV7kӹΊ6]ʫnuEl4-\SmƖpK۬hMOfjlK%Ɇɡ5!@kkK)Qzktlw_ws/8yQ[]'G6FFi)ōp,_"C<yaH9+%`<R7 ͛dcʰlČ"iJ=ϙ ʈQ'/myޞB<84^66HbG 2{D鑬1RnVjXcOUH@ qrh7 ߒ3_xy82:|ݫY6199VJ%-\|W#q(32MRLLgږD+qb_mE-#i*Wma>ҵ LWqNZkCev1۔uauuM p(ޑ#I.ӆNRv/I,W1fVװe*5xj+{ZCLSpItw焦0Kalުp"&W3242|3,5,ە!ݙs **2O {+ίpog[$Ĵ`؟l1,Fe&r_Mmֈ̚8״& .I+rҠ?麹onU2MsW֙)|Ooh[G;:?O^;ᶗ >r+g+*R9U]=VEB%qX: ]}dWaVΰ"29"[^"Ks V*ٴm"'s9"KH M0qlf 3͢ zv_u Vգe҂YӦWr5o}b]F<<0\c}}J+SY)f.\P,-#"II(9 qyhttjK.__ R,gx8Y00ƚ'S ))iOGWXy\T}Ƭ;FS]%A@5:2kChyuu4Ԯdʀ}N6/IXmCJW9 a%NRӼܯے~߾wyّn31 [fUQ}vmW{F˲#(Q2HKVG+mkwdK ^_U0R. %٨+,xbb,e"Z[|όBZE<٬eysv@8dH>+a&i<^;4v02#o!iJ3<2)UD m2=;plkUUUP0myrﶣ,\sikK!\w+۟xE7 ܽYg9݂$;X/-(pqds%BJHuPΥI[$LhphiAƭ}W6xIօXM }춍\MM"衁4jU~pf,z>;UFT&ޑ~-K |rb>WRR׸ǒ~&YUFч?ԕ0yh[9?GxMBNTDe j!xTQ5M4;q.x-;?2בUxAQGR ۮZT9}FC==3kG}2t-\Dbmnv3Cmq)[s5hf-/:lYY^…$9E#XaWJI眗VZ^i!fOTWWPw%Dxxн/ Rig; Shk ǧx}rLX<rFv$)QJHE|LCx[ ̸Oe-m }} k7*~>Q?s^W/KV-h?(˛9CHJ I4΄MELG^auK*~qÎm19CTT-KƒbՒmѸޭU "_]R ua_zފEMpxO|tȒF>;\Bﶜccq&$W]8i ] @s|(v.kw!*+.zy=R:ɴ}#!xP.z,vuf*_e|BIo">2kƉdzxl,3sk-+׆#M,˹WR%s{vK3\9Tfh[YH@ȇsU׎ GHem 1ܣ ϣªxKlFol]=\d>'!&JhO{g8M%xKkF6VT^(TV+#om$ 026a0a,t㩣Yqtf!cHIR1Ir3ڔӣw;f˖{ %! //!yIHBB I~ 0-1q$>s~ǹsug4g^t]-վi.PKs{nq6nB5E+D`dGfoS׹`N,tnIPzw`6gr)h.+ ( FT҂dnsS5Y*}g@J窇FMUFOVz%2Guk9KwvNM0c_{t5CJĴq`p58|.t~^A֝.eQzVȇO6O^|M'^!_N* )r0pd2TM>؜M-y:5۲ K~MWkۯstI X\Z-4]ÐlxsƋ:1 l'4y)BٌpTK/DK%u]J6_2F.a{o^7)FcG~}AzDcU_8nqm@w[gW ~plh9tӀeJ/*9JDr OqV&:DDotoXN4/oOk_IGͰ{-C"c;u϶&ffc1ũbb6?w׺fgcfmp%yzːj4`9P4䲙7҂ ;dUnmT+*CrT;i#- Ca ל@39pǵppPiv{3dž\y8r({jv;Jb&!Ub'DbʒŢzihJU3Y.Of҉Jۯ^mfFLC4G0$ vOgkMw|?ZdT($ժ|644{!(w.74Ï>_ KļQD9r@eMK :@SP|WwJ,eiVŔR9jsﹹHqtBD4hOrf/0JDҘ_Ly_HްL2yUy1Ua`*B-p0LR*|RN2{c'f/k925H$ZʶD9oB )3b.偑tG7[zR+w%12Rx@*wE"1o>&l):]J՛/r]=4X) pmTaC6ZӍu[gnLosopu>vwu}>ape[?|ѯ~ٟdh47o٬#<4cgTAXF ׇ3ӏ -x=R_^(#ݡmio 4Vg0.#$: *lѺu* ?t@0hxq6msIebx" 뭪]R'OԄ[ߓI<7 HQq>ٿ'zTKs|B?wv?=>1y! 8Yz@$!*@D8 ,Ƕ߰)5X"k%̡ut~cוcNs;Z㋶-!cgS73?x>5Ț#gTJ %t[=揾?7P;6:qj^o Bt2cm]7 0[ׯV6N<{<đԤi,ye -6x3#eC7=41e {Եwgέ s{z]I!MoR0B*MA۪rqݮX:/G]$iMo&$C]T^nčc1;ĩS@`m?~T*Z GK)S}RW_~4ͩ|ƑMYH!c!Zyy2,o*\ץD,n%ܒãL4*hkܰ6lWuĊ%[JTP33`0lsU};~{57prYFHS$%[w~3@(]ѐ‹;h[Jk\۳IGm֖risKQ5nj3rG:lx2h{.-,D)`DPudn򸰼whEE4 6}i_:~O>Hs m={&.r@m}J! ðmXG å`-X=yxڛڙ^UBQM-49=gCSK\Iڣ6-}xeL&- Og2eGTʞN;F!ik9V,z Gv.m? o 6B8iK~mog/)e(dss1yrx$~~._3\h{a VmcT>ހ̧?E$9|=G%O_z]o5Mzk6>Ic'N ƑMO֧@)^(JH TuvVR4xwKz[ P@˕hWKYCkf7mǞxȬ7lxuF,F 1ϡ_8 #@á 0J&D8 ZIW#kOX@nt! D3U! i#ۧN*#et1 T̰>7*X?˕zhod\1 Pק{H\Mgs*8>56zkow{"iY5Rp's?:eĐ^̂]R5|s"W@5y˦XS?3Y:gMmZ;;d7 R+cVFMHB"zڏghT8Z)# lO&޼w_?14b1^N$*R"#Ǜ[x"ӈ9 *Œ[O.-2ڹq=โO=hն+WgBEX;|x3ӽٰߒ ,1BfF u8zGĪsUq<n3g˕gF[B XR1_mTlԣ>J$$.b/ی䭷ɱ޺e].#d׬9=5㳉Tq7c*@s(n_#+klc~19AMs׶Çy`hm{ ֏?|: ӦQ2S LQl6HZWv:K4>˳ >y_;>.l.?Pq/B(5_ !|\*c.1BRw)g]:8+uڱJ[rܺo?煔bN=iT+J{gL53c<;0;H s< @RiTOBK()^6|5i+{ Ws=}qdӓ:+ܥU/{t65* ?j*-=W@ɰJPĴ:*WN~Y \\%Dkv IDAT~;;b+U";TM5#M; I$\dL2$H Iub2j8tn5@E l۾ŗzF=Zh;TtlZx3$H&sr~YƠi?]}=ٹ#[?XTvkv'=ݳ+uZZߐ^}Dj̢>^/|UW\Q*_>rgvl7swsJe!_~SxΟ|ffgW˦-`s Hr!f#Ǩ]HݞՀKsk^@*V =pIqpe61j!됰)#d(796q@@\ 3]S?'\j\d'u֫}l|hX.}TftKYW5 R qΧ34"i ǖaM ^ DK4Ihт|_~⩧.yuk^sU[M,0jSJIX 21dK (8t%I ^V^y{:?$MW]3pDh*ڎIL3Y0gޱv@H)k$NܵS-=ݛ6 n:8uK[g?I_e êgP#mp*%ӱ r4j 5GPuuƍ{ٜe޼妎YB4Ռ# TKd{[^w5x|kv[/{0җkَ. ܭAğy{/l_2x,u=~*/e=GMU Ucr#U]{~+1uz!lX?UոrԳl t {vxwfc}] xGR+qc+-lv6YMi.伊G LlwYXb:7xVz?jm$+\koX}[nV YżܸNЫO'^:ӦNȞ3"XdFimS9|b)v1^x;8_>6[N431#[.v[77Șib3D jN\b@MH2{3c*lۂzaPmL!g_w[/ZK3dŴa!Yn(;/z{=/=Nmw]g1\\q7ض_ʾKu l4Dq/{'kI":KldkujͲz'gґDR6hܞ]zpנ)J>5=:/83}虅SѓC4]ǕQ6sSdڢb C#*KBvjPϾxzV1 ;G̠LLOMN=LJx@Z\A tB)yt]$k,)-M64[I$vUx-DyiS|~δ%"nk^cdѦ_ףVuB/{xqSۿo߹{Ň},J_qeMMq/9T*>S{wpUQ``ݺcO$S)_βYJj@cz KTw_ِUrsvP(jcM (CDě r 1!eS,lE\5XfˇZZm1#**g$6t4j*kVl64ZwfTrhq19t%Pp!rάOO& V |I2=9lUA 9.VvFOrebDsӔJJފ<5 ˰X)e`eI-f ΩFg&4f'{`hd'̧?%\sϝ޵sJK_wz ?jZ8:y?-Z8~s x~_n:DZ'}dVܠzkCZIX\E3nxӛz{Elġ瞫-b1KZ6YaGR$$+:Goj{ZZ]{wG?|o)葾*dsKѣ'T*=׽]cVb.3n2J>=ql&fWcJ\, d8V,J)"0%8ȑ7v?RȗU{e7uFn`\]`| [p{6 )B0LW : jBu M̴CZZO$$$Aˬsuy ڸs-[/K_/3 kT(Hx{Zwm?=b}R $DOU@eq*>.zK#"|\lll(@DI«AHi¹"$|\su>L9;ޖ W87bRQ-s5c`wDP(M 5]mU`G{Ss,Q-K knd3/8:16#%A-dEºB0t1hjk1d%(ʎK(`!_ڵn9ԶP$U4\Bd=plldݑH\=Wh]A@ Adێ<4l'8t/l ȫH%O&ӴЁLv\:MZ\Z}*B-kCj9 "t!Dhdjh)*cc!-Ba]U#jRȖmww( /4_"5}X!Enk]۱\êB*tEod@ #xir"Dn|lz|!m("Ej hYۑ O(~7oq Eu2OV 8bEQ{67>|\|لlR%#)A=.FjǪ{ƱN+tFdնVhL(VSEqs"H=cm,tB PcKΣh8 ˮ8Tn9WLJYCJh+e )aZP&¡an|\tل#Q9H qV ,/wZ41IZT\8±4j5JW}HtpiGJWʈ!@CE=o'%S9@03 \ǭ:n0tlk8cAGZkK< r"2zwuoR n޲cߎmPǼ")B{ͳyh xx|VFkj̤ruƱG":8άzFBu7u[6Wt u ׮w,}gwF*Lc1+eȶo-UWT]  Y@_n[rvG1lV!b  I#j4FjL#Z,GE :FZ|כ[HsX=}Щ.zS#U^ 7E`jggljO Tܴ`r:T:Eۇ]VQ5lG67kxM7rG钸03skM[]1{VBIE *|_獐ST8vmGP8 ٶ q$\θleE+W+* ¨pcmtG} n^R)kCϧ>>u vնbD0f={@1NVe\C0(;:m舢?`P]hh"hÇA7zՊCCy_<>.lzK. +1Н+Zk^3"\s**JT U=r^c/1M߾n]j~=6US!-k%# ?qp%dH,b8nŕuP68<11vtbq vʹхJª&|təd&K⡃ݭ}-T6g)%ydˋf jӇlb ) $|C>cEZdWχz3tP B^fJ5@<*?j(j3h9T&u7wSN\&@˶RDB*BOsW^y՗?D6e&\r]&E[8mMop<5J\H&W8iH233{<L'K+k[S O @2NST"768*'gNOΜqVjlnkU5:` P"*sVѰ~t.rҀwtĜ\Y9Irһ=bxS$?t2Ԣ6Z(wtDo5-z0L re];Zt-80<1 E)z ڤpqġpd!Ue+eK +•@( 3'NNqMoE3L6ds!=rl:O\q铧f:X\2ծxɒrH*a~+J6zڮ8 _}B)DR!cJc^Z#(뜅J#cֵ4'8AEgáUh]UUQGڲmOG }=TqʙҴ1߉I @$ KǦbqTdH' yzv}޸hVrr|xj,Yvw+b**J9@`M,zz1mIٽv볳3OM% @K@(b|pm(4Iic x\mlCV&mvEY}ݼ|q+FE~c+7/cW||ߝw32:_3}Jlv)6$)@6-HQF ǏvM]\X/ Pp806ǣ*Z4⒢*)wƞΖx,̥H,f*!#;DcgFG擛@\ 0Emm MABֶ;֮]#%E}ogf-ͷrnP^#DTT ۵t\ht.gt eZzi@G#Bq傁+>!.ٌsk_9bGLNֵ453-U.kr~y34wFv]ﻴ]EE_}CO-^fk(8 !7H4inHT]qičkKBxv W ǥp ʕK6 :BE8GqmMZvj>Q5\.ŕ@ggO5BQ`,oݸaߺgǦa_wM뢱aYzP8WQ+()XWY)'SdɴH*JZ,RVז;{m#Gor'@@s$/RHd|ѪJIB0(#" ua˕+YWZRlx F|뮽-wam7~#^q@"# *PcTً3+S r oq}wOK,bZ\jl]gxXU@YΕS8W0j9]̜N4$Ác%[bm(p$'(e pȪ4&1Db|zlLQP!F% !fٽe-O<=)M۵\ %@E%ƃ?uKRlhH@P]1ۊjFvE ʬ|-_ymֽE?.\>yv<OZV-9!/ GvܐQx({wĒ>{V_b֭ E⺮Mq IDATĜB*up\ٹ|.V4t:U#E\ ]Ŋ-R0-j&Z2ՙDd$״T" SlaKf*.@"M/,Kx4zݞ 9g bqI5ö )r09Q#Zxݯ] Bz(sO 軼>|\\lmDD⛄DDʹ58;BZ$fKEeCaתx0,jYUUS=5]k 8l&,;ͲaϞxE8gy}mm9S5PAp(bRH@ DF6]ȤG^Z,a0)#`XBz "5Z2@Pjoh?>7bܿq=ɤ XYYAIְꀥr*yhn$ӇջxFWz¶m/?̅/Ȧ=KN#P'I״Pqܚz~l0{nLzT "-g9#Mi ŠVmը6EL&g$&'V3s'޲K*AWB\v'ˊp0B?޶jLa;Bp@gQFĚH4CsTH@=7=T\qbn~Sll " d)#_ٞǚ&:-j9X"R]D)X3 LTU6Pʳ5 d )zr]h}5ܚBS2=Bh\IAt:ckNhz  aCjzh|lXq\ Z ;ȋi j* \߃s)՛5=N!Z)[XRY$9L<0~\*SA͔JEI&@I`A8& IZB䊥a ! 9m+J'hf"'p@@8ԧ 97G螻D&nT ]lK/˿< ^w.^Tt>~ z~G՜ߑrx xӝ]"6sϝeAΛT%i@(`~>x޺wR|>>6 HU̒jo߸~m_\|@ v- zPJje%ҩTrw- ?&=P6+eWV\• }kքW6cF#*BQZ?ؖ+i``]!'B $Lz❁P q8 Y%J {&L}_kmPR1(x\qQesy<|W06EXS++[u>DTin7ǢBzr!1,$H pj4>9b:k;.gLLT̙M Y,N銙sueЕRs\XWuD:;Fӕu;9U= cضmEጡ+kTp^kǂbZLvD"S-'8AAܱvMTJΧZX-7O=LZBD)c VG|x}Pge1H @RZՓz>^%Bgs+ h 4fmWg[@db.cێbD@9 gZfp p&)+\*:"a$- %LAekQ@ rر6j s$BJqMǣz*SpH,;[N3łڈg:c99=ÛocJEA -V:<~={|:뙽ȐN@ !rf 2SҸMhT!0z k㑠IqEA3",+c +$3"8eIe\bqh|O+Zѣ|mueZ\cacf %sY,8abBΔO%qc' `wF]4Q,|{")ޣG'+1N+7j$4f OB NSmjCTltY6( Ԁ !hW*+*A,j$/~1nr|lr͍ՕV7*( 4xrO ٙdlհ @ ^V#yGV +KJ -WQP3e֚T̯^76G;WY̐e#v`:D$ߍ"ϯL1 v}=x{rNkseuyiuV8:66E!=p`vb9v,plcA1<AQ!bۚbL(TAI6 t |_L㉸ftyMq| 8T.03Wu`Dsk@[kk/bۋF",YYk-џVa+s#%oϴ}WFw_Z `Ov[))= `( J.Zw҄r]K$-)bj͔ %*Ӧ^\X^l6{jp⊾)hHŒR0(Z:D)"Y,SsqkevG>xf:ҁ{*%J&V ZV"#]E$9>-#z,U cۙȖga/D1pH)qsYRJX$D" j@DwZP3NIۺk@^f+rD2S;%Q4ݮ|bӍ !X H@!#Kؖ]2( I夛#Ǎ&Hisv/ Ff+r V3Axvc٨홻t⅕K;zՌ1SuޔٍVJ !2XzF#-w. )>{W.]c6EJ웗rM1ԟ&Ǻ)[2Jc+GgP_qˑՕvj/]?9&I&CXŵF" ->[)z; Q eE" 'Ȳ=RU ve&A=H iz%DiTv|qq%JaM]&)xAM= :zIuO/\HwFĖsX9r &jBR:gܿM{J[\BfZavfxW qԳwoJ_]: iE2u!m[ E!QIm-֓4Jݻo_=l/Q7aiu#H b(s ~e?zB _9na3J+eV9KTމ` RmKz4)tֈNPja26I|)5WPXE-Ẻ`^sl HIj]U@>T GYIfkGzƥv{-IX݋zQXKH;n=\/Ӂ+ 8D)lx@hfu~2\ot_}fzHe(G76(ImiZ!r`nCM\Xj9mw6u ""(4 B04)Qv'VYV3!TDЀ?1F@$XtEք`h-+ZatOub4גxul̮,z@Ը mEaY;+3I 0ŸCkc}ڄ|_7: RaVZ@r>] vrv{!Mǯ47j/;dY ("R:;=-Bz"ddH9!XЙM*+  YN#GhDBv4I% χ2j4_\c/εT9X)z@S-,Ú iBPޯ=-ZO:rR+U;(rٮlY (F V-TzĝmD@5KC4ItD~SRt"@vW.qC3(ñrQdЋ~h6͇{H i" Ҁ^f,o!f̪Roݶd[\;Jˑ& DTĞrAZ l&F[rf -=]BkfxD?g|sid.(AR*fVsim3q.ST@S5еr8Q6J6Fly FvHIEmT4qk(` tH)O :=b.PbI%"&rP,'v3mk;!P BoڥbMm6ɖy,DXd z-gA۴3*GoN',G)(sˡ (Yg?8ӽ.qv1e ڷ:B=Sӻ&kc I0V QVVK/q|m}<:33=>^3FQ_یKli*,;LYXCb8mPs"R_V"\?G@}vM-,.X |gm=o 66VkFBrNPQZg!,,mFE)tLP@TU'b>DxL,-XA#H$oġC zXEc@.za1 J) {cgov:Y @#3DO<@%J^ kPBn8qNe|q!ZVRy{kGo<'Oʨ;n}c3R} `IDX6dJ) A(w}`P{p|xbk>(lȆwz̤&]U4TQZAB_ r4>^l5su;fRlJY6nPpqVaNGʸhȃ=Mݾ0 dR E_izv%Ai*+Pc`vDR,@!PT*=Ѓ=B$W},1>яdc>?jh4ϿS_|Z|CJ 9VND)f},[aa>+\߃BAѤ:mS˦V0 H)- +c1* Qn7ݮM\xD)wJ^| pm70!a{qv R<3-b5P|LݝܙsFDc ԫ鉩Z1 W R9Imn=M 9f/ $[6BFt'Nn]ŧ">jGO{h,+e]>0~;TKq])RBHm~5'N!}1+cJi"EjZXL˥VNSvvo:ePzmN˭מ赋.ɢ 5Z&^7(yMT[i\1E41-Z Bt%zU2J&bER9EmT,?>hsrf=Դu4.c IR[u0=y㙯pwK>*Cw,rpHwtS.zsC1أo=w8t//<Ѓ9~om7X숿+ (lyM; tGdP1ZKfRt̔˅3F Xa IDATQ$FՔioXH]w.h η_~/ͅ310A݋R Ѐ"Z)%]Du`>*~Ǒ_kvOV(j.斱…NE<~x64'7:M]GMdY\ٵN7^s7RTD8T Bt1*q]LnGZ7bRfjSW>E){`cs3GGXT֋/K#'N=&4=w}މq9{vCۄSD, ,,7SEL܃(N o Wf1a67Z̕J fei!BO,kMF{ U(g|hri!Vk&/>{>X6_}P$@"H!z.hit\ $"Qlm~r?Ԙ\ (Sq*\xS.McaOWJr`l!բ1P `-Dc|rKݤ\|lΦ99vjKhY+kjZ&Vl:]v“a̖Nv BԃR-8S_0Y^Y;{|+"ej..jFŬd(R ADNE+uZ_)7ޚ!nwVpPθ(UaK:w 2)fM<|*ݾ[ lŻ0)v RgmlxL <3OZV 8vi7UeI5ISG9}cKz[S{v8r D+8 J)aRjD꤬}t{T*(M+b"F UXTfaĉ+RZCMVscuLn*y<sD@MKgϟ9y\$ Ҟ"JK31gfj23FёQ]lͲjs|\.];" Zz;Zo(76whLi,3t:M %atpYk6^ mt% B)4qv;H˅`$IzQ66_|o}Z|ot=m5x23Vt $X{BND%4KDH'{SYEN Iz玤!|H ln. Ԧn8"2Lbᶱ + ܖi-T9ޤWAP꯸9ݫ>7GzT*5-z@ y7Zzv1/LO_4ODh6r,",`b0P K6m& 2rb0ot8؅7R:'K4}/[ryjSqx~Ա>|URg-y I n|ZZFO<|{& Oijj_͕/b#IXPR'v#DOh|FZ<4U |AbV'mN-l ,ā+Nmoll4bH^Y)@+2DuvvB|Kl~\-)u^)tGw0/|3PV V~ŏ<__O*/83= `W=^?mFR4FŲcdXp}8(@kwۃ,%'3tqiwx,:qrjb\HaHFSJ%&o2fHi~r8zu!KQ LMO7r{NF#lz*ϯƑKt|f0ir,Jkj&Ђt'HWd\+L^dٺ҅(wb'N6@ѩ@yJ9vc{o\^h:v䤛^n4*+d\n4*]2u=sc|%q_?y_/> |F^N"^f*ndRJ9R f8,8G`K2x u&]E H(ZZߜV['Nݿϲ59lg:{a|_;^wa:~X/,%U# ZS] S^xU>7石n԰2M m&$2G-UmѸ==8jS$A7ھRH˺]Z#NX&IZ+E 451u8߻sz훫!8덈c+wCq#zĉ'N zsc48I{,|??S6mm^4+Gvl3cR"2&<%β%5Cb ]ۖGYr= @H PK$ ~($𩢿(&QϽw(.^^C zz~t}|8{HKW-e|5*Z4ݓd8q:9gӴww><{tb z$= \`ZyU+P׉\9 +Y 1LF/22ZemҡkkNk=;^,Km&C8\Gd+wi=gNm?‹=pRۼf=sOfFCt, Ͱ 2w;.׷Ÿ"PǦmn^!Hõ8ûKłn4"N:I2Qxnrbsf^v]Nw¹oXܲzknGNole TLKc̓IQ#u>Q' @ C "$j  Q|rzW/Q_+ypa.Y3[Ȥf<#ZlTJD {X, Ήf?׵t9O8'w<و0<:>߷8qͻy{x7 _oHEl͕y{-~J-ݺ"D 9"fwU5u]cA g7-{'ǃB(%)ҝ76Vn+˗z} vq]m pcw?XD;lZ1}p3"h@< Z,0iCNGgҋ]U hHKh/[Һ\& մndkN$|<ϷEֲX$1eSC4U֐Mw `A)c挒K#MzR"N96I}˻[bRu!8#n[|*.np=򷿱f,m(vJhS2'-DUBD'NWKbj=he# =U\LM2-) aV7ӓjjR`HM5A_<߫mTd}ծ\V5M8H HC&<%zcIAb@zd4@U&He匃A RT=#Zk0<kFJ)v&qs0Ǣ""a6C,v;D j@۶-2z#g+gζmgvL>0%ͮNbo4pSW̟yCOxq˭ߺK "AgLTL)ֺn$%0+P:Gh~D}<{m^OAgbAgJU굗&!((R )ЋR8{+%TI1AX/}̼;fvGIX M:sGItt dYts99ZQJcCc-&7f tU "+k +'?F es0Oͳϭt}Ղ^<#.v{tcr3V?٧MO??|S{ocSZ{KRrBv$6FA=ό\,_8:9Ͽx8V88;[?&~dϝμ 'ܳ %Ҋ) h55b* ~//1%ٍ")}zvu>Y?LʴͯtO]楥gI&E^|uę޲_d\?4ˏ?1.^u^jK%ʴYrΑFP5Mc X,lFK2&B<ҽ*vyb$>t n; &(ӟpWf+^ǎF%&VaxQgn h_対tx۫W:H׾Zp˞-JG.=ޱ?zfb03|s6vڥKI T;*?ɽ.+ȓs' f^zmW~礎ݫ~a^IXXp!MH@$h*|ٷNuy黇0%@]-n 1]qmZ;MJCD+1VJeb9v`>IDݙW s3[gHMoVS_o-&+Rff#_L >gˡ=˯& Ɇ yL3mUe_Rf>4DO҉$fٷδ?3 _X+j1dB)yп%3ƴ$V(O@7ƕ,H@s+:!ؑpn{7VTP cT QFqn4&fC9nm:K5[k5)"CJDXirT-، Pon4d2Y24oZm:x٣&򁲢R"tsF7 US?z>w7W:x H `FL/lz{ϑ=?zxdP+Or"yj rѯI g,x3jmhT8$#;JW Q'lf[n-nDFbZQnؐm@\edz2\#Gadq0-)A 2!O 9̢AxMHTJŠn9^]ͳ |rO}=͞(zf!pPHUIA̤>tf"Az?_cWvxI"Mp94kpoԱ&5륢T̜ * IleTށ2w$c\*i`ѹ-?#lRW+ 3kU76!fVJU}a*wn l#E(~gK+$$ D$(J/Ʊ/4#KT t˧[/8w |Ե >|.j\T)hoo66-'Kĥ H5AS@ut/bY|/x+R 2k|]H7[Na㪭+DPQQZ!]~el0‘o y{ BSy^I_{=nT"[ՐB3IVReWպHtަ<"~oӖ{1 QEK{ۯyeettx!ꭱzsSJYR amθHHv i- mQ ʾ_?<{qkYe oild:vL .LN?35c'2^1`SSB]XPY!~[SG8B$&. VI6!?JLD@+Q 9lʄZDB|]R&ڭn+\-t5 ;ţ#2I?=zĉK0H˜E4c-<ظ_#kH\ƵVO  *x"H ,pz^ JNBPZ.d/7h@zGߊK+Kf0Z d.mX HR  FJ/%I(UE@ ) E}BLDrp 3HZsl Qv2= z"{#812$KvGvt3sgff~e^^~y}cc^?yۑ#{UT~c޵+㓧N=Og?<ޒ:~u=B@`ZPDAE@Y%q_ѠAfpF&8 ڌ*4C7CwnpjvuGݧ^Gn[:<9)g3:]fVA\Uj! 9ݔ9 -LJ56_$B k?h\_=RO($kFF˴zjv(P#vZ~{"L{%f#wJ4sb+T+9kY=q >2;S%mdL`AU'D6ym4 ,WvYSS|^xe9wyIsGN~?{ށp\~ŕyٴ""Re'PUX#Sv ;Cg-@vtMuf-P@UL;gv>{j~ņߊ?|T.=ď̟?'* ŧ) Ǎ_slنƀ:Ma MLtG>pɩ^7ݴߊ'|~9]G>GtK۩kʻʁ7س;lن<[NG;:[{/W]}5~Y?8/;3o&?/ޏ={"[cهcy]re~|`͚˖=p[ #Qu&ȖmktU9A(%1!W[n]7tgڵk̟7Dgmߛ1vqw^:wsϱ 9sf" #ι{ R2]W%[4m2}I_~ ~o}=&֭_>wʸGhƞMRcI3e6d/Ըj7߲xt/pSrAnGOQ=rUl.7~퓓xiSSSjpkOF゙;;;GeF&w@Pp-O_rُ~ء~+.8/ccy+\|eN\7.b }*O 1/Zd&-0p@9Ur`*-f6&.OidLbmd[Og>xg9flٯW+G:`= g_rwopa~ *ɱ fAteLvGb9"(d0-"qփ=tٟ=-Z̋-:G>zN=㬼$z͚N<)W|K4fBǰ& fZb}"MМ-[!5}o$zBNݢ-[yO8og]or)on@ӎ;NLLrm_qEQٴ.aQEUzrC{By͟?{ml9r"<[{T7oonK/c6>>/_J^mٌrӂIIUl[벝?y>mlYiUMP8AWs[#V_~y&z45BDe'7B Q%b&VDK"aRrkɒdCkڧ!ml r挀IUz&b"B=#*->"D9Bԫlٶ-]d͛7e/yщDSS6>j]k׭6FKϖmުxavrh03CjL_il=n]NUZ(p>Ʊݨn E=ֲ'70{׽U|KGFFnM=aŶ{dhZ c[["%Vbɖ̖Y^Y/#ᥘGYv[}vNU]{֩'/Zpvv6sVI%~Q:`e6sM|GGG WE[n1 SYF|y^-i;~Ox¥xzzY}ij5^Q{Ww "殲}'׃ glfلᨩPDc-kȸhQ ;lنP:i %Pt+G0?ٲͨӍQ/3pX  zdclClv*G+ʲ̌TٲiY%) "L"&LE1ٲ E50b9",E.[-lFrSUfӅҌy݌ʖmF CdZʹ>C-ی[`-6қٲer זe>,>WeBfx0ÿ+QF&e˖m c ]`"f̓lٶzeYچNx*UY09)S5@f65(*JnHPg\ʖm2j`{D pf6,>QYJj)lٲܳO=~}FGFG>O}6:3ceK~S-]7~ڇ~y =gkm&rSOH"Bl=v?>2+fٌptTʲ"TQsΖ-0[ Q=wh6 Uo |3?v/yxw͘cOy˸ͷDs6G;]0)AU%3lٲ  \B=o.7| E.\gXȳ]ols= erEqQ(_75oxk6n\fE_o'?~?S^ǤiUf!A'=5OޞzQަOnhG/E(@JZ(JUET<24t߬|hCK,}.;-o^tac;,[vbUE]X&a&czey|> `͚5 .C=U>~rro,ٺv=kmfwij {_8'N @q @=Ⱥg^y%ŋu?|_5k׽o+X&~-aԣbPg*Xp׾'n,N\7 6Ⱦ{p%ijh-/TD * Ȋ 0U ;q ~b~|]_?K`[{>y?g.XpΗGO7z݉W_s1GK^;ÿO9֮̇͗`Zރ*O1sH;_e `ҥ;-/v҉"|~~/G:`|?dh}{ˎo=n^z6Lׁ*3yjh8mtdd睗yqGoƍuϜ/]ǽm=+8|Qw͙==߿@T֏0 NɑrhlSo>C{_ŝva+Z?<_lM>zm1ѓbb j&Hզ b9x‡N jK׿Yy)XdS:𣧞~]wx8s3.O~sغ!GCnkּ:S$/}9_TfxwGu+" ? ۵JPUb.U"3A7+ 7~?J[nqkVzAnvwu `1◿|ً_|Sv~ߜb3A1*ADD ܊5k,Xp 7~ m ƦME/ V1(v*ZR=(ܹݷmo矾|oxQGyTK @#㪓.CUnlgc(;.TJevaDܣsٶE۰a#]w}\αz͚ӯx/~+Vrcahkc'Y=,[-lG,Jj^!pp,oֽ<9SyʡGGG{ot#Q9Dъ8RvٲmL7{"缯+;ޗQ3?{m}n͞5IOO_W?z[&1V0H@7?ٲPaH +-ޘ  3ɑGp"f6TvU^ՑU|&܂ ΖmM}Ȉ#Dūs-bCR!.+-pZ ?(b.3L2[}6 3U xA}\3@-К]#٦H5oCP̩- ?XyU"UʜhC^lن"•Q#B^3Ц˖- =ιY? EnlJ4mUQƌPxl[ٌ&6|F@;DyeNk#,ZJ笄Izٲi]aT.7G7ieKK, -OwsSϝ3Nj^ʉɼtۖ;f7M܀ɀs2F2[6cz0EETX3i{^.m>oʕ? ;d#a̶4sٲmgӖ"axdqr( l>Ѕ]tҼIcML Wr0- ?YT N+YݠUg6mK/>UWcϩϯF=?{~?dhx]-\ƏOqP>@Ѷ_lf*1XUEԋ *nviZE?듟 K/ߝ| Ż3e˖b̼N;k'야:b"V(i!*/Ճ h=f˖mmǹHc(īǰu(Ry"GDԧ4b:h[ RJDpOQŠ.<gC-"P& gZ?*֩V̀F&ZD"&gϙoZ CahTqCF Ev5JsEqړPP}dԍv!3Tջ1{3w$a0t±QzPOT=sd @`K0 0;p-8U4j%D 2P"΃ Vb%p]αB"U"Uu8ܷP%CJ =ZRBHñ}{( IDAT>n} lن$< teK[q h.-dCJ>" *6NJܕ۫0::UWUer#UaMlaB,'A(D}RB!bv0GԸH`"ʿ'y&Ю|&*$ZVt.ŴYl]ގvx5+*B ^ALS1%ZG@PBDĪ 3@Ts=Tݩ!x+XVVwQ#W.Aɱ((’2"\t+XEJ8 pa +fDP%GJ&4|e8%U!喴ºK;e6栾$KlC2TM΄M#6[sLH+9n EQzzzm !TcV)y{zM>W쭯n+ȗ>Td'4EVgS=8 )P;c Tx@Hbzx]b0,dTIPA::6xvjگQx3;BTx<1T R}oAE;s„.S)}x3@P"3yR @J\ wK4DLNl(Ď\b1L^%FEdv@aPG{yk˖m8MAT"R(KD!{k0A)fG5`S=XvC+iALNDqxdVE>,.bgmJEJfGk&třT}]jFRK5t Baӓ1o%Pn,cι,zxZE"q!ڮC(ODZpQ,"D,!d }&k*X eRf"R_@)Er@$[0UmTa( Ց4Ub' $(8Ԣ'jP҃Tȉ*}BȼI *p`R/^P0DAsyNw?{aO{ \>n^ߊ/Ovʗ'=q_^w#xvv;55=vMNNV&˲%1JzN`"V\׌I:UIH6Ba#r4TIUc׶Bh7WSvøPf*E^a۲vRB,8T_ݶu3ysYHNU²h%$[EQއ130~Xoox/F̧R$f遛MD&դȏc-dpJe P-/cC=V aA6: ^w h5M~yy㷿]x1ß "kD׽z͚]vv{3:6:::zɧ>Blur&~o'm6/dfR.:n9^Cպ9S ZF\lQ]\mSQNZȤd6ࡒV{%fs[n3bpXHp't5nʠ D üQӠPX?/Q)"Yi1U.Vcw|`:ld(sl[]p*bEQHVJZLExXa)=^y^{񼣏~{ޫ*~y v^8~zzz+g/itr|o;~{?'}tЁ޿VOq:uEmoy׿|-M^xafp<,y&v6ΐִ9WNxxj:-H*Դ[\w˺$-ԎNʒnJGf6SYhH˲I,1o˭TmO=VL(Eg }z]`^(%ՀwTZ"+[Dw]/GZ n0p=+]톭@fr,KaS$5?6sTDLFeDTN74l:؛ESS]roVc_[GʻV5==c3N8j?~_c׾/9ؓO?c'I{?"_a(x'8&D˓2[[qIk&^l2nͦJVw|tXj+ lNIک8cbљ%comlE'U$٭ =|={P -Q<8 t9񅑹XkQ*וK_Yê-y|^+ vr6egL7]{g7fH]eɮ}7`!hZBAnsptLl׾U^w}v'~拏}+^VNއVώ~γ[ _5wJ@EX B(5iט$Wq8ҫr0d4LT+8G׉N wD7}W0kwYC3[aQ#"VL#P!vxX"•5 þڜ%q<B׺*fTR%&v="ڰ[IȒ4;jOD%REg/Ȇyj!,+))) 0@ 0KAGSA0@x)iR r-pL 0{r`fȸ V/7a+ 4ԨF*Ar7q̬ Q!Oچ~1S>++vEYsyJ|q `vڸqc}~ʮ[qo0%ΥՅV!Y0 e-~qo퍌̙=W SSS_g>}=wov.#ux?}ww;EDQ=J=$"D!i&FGCV$T FJfRk1Zo-;2utoNJe$p1%; Ǝ%yRq+.Vڶk2 t۶EfJz׌As {!^ϩY9f4>݇Ҳ9=:Vv֚41SǨsr ,+L"**.F?.uiBѻzIDX"=[vB^qԟ|ywZQ6lvU?ׄb"bˉ.8&EjF;"EzxL,,>un>h=4vrZ_=̒QCŝ@PY1ǢqVDu ^*~n0D[Tھ195բxhhme\&&&'V{#y#%;@eRQ_i':2tv#WvRP_/f{BܹCl 6b+Rknो"0VU I zVMjQ Y,t;\9ANI''b+/[2b;0VZ{ k-\hz2$PAzRT2Lll^U#!ˌ (3X8 EGXc*Ipsw/cݍ9$Xa˾]: ФpH&T2H\"+ڦJ^]`#RzL$hھ/I7^A`.PķZGIU܆>EQ國<5f Q+˘7ڏɫV|`pwKJK- ‹ )GLalنbvbkqxS133'3͞mW}?ӑ C7BҶmerIT5aAMJoyqv Rx(! xQ.`'_-F 0ù̅-.42;,*.ՈH>tFu{ PǒpB}/GBZ$$.)н6OmU4JE1YQ~Z7d~u$ `~p[R +i(kJ='^"a%%-[2;R uRXjGfS Nflv3IĹ$ 'yFtQ1}?.clb͂GGJX>)ɡ3v?ŮmV %MAtسchv/ePU%b4~N"4ڞU!BW5XD0|G4 V#`̍,׬ಇtRxW-Ͱ$#Vzu{Kex].,.c L=ݛ=:erӲbj lnPlwѮ;ݧbLRJ͇C >ACԐbP:|PMHvõH = E֙j7ѼEQhT'u 4`: /GuiZLM1ëF`ؒOVUiؗ{KfOP_Dv!߰^S^̜j[(ۡ9N.htDħ,M>+D0P͔ٲ RX7[TEMIgL4;4咹E% ^'tS ޽yN/3I޾MV Vn1l^50|R|rT-n뉠bUhRS.G0㪕ڥ2c*Hi@ޘ/}O0t Y"K W r;gb*kgv>tHef(Y + jوxT_g=lنךVv?Cy'Xj"֎f>β+ncʷKgEq:n9%")0u#RlXos(: ~;1UȼTGU dǓQHƣ7q3+ P#)*i "[5bE [79Nl((=/+Hc\okeN5{&E%"'>-6D¯{.sԷB@!Gж\-޴bbȷװ7X 5tfmꥰUfm[: hҒE~Eފۺ$2WW$$P !Ef8KBAbP**WT.=RODH6͔w@əJ.g}$"8ZmLOaDu;fSpjF~a5,p瑑ules{.<덄{Vu╈ T!lӈ(+WBecL\58kœ,m1)8Kdq lG"E drO7[!Ic:T)afWQ#6/1j)16uWtӈb$sQtbfYb6U.iW<5P9 GR̰K;@da6lںQV{WtY+jvųR÷)u)/:76/0B[0r*Ȯ:6a6N&gA TU"ڌ7Mj2W)' ͔|lن\qV_V= a.t#ojS2:N1Jbq"t|#:}B}T0@x%V/"ڔbmJ܆&cNfk)ha--Imos,AX*RsRBA,4:7>,f4k @4*BT+ +ɮBيz#P[N^Q_nZ ю%zGI"ylĔlن< \ H3ޡI,UHkZ.Q< ,'*9ST6%E$ChTW'YZ$gLmע{8;ŃH`Ɗח @NNj@iir7^T W@|X9Tp,KHZ0"0Fy7ZNCȳ slhx5E%ĮpuD4k!9S2hYt(pE2X-3nrӪA\V; n7䋄v1APRmCp&x@*z_`'ZЧX8kLsԜ?H$7zuv[ؐK/Q (utK-G TT &=Xq_N1 {6N:Ȟ0KRQ^D'DVʞO\.TΎ὇r"q[VLB^+}\xL\z¤L Lyd([5_L323\$mrȽ02c]@=A`=RfKukq Gj{$T`5đ#$H+G"d\ ^DI8Wk$mRX U/"mg .ѽEgY_@1UIg:bDv 1y4}e m5jٹX98A9 ދ\j \qz%=F֩"T .w;*hb2s-b%Vr Aqϖ4b>v%#HRJā[R&n&Q^lن+5EŮdzHi]in//DSk#;*w&^&"X^ɊKo6U,!@{ld|3A8& b2(w\cZRuh4 zN;Y!J-s$aj\ܞД09#m+7S}ǹpO}RpCRHH.mBMQi.z{RP`Alm{ɦP\3zrěL^َ$.Fr־ߥLʃx@ffҢ{,[m€bP7,mvaLɹxK%CGi8ȷCg 3qĭa2̽ 3M"/`Υ B2H&Hdv <~SδVx1GO2wNQ' S~8%sǨBtT#R va IDAT4H{bBZlUhErPW0ۘwD"R"!yS$w{+{^]v^_{ƷMλV}߸Xli.-J:G (kS4l#P0h"NZW' -o@Q}&I%f!O . C C f[It5y;4  ԷmtS0bͳ7U7~v캢3!h7 !x?Z0֗ӪubT4,NU0E% غ "PojL($R3i)( (,[M./ou?oދ=gvy_7szCknl[<*&<c$3<2髒#U"g!+bؔ>HBEZ*5{0A '>+dhEYḾ()Q%h}U[eY2M=R$T09İBfF>(!ַ:Ԟ4stl>VC!Iz);|Ү@@\PY,:]Z-Dž82<1D -0ԄdZ3têI ) 2*Ђ8׶&8ga"*A {"biJWڶ9ßs_bV^|~x_̶%\M3BZlFtU5hwSR=? eYR{%MHpItxQ:7LOYn"3G;m@& d$RQv"1NYH3pVP!H%k\/G w`iW .ԧRoc?ԍO_X2cLUFYѺ0 n9hz6%3Or>U$1+d)9cB{Q)bFB݊G▴?{|1L޼^\54@i;b;Po@KFt!%ɨ#l liqS巄Nu$fM:"AŁ>hVwcf*P9 Hزt%8e_ b]yNr6n/@hX4f^3{Q}I5F01^R+.tnJ|&|viUs$=ҘjR;zl+1ku^'R2;y/^[0ʖ-۰}`7uuM9%i8cЬu!KL<ZX,Xs [ .nznܥL mʑDj7imRzjwmH%LaNFDI0yt-Usw7d`rESI!bK\)ԍXm4/W5n(b*\"Aβ`+ֶs/p}Q #9d ;OoLNNMOO?b-Qa6d_N2.cĞXߐ&6_WqòriɁ Dg?FM)!4%+0#ETQ1ي}WYqKfAmRW>۱C5w+mؤ$oMҷ؂JȅnpLN[}%u<OMhyƱ^(5l=^pWbd5dt1m4֍ Qk Z2g9C`&@R8s/oE"_\sL |q@TRm]6P غ ;jTm!$"l ;EP׽UipzI*V?o\6N4뉈(4Iłs,.li-ݞ$Sƴ`:@] 4:?ygګePrjV,/֚p9nVFZկgZ@(8}"&R3kdcT5bԔRRS=s$덻+f %{&qjxR6%Y#O7bwWJJߔ.,yT$Q@T*C[֏WrϮ AyqF;m2iGYk'4I"׏gJe @T8A0T&A6JǍ]a8΄arw炒flkܸ}&(k8+Wrw>\db=^…M˲,vE}(9 EfL)Du[2\@deRqbbKH^;Ȟ$n=>"R)9#nxRG Qy˗-[{W좯 (rJV $)0@ܛW U(,&EDbdH$_gpm^-b9 w.g$lY6_Ra4ѶOZLι{ov6M?{:9LLp_<̋Ǹ*3)hpa0B+uHk fI"L:W1H/ yd([!5Ӂq> A50#'$Rv M Q&@h6aFQRnC9DNtiFdÂzVGTR+'_9~k9l+*yTѤ]Y&9::R}ɵ&PgeuD{3x76GPq9;BYr7L0>d9тQ؅;qG-X%!4_\/ ‚rWA4 FX|-[!M%Q⻛Ѧə=E3*;q7fw+{dO J86TfJ 72z]?_89[fHB6b]9h+ryQOD^|(MbnʅM00'P+3'L%^ \>{bejڑx_Š*TK@nd9z\%Bj}PXEXR@ats O*" jL&u (C}lۀ% :cȚ>0eTOH())O>Ftk"%-҄!mB GZzt0! I-{C3Q_[;aƷe!^hՄQIgLn 숃f{mK'W@mv%% e2s{HvL U3(sT@@#X`vpͦb'_POrePhA WBYB2y T4@ 5ٲm#nKAm q+jR?q4t-;`8o-u<"Ru<eP/&Yrč:sA4 )QIx 4Hc0humI<RLZMA+ P 1A6 !+պfekn$z##~zJ|ÎûCqpHMҢ)B엥e ?>ƴ,QPr]|@\0[""o[/ -bRUui:Zܜf6F+:gNڨaDJL]4xx[NZfk+*!R^v=2h؅%T; e$+l&tNm/~G1p[[6O蒭xQ}-˾cRTq[QZ+ rmYcLpfzIRPAb ҰyT Gs޵d6 P,i_ R Z0|eFIW%I9( U}%g UlÜFdf"&zp/6mv{QeKy05S;Bvhr@_{3qB `K:Ju3?RQO4NC(“ėMH+Y:-sc%~ 9ɽQW8h=Gŋ&*k\Ml+Qx֣;L/t^F -59r n mFb5Rj$M՜ZuY둼pR-vxR޻FmaO>o=s״IhFVBdcG_ |GFWx^d%k 06` ,Ym,HЀ4ͥg=*?j:k_Ch{>۵멧(yq#̞EPLwJaOh<{j~5 644a1Ҕ%9n,*^ Hf7_X]OxxVz=5 ^m:l:ix˩1TȞmn{l59G\ X[>b0w lceOX r8s)ӗeYc0goipިsZotQ 2qaVPeS)Ș q\IZ=%YA$TMhMd3Tg&Ʉ \_v!(zN%&L"ω3;)]Ov _<*)QJY u]UĴ̀XSM!!jV5'@e ;`]ZWH :~U~\_6۔%e oci,]q"j͜ADU -QrNK) * UU)L2Tܨtj.bKҲ(q!ȢBJG/38(&VdIżĤВq5ZI} JmQmw;pq\YƠUm c#;g{M*}w{ѼŒё4f'r.SZs^g |=v>zBY9l-K)B:n'7XIXY lEy`]*n].wM٤]\Ef~wm;Y%g.酛0ZTZIU#9mrx)iq?B"A@Dk5أL7tX =j/q\JeLsT;wY0JISJ],F;!kI,4I5"nktmsGFg6lx'D|)Nԕ.<+1Aj 3ײ/v ' v|_"%s[׍T\뭈Lʲls]ϧ&ZZ@o _^W[IBþYU J PkE!(XV'*76 ͖A&*z"qq}f:ҕ h0j5BLJTA>ՄyFl,dLZ"C䘄Os2? ̋a?>xvYwx<ӕ;Oa+ngFjvTDuNOe!!]Tn2_a~ Ru#)>< nGbqG*)5TUz*S""&}ٶrp^>·շՓ&$WtB{3JQZj:6`4kb8'p.`5TB8GG( 'Y!d~d ,93 L@Š *ѵq[Hy=og_yD*M*G 6vjsYvzJnO^-.j2hho]"bL,`ْ1RwK%2MJM.Cqq쓹97&~d6E,ov3%BpzZH j+LJYP^yjf辡 \Q!|Q%2[=6:@]H3H AwjZ/)OlQo'&v:RVpC}vM.XaGXv*Y!)wT+~HA"00/jtu]A_WAT>DE쫠2ntzUteZT me5ѕXǪ~JU4i;SdsQF;: gqu:oV ن/Q+-* ˛ԝY›RKS0Ĕ1u}f/`qM'Za9xHI>\eY6ڡ% CSw a^,ϘzjB^4$v;.⯇]2蘩G^;K)\YoE O3eCVcNԒ FA,s;HU㺢Yg:1`9>Ӗ8:GVҴ"kEfft=U*Bl9;G(Zb*UʠYy-03H H>wܰu6Qщafo;'VfYUA]UFR񕩈JJ$|e5p27~\خu=995#WϿp,,…^/={q#).yעs.e&!爛v7_O IDATI0zσY?  Ilꩉ`OƄ2T|C6`i!DFL1ѳҌmT~T Q5@[Zdpֽ͉yUQ\(&fj6LXPNe= =ʕ+W>xᮻ~+~Gιs^WԿWP/No7kǿ6,~ӛ^/~$pan-M tҔ )ØJvE"Y`G6.A!*Vֲ\mQ[ !M*p F>;o2PDwZc;Nњn˽c6[w}f6VeiQ|.Dz<;SJ_n%AMD76W+ypISiMA]L!ƳB"[0xޑ4MRAH`+fa,+)-)Ytzy.C(ʅ2zCQr{uLHnO_W#LPa~)_;왹"qi{vK^~K~Sz񋿑}_ گ~i-7Ưk8|Ⲟӂ@dJ*f̈́TzyPVbC1i] j+&b-lD^nƬ\OzBRJ{NIdOLA#d dTHN)o.?y YId쌵a#5Zӳ$祔Rv&D,ÿŰZDLoOw?%~)WU/i\pYWg ` {C`8L[5_/|}'>+^_t>\= w|!|k\ϗK{__x^-Oկ{&GtIP ˥`Q$ g`  s(ޤ=& ՔMUj0jDO׎-6+V1ɹd o&9HHiWӔե6w$Xqm v U%t*R<>rWM[#xnq7x e[#$rAb{nT-F81E# DNF:B"Uۗ#>77Eet8n5@JgU ^U{nZBQ ]g. ׫EMZisZDI,Ak|snk.Lk]ZMvuz΃=.]/>½/_>Ns)Ǎkq,7 4,f'qߍQ 1x!d)NV eqՋ\pzzL(.#ӿR.yry)-;)ߍY,lޞzN9v%t6q47R~Wb?N@@P̬MQ)QzL!Ց "bm8@"uޘJ],V3_Z>| "Hc\bX;f&bQd H͠XX}3))(\|_99J)&+5WFFQWRP|5{24=enfbڵٛ"`/-ul1Ft*Bg!AɕfIu'Z!n%iW1 QFV]k'B F*)Z50ۚK ;fΡ;c*쟴nU,ڦ )1%oW%UAL\"",uk1Tg@0L$¼2+Pz' LW3kmlA.~&&Vb> X+vZ>}>e_̢X7fVTf6lC 5 z3+ym{g'?s8ӵo䦓}ٗIO7?mg~<2MRwBK #/fև>BSE0mUE?82EQj*MF6oPƍ(0w} }S"+8DѴ 6}(&m)5݉(MUgPkUB~w$ {ȤzQSYL.}D=jED*̬BÛg>~ɋ_tZ}{~̙|ϝ;}ޏ^{7kMgϟ7=…?C OO?8̃_2;3wt$pKU6BHiLChө(qOܜzST)Ua&&5J_;NR!!%.tS{LTI_I.K (Z @6Ron- }҆} DjMM`킺sJ)Pt"⍌>"R3.]+<]3TȒ:&\tHw6C^&0 XH;v3q#0r\ݞq{M__ƯW._cq~iG{>!D:g&K]Y={L8bvV햼: 'Jg_W~HVx'&WQ`p`a+FGmj}ðrODÞPhۯR_@ 9%ȡX׵Ќ04躲#K֥Ua.)ԗH,VLҜl78'XJX!`| *2hanBDM|o$_J)J@Qmױ^cӽr|9K#otᮻ>y~~|(|BgE"ژDuH~yqZWAVVP`v!6->9i wkQz$$4gr$R8~,U3S\7R++4S6v}f?~񳈸V~Ȫ¿kxOY$ E@d@M 6jk8)&I('L sɟn*0l0#(IPSz(`bNf:T#3눥\6<ӬH83yF R-3]LU4iٶLsS|';VrDS;0zNLx$R%#i :Hl?nێtt~*ؤ;W8PPjd<`lI,;") ^Z;eIp/`e3T78oݺw /35/lMkvMO(-|S{}zMzRD,9Xظ͖Q 6;b>8uBcX 7b! t0MɒSgK,e[G\NOgQ܁[61Igċo},(Z97\&'@9X4tL#/f"UXfh0``n;;$C|XUJo6fO{5*ԺZLpi7etzJ)R+olf[dv%'e1@?"l89 e%ekgmIBZqax'0)YɆH#z&q\ڎ8Ӡ )csh0'y#t%ޘAF˝9:@̉nʱtP[W5:f; R:Mþa;;F--zqDo6EF2G Oˮ421VԟJu'g*X Ro+KqAi1xq)EU|I%U=b0Alvqݎ; "h?LZx4n#x&DdZUV&,Mv%ptg8WB VG7OvrK=Ht!QZo֕6YA*Ť`HeMg61"r F_'c. =7}ڄ]\;!~SeM 3lX̻R}'&*Ĥ`k-c+LNHEa&&5n`PBFS۫u+1rӗx]Z,Rxٛ R^>1q IDATnG=ˆ,=Ĩ3.<\w`i\#J&,7 c\Mi`7LD\$CBQ)*Ȁ2K^f[]GpMJ>z_̇0<7 HO{)AZ[nfG}~~OiV?jDBN83hzg^YOHEh{2388 uuCqFI^Id[#_)-ԩ뚟"rn\lE)e\>nDHm:$18'ԯ-b7:w0#<ு! d7USkuj2r+:vscERpe[ VϞ (F6Ez;n Z ZW~{2 2ۯ sUy\oiwRV}EXv7+@e嚥.884߿gLCA lb mNC=S#)*}%BRtK  1Ɩ߸q=v04L|ͥMǤ'~R>]̱_Km ><]&x24+j׸giג75鏮j P^Z+kw.̶SBZgpU-^uG'y>t<Ã}ewnz'~~{8RӷC33gٟe*"ċG8:Kk8>hns"Č6MCՄ[d͕?qK\!Z8\H1@jxS[wZj0<SoڎLУWv*sҺغݎݚZ3?̴(%;vxkܓC^> ӜP0kx,a;j M]$NEMZ3Z/gezzoЪӀf].*" 65 !>ә ܷ/u׽z>ǥ8ʺ`꽕" ^Hn5?b(ͨlq2.,]p>1e^ 0&iVHD "uձ^RVn1Φ=ܣ"!cJ(Ǿw</l`2#PѤ˂sHy45Rw&&BX [WYTYEvm|šJ-.X/Y.p"#m;XʦQ"7SADM=Eڛc;ˮVx!ݡ 0#b"MTxO^Y>Λh=+,VY \ZBJKp};vmGnW=w?-z^ƯŻ_O>y8 f+#*sXR1E%$FŨ\`]7GcedmdƐH1dzݨ'a.(l2_SIPk xϓhNeMMm_|aJVr վ`^=bJSu-6}ʆ .Cq~\1xu s#Jb#a==<:IDZrlA*Q[Ro+vO^JVgjz'$4?~0}_g&l_S_}._2/''/y[8s7x_=_'>q?>*7&^F6Ā A. `E LQ1 ?{`IvXE]V\$)S ~Rіi IRDNw<Č-s:윚xzllBh\ToQ4CL$( iRegc6YڟB*9Hz|5ǗڴUJX` leU{K>uUndzʹ ֺ_x[~BPQ0ϠAs׾~E3K _-oO}׾ݷ/v-)zF's]g. 8,.n'Px7TG`VRAiyÚՠ ]/P9(9%,fVZOŚf?PMku#'(fuL'yW{ &&uB11)*yfij6rK!Ob&r=&Bgz W+{ȅM[| J+_};~#—;۾?^{?'v/X_|k^o~_cz"c)ΝsqѲ9D~ڰJtLnneySZVwi[ħ54LgРK1J҈ǟA׹ =4چJ6DktTYB0C.3ۅRTNU9߈nY]UkDjJ370Uh1MC|.7ڔjxJ)L5DloWu]w]|P|r+|Բ*Ejy||z\8);]R-~3/w|w|??]֟G|p/,_g_乴WůڿmQ…ݳhik%\eZ`|.O4PXV%_A޼h(r$¾&t>FRȶZj6:澉JIYpTn`ȘaSd^J䞄t]/L}V>Lv 1gMjT#wڛ2!մvVQNI8evZK.LrR%a*h#+;1R55X8?%CpmfYkx<|Q<~Q Z;~ОREEo¢'zb.LfM"e=",Kb#bjH=swc°( Bޖ<~=hԼXh@@[k+:32LXz鬷]e^fW//FȓUz+'G n3;EuO`.Rk){33q};_WG~K^ ྏ~8 Ï_~L/si5y_*裏}zʕt-,G-Rz{lJ%X{[NshB[m]3Nr,= s_ݪeD nw&{$^mGU:?S=[&6 gCu&sF@1N`17 C0n9DuǖN} VQZQ$/@#).0ǤQf :9,0xJEٷcrVU]h']ވ.tg~ҝU(/uTdI (Ϝߏ}{??~5Ϟ菿8~]OE'L~ח_xϏS/߳5 {^p mZ׀.k fԷvDMZ-}r^驯tF[<7!f3qbx+8mc֒D+>mٵ1Zsa<8"z%k2~ql6K/M`[-@e-/1hku]CQ"wuU+ )11zqfpDh<8 :V]vtʏ/O=\}T^pEUD{7)Npw~Ƿ=…O~vXmW>[SO#0?[oTI<`@*)T@M ]T '*LXg), `n=Q~#d1?v zu ]ҿ)d7<3T6.uw~w]z΃+k%/>9sɣ sx|m%Rr: bZ%[esFDDM^J$Hry0=c-D}}7ϙ¬"u'v1*[*}~N1A7DνC8$VHlNM<2@zbp漫a /W.[Kqd[sD [*ߔ UYҲ,.-Џ%L6*`/}J~Rx%+B_p»Zu7Tv3|Է| ?ŋp}?}O1ǽN?/~q*6J)6sd!l2BeH 3D) 9s~~c)k84$;~pg0o]F8|T`S۠b"`E1K8\A vΘA9!dѲ,U)r"lf4Ugʦ A]=j3׸isAB.ӳj/ۍa;K)BO0RRک+Xg? - R`Ƴdb{?;ͩ8eK{ӗfW$SqR.aW9VCEI9 VɣDځq(i1m A ƛ|1ႦGk!|X|ir)$>Ҽ(-sZ5J{/aO[S|,[ǎ qm(3c S74<4lX6E([ 6޴ΖR:eVO #ڼLI5J#yHKt\QM Nڔ@g:҆[ pn9HMd91ԜtWfjK:ky074"^hL^7Tv1˲X"4a_n=ҋH]"-6& Io,f ZvZ ʫ@Jŀvq=Gܞy1.ks8mA>#EtKA6Ym~ cDI{O,h^%7r݃0-^Fh[<3'r@A7:ԑZ"~m"Zk&6UTBOyVFjqVa k]oceLwũwsmI лcKrf;b828APoU%[6gi%lGكjD N~R-wHP$5СTv460*x⭳"TCa&Gl#6S6#Tæ 6rvbDCq5;Mv;)4z~_ϖsJ7TZScATmȶA,R.#%۹,Qǹw *YCuCx. };6~iQ94)cKʅ\I,1#x 98}<ݱc~Q``2Ǥa ]$/q\BK򗰔0+;5ah1y ]|M}6 8JR65̰L( (xfc`鲕=-@`~%B11_ IDATSҟ:(-=wYRƨ M`::v9 KfYbz0@T(aњ7e'Ыַ9xz%>e<76Y+ƹLJU]PSqu5y GQnKsKu'SnLuVzƦ^_=}i =Ȅ'Q?Tm6Fkӕ~$˃0eE)0TV!QӦ`VHB/ܜ1);O<e]J=ܲY3z >,Ǿ^*aTKNTtߦ{oiZga ?#'r\|]#О;Դc0B͝k ρ qq}JNIn$\^eZ.Ťi[̭*b'-*`l&6}s-#q,V7."pcZ٦aL("U4שf.ݯ(sQn;FxU3Ԑ.aUPw%Qn\m'HP MNc c'QE %O'`8<wQ*YۘVS73uqHPllT^ĝ%'<쭤46݂@=26tQTfUJ^+%_pv@3=ֆ S4σS,a4s&?1㜋">MY{`!u 8t7 1AVU{w6;\1YD)r-Z4otq9adᱪILBazi,c:-{۩EF ޔPR:zzȻ[F޸<|@?d,fS=dczI2>Xl1Jju(V'胗"n|I>y%b*S.^.gq44"U[yZ=m}XBXdm3ד{SOd&3>1JOqO٧g/>66(E?'DL8g<3gDSI$Lk)iZ5GT Z .tC 멳@~Ǝ a70/?2H%ɖL4nb䠜>oaੀ]8$w؝%uć{O[f )gwMx)]Vi=Z~w;(Zt1٠046ѵZhŏ^'CI%e.G)7ӡ&":k#b+3tBDMJ:9䅣+iO5Y &^.b0Z&)J7LTF64n%^Y v~B^FS S")Om }@!'3/\j5+Mvqݎ)ĪeYt`2;U%6) ͈TԘMZQu5ftΈtM6-gp%)8$]."PtvR /*I7S$2 b1 j(뽌9̝Ӕ"x"n Mزj].п2 u.,ʶei{, (fzKюN{^gZxX2a"f֭oReP5%ɶ79V7em˺Q &oӍo\5RR(Uf\887:H8,eY`B3_yyB(fP]UeSb?ŽC7;at$Z[tEG4 ]7ZkJfbr#IWSM }ʟůxZ~Ez\ .։SoW`v_Oyw|J}}d\ŞЋ,u`xf'$dq;w9"4e67]CU?o4%%8}>f M}ί񛉴h ԫe2;~UkvW()dL2S:(e31sL%&̢E)tg;B)<ӈbVVu]g0Ny2s\!.3&1TJ^](vonQzArY0VkT`ևg)6)^Q.c}7σ]6uu >Ą#tP '}8$yPX&!BO5gr\뮷|w|z 7?pܹ~۸;=o{==99{r{o?2?iz&d%xթHBb$XA"ăS :< 19Gi~úCDAU\Olgl_12XCSF;)rd:7}b}CN9R: lMV1HV0fgN|R*;ʚ 讑*YX2G=GA(TS4ot~pZ  wݕ>/GotW}Wr7?hE"ߥnᩪRA(BP&22C*k3 鲵eY(e!b4fP; UH**MR1г.$H ֪X:A$T .w%-MKv}J j5kZSF?kڒf p_W[ݑ6)"G^{7$ V 542Q,fÇUNjXWyD/ZyƦ.X? exDZP:UB#ޟAN<԰x+ ~-TQjԮU9KǜyR(e]ڪ0E*gd{7??ůx7|W|%w8gؽ`A8Gu#7}:Է]UqpU%"i,\Q s0CŤݏQ8* qZ{cF~Y}HlJI9^u 9{U[Jk~%Bn?m"u[YjIlV1C:f^e ~ZWe]}0l54 qAQ$ 9pHͫiSÛ nQX ~( Fk<{h=B~7W㋾/:}uƨt恳_/rr 75UO{Ѝ2s&B7QiсLti12vkڼc7[ vdj:ufSu_Ac924|N`u韏j5 o8S|íw񴏗|ޭ‹~o{O#LwA> k7Y*x - .7`9Rrk A=8d=J);B^>j| 5U!=Vfba0`mQPvXh/ϓf-y4TlltcskE?6-yaIj^Nܓ]_\zo X]uƷ g)AGYx^AfXB44!i9#E.9g- S7rOYZ.FFֆ>hN|,ݦ {?Bo;A N~5i^:d.3v1O1l6Dtp&ӛsj^X+"qcJݔ:c>(7ܵS` IFmN#W hz^sE0l]X)#w7ѳZ}|ogy8 Ï_~L/1;>~p5K˟|y/V( ].iPtKU+r?zKjt(]ӻ-[m۔-Mw[@T9 {&d4ܞoCP|oVY[NȭI*.jZHD4r}-Q  }vIP)V3Sb*`?<ܻ*\ksbJԘFdԴA!*bSbvQcR@P,޳?묽}[ϭ9z׻)Dig917U*َ̋&WtI ¶l}?.ŒWn}2 Ѕkۚb3%gU@/Brj*Ж ۛxːmIR×׾ 'iO|"5daL~D^TXWOpc~fUMwl4 mm!|l?f֡qIЋO+rI#yZB ŬF \$YE$t/9v.DlCDj Bn Xq':`Þ$Lykis;P1/$=o!MbOR& "Ui"g<AmkYaz%\(ˏDBXm0Kyе\f~+_|?7SLrK[#:>uOw~󓶚:RM@C<ܱVkv֚z)$֗x5Pe:s=vQS#_5MDTOr%5RZEk>_ljqK,X3 VVfQفd'ߚQa_+gaeјz1%l8yʭs&;K)3P<ص&WY7L :7!si7Dc_;?|GK|ï}|ʽ'O 7:ݛ?xnFG.t0D0"W\I݊`1Z0) $%B5mԗ٠traDd TW?.KZG'E|JmpE`0Ozf69|{->yUH٠ ѿv_ wm73&5m۩MWkVBv+ Ӱ 7b2t]w{$e//KU~ms=_~/>1p-k^y}qcnJ1hWCYR(i.XoZt975)-Rl|$V”R]>T QįseI0,V#R1Cp ·=z 7\Iږhl˖a^ 9" ǗP3~Wm%ϙ!J\>o S9_i)O9v0YV81aV8W"+B3 @0!NDdOai!-;W^y埾~؟coo7~~ X;RpK.XB(,fH2#!S=xYy-)n]S峔,Kw PraPJVFle␝^/4>jMFUX1R#BT_BjuY "0Dd"Vf\SƖ&M 8>1žڨzز\sZG߾G@BI65y&PJ0Ф"Ѥdb:ֱ0D{j>eёR C̯?Ek6 pFh`'S!rYCI"e~7ψd|cp.{mSL4cL/RX?IUJ hQQLZ=%؋?fY_zA+CdJ7`ً~–*:nň#@@ |ѷ,nf5^tױ}:;5^zP-.ڑW[!lՠJo]2I4/j}-%"2 0X$ֱ}|0B%'{_5?n݁*6.XkfX84q\m$HE.D% Pw^*|zw)Rx 0mЙX ~A_v36T#:* ^Ç% LP*cjy:ֱK&*Pa BzUYxUh\kJAS `˜XkuH ,\hL&[vLɞj9#X>LڠdRD:D0 Jt8ьZJz4X+,PX3l헖7 ϠQM{-IjИRT‘z!<ocā@Ιdqk\A >Y+koP)i4JgAr[[[̌™(, =D#%`)dGۺc^p121&vVI4Q[k2Bl#۠W4"Rj5`Y58 N?P;ŝ񂥐kN)Yi*S>?>=M^kоƽdA3ooV! ++t]ֱUe aN2Z6v d?30$*q\}*03X>]e4br-{٬5M F+CΪ>:֜#}tmflB8YZڷBZ=mX}GK<3}6"τ-̀j԰(ΰ;l4({ WdfXtGj܌W:ֱâK\ V= A~0PL[ZZ@ۂTŭH!C>Ia]B;hHo:kqML͏9Hܣ_Cl(#5(-!FNБxxV u`Vɞ~ptZFeψC 1BQV3rbABWw*^Y~hh IDAT俷+"=}z]ֱ6:{YmpU4J8jgCPw{Ҧek/ *QP3ѫ|ő*1Ur@QxzhSU&iֻ E[W9#E_ Iзvn\SIU`b@K]:܊% .~AFdg5+?[`\ָ}d9+bgjotIZ ck~ MepW :ֱ3gD29Tʑ' Hתj|o" jBDMQQ"`$"%.g*D"*UIE-ܖf{J +x`ҋ3nz'v>UR5Gr^ 1w5"jᙱ*-U@n]衳1Ib*!8UN- 0# #4M%m*W +HX"H<*^XxP4QM#Qw2(wob- ί/2"aPm؟|]BMe1I:-Ydr6aR'9r/l;ucE dyslCє^E ?["@7\ /ib;u5p K?=/-2!mfS~4#n_m֥LIsV h\!w.,Û#XwNwapá>!YF 7a탔 gnKu9&_k)$4vC5N ;8zpyW5]Aڋ9G^lJf CwBRµ[~zRՒ p ь ̩MЩS"xm̌V'򪠚NcSG7D VkmNVQ^n?xG8Ac%0c?Gm> .TXqy˂c~yosPfOZquABT-JA'?wnmAu(Tp vr2CR|\wBq+;}fX0OXM!{66װ)S]Tk YCOaY!Er{:̭UNy&X:hȥ K." e%^2 '$jQՊHjW(i0>}@$) 0g(\ J(,9Ԇd\ ]}eSTmBB=Kٗp%P$9%K$-|`DPu )eC)}y RuD )(#itECVݖ#o VY94LTr)hs&Z]ΙHEPs_}2/-ReE!rS٨-ءғ322!LegϳpBN߻u]v˭87_Ngdnl!myy롏_u sN>_5WqE#$`I[5Ci2UwCܤod:.!ݠ[9xv~zwiUWo_}Wly׉3g҅ϝǎ^ǃv ubJx7$ 8B:s0ҖYBL %*3B1K0Y"st룱%&o S mkݰ}sqSx%~Q x#wqDaiensJO9Ybp(A!6+e6|xmfRm{NO?T#֛H*96tEaR ^+ Cz?ϼzQ. 206$MϜ:pp+˄Μý]K:Aw iTLߧ,TFDL}OmE@Ȭ˹u~UC <45}Ѵ ÊϳT(ѭE B:8 ]͊kz+V!^5aY븡+ˋZw{(Y̪B=E& ؙ̰]7qچ߬wYa!4qqIL"I"6ɺU&vnڧK*8{,DtaDLSp±|[nSw~g>w= ŭsW_oF?Çl<_ /?}6eOq: ؤ#] wEhI3'D/ύr*}>Ŝ,)Q׹=/UB=KDHݔVSo5ntR9eMdi2%bPك=&틯}YٜUc(N4F -.<$r 1ۋ{U+neUhYi1vЉHt E V Q<|f, /q /Q[f=X=A'Ui)vtn?焨bŤ"H~{8X>=EHD8֙Hd3ȷ^h{;]4s~?؟q7h ¨%xq$9)= j|h\pA 1h]OdZ#PTaQ"W] ̩6R MSJa0,F[t=fڲ5C`hݗ1 :5+I'rHx+,4j`h]l%~6f}w t^(; aʜILbA/D.no{N>=iKwۇy_+nקNY箹^GoW7|w?utǏ'[_ޓ_c/-v)\uOmmWڟѣǏ ؏-?vۅKz 7D?2|l44OM 9"8: (NKBdZ-jQiA#HKQl!#Gxv#(eNuV uY?Mm͢1~'}չEhBB /Q 'wO;Nvɛnfx$}mYϸ}~9w|<{5}ֵ<ȑ#wܹswy{^8~|^zy~<]rS}GS?~ Ν;cǎ/|{^қn|ˡÇ??;O?7m=볈 Hf H,T$$! BD  $Xcˏ}PgR6RzQY䚅5 e"hin@wCA+)lW[6e0ޢL WՑެ#L)QևN:࣋Wvhp!k)1"nMTɲ acὊ#P0sA`@Y$0Q=8J$f@L9 /ʔT_V@«b"L@$X?SO!"0&@~e]PW`= -;~23(vE e9Kҡ%- BME`xGcyyw;OsJ麫|C|g\5QII93as[G.ͩ'}s˞:tixӛoO~w�squY09=yuׅ8q\qǝwC?^s͏؏ggI.\xoyӞwO~;=yr l|#@ Y_enloŶ^gI}H4O}[g٧=uȇRoj뛲~,ιQajp4Ԭ\<9rdz“g^p_cҧ{5KĽ/N'O__3?/~K^{?}ҥ n >}So}?yӞ[__~W_v_؞K`2/r$hD2gn[`EvYQ.PJjG^'k \&]4TgMotsaVD^5ZMHPΉ(臄kvG&jR B(üyg(zW!YVOHO4~3U  7X* P>U2 ~f2^$\^x/Z2T(wd %GۇO>Wn蓼@0m7iOQ ǽ6Vx!`ў& Bժ{.(f4d >)hg-e/J‚ k fT,k Q[AZz8ϳ#`Kej D" M jP۾_%PgwQ#>yQJ 7ny)6mmm,Og|ƧKWk_o?~o _s]w ǯ8%zw|۷|'~R?o{o#//ү<я:uM_~9{6|rxI:^70zm#Ngu 7ZA9KF۶{Jk`TxfpN8\k]o D{ZԹT<ó{#"}pLL cΥ6D<4DA$-̺RNJ2c "M7`bDQ+jJbmO`}]"[?wi`Js4x; xK yOJk<ϡGVrK+6f4E TGs 4 u [ IDAT=A8 ƌexO2IypA@w%0w.RBy^O\6Y!ُD^ĠvX.8}KaޛMmoן{O>lO;)l'H9Ýw;S>+_ƙu P嬸ߛƂ]o ڒc4^.DY>"k 0$ۊ)& #MAoAח?_? w&J9vT v,%zW>rŋD@`cG?ozi>iҦ6l39g#5w$d9Ȓ9N˂USPzE~ocnM_=_~$=6B}qaYDLH\]UFQ߭zC?gB 0veF@KT֤U3s &:_rny[r6OtEs\"Iz}<އf T)sƶ|` sB" y֎=rpgǏ]q֔|y҅ {]ڰ&BIr.E|fd-(Z| l7&HBz S lrbD{ d Ut9gQ*Dʙ$2\w DIgTopVKvai"us}2ޢ /UK"R]aRw"=e۬`hd"bJ*5wj5K.}w yJE\Rʬz&i Crcυ*rU p2T[ 6/zUߤϐS"er GCmE. z>]e_OC>S~N 8qy޺֮c.O 6|5^(!,F̶P<҃{; jeKG.:Aġbg-![)Z1t 0сP#T~閁F)\O\hv נϭ{Tքˋ\;@FDZT(KiZ!ɐYBlWʿҡMyzm 9R z-ݠDd?GQhmd >ѠzX>\g V˷Z/k)E>L'&y<ː@$(vA1\pu3^BXtc'YVۤѥzSo3e۝"PniPeO|pmPfeo6+axӂglWc scb'^6bm31 /ֈu1_F*AD- L=bE_%Zc@5ZOÜh Fbu2"_23Nzu;{Yt9r#:ֱOGK(!8Z/`ȨTN@@RRd,nx HK?/[GU:\bRfO[h"W@q!S#lqzz:yp ~{aԉTȸn\I/*zuz?CKhpGRZ"A&lo JS$):O;5QtS+kuvl9S5*G$3hWa*ʈ!G=*âjwu9Ӯٺ sN)-% mNjg.3>cnSTͥ@}5-B }3jtMQ")u  =/R,jv/褡ACh5Ei>Vt DwK_P: mQmڲ1s[yT 7kQe+3ϖ;^/,nAArt}e&Xr>x7=-h!J^}DZ;nҷW.Zq$Z═F?>[|墹YŤ;BZrs; RvBQnm+m{$bZ5qj#9ר53س4os!"@{I4#{mBu@K= + րڸWS Ղ^?GU B̀8%$gYVuױ /{~B ])E2FI EDeDre4 |Z#x$\$[%޶#H*d-{0,z<`-4+sTM!ǰWR/ ƺ 64{pO GD<ÈeS"dcAӬi Bpݟ,dr_0PpW _kQwS 4RCͅ&fΞh6wrZÒJa :l\K|NhMAYנuLwa`=iƬ_iD["kYwدݞwDSVǸl] QnV{!-PXpX4rvk:jSu o eA)؎FF-^d(ΌOdE_ub 7HD p|h}Ħ/E3@i'I}]ֱ9%fNzmaS zR3xTU:) ݜz v/6drCEz 0p cQrJ&3EIO`BWOB&ZsN re^C4|M.. ,P=vw}+NOeK:+;s#k{bj/C_:ֱoGӎIKn؇.~u[%A_ur2AN!4ޒKJ5 姘8 &%5hqQ JNKU>l&$ֻVsJ6).`E|4*,Ԯ=Ro;/BDy28M  g.u3%s+.Xwv o#F;hKh *!!4}㙑R[aZjwxmZ:sЅȥXjO~2sZdf)9h Q Q!h=ʚ@VQ[\_&l\7 c_zXφչR"B]aWvY%nHi:dQ 6Kq>圵5rNSJR2 K(ɉ 'CW/m?UvOS7 EGR T%{e(JCA27!bucL]3 Ҡol3!Yxu d '!b/ym_:ֱoG+3Ĕ@ bgm,([n]s)r [f^m~c=󫟢^߱\i!*zyC^&sYva>-mXէ)n-tz-c>efL$$_T=gT4u ~\[e TE@DMz/O#|+lu VB9's^}l9'UrwKqzנuhkk0;z`cVj]ɓEgH\oP6#-Bg~]F;朞BKR "^ժ}ΐ'LHBq4t+rr V8l4MaJBIm`Z5R *-/< 8F_:ֱ%Zd߭@ $…! PKAm>eX"__ QA+i~l:L1vMcS/kӯ96i Wk&UhjRjܮah8iDYHf"%Гzf_CfgsvmWb ڝdx;6\+ⴐڹPXx]ֱ~YKm $">FZ!ke#KSKgͷN DPYH\ʞ#rlNi," ARh&GP٭۫Z"d3]g[ bJAi0sFcsеK3*,88{7xwA1;.Rf:A/vBkY/ @zOcUW:Bo)Ж_z/&{)7G(,_OGŀd)|IB+:ֱ};"SfJAmKc0БLù_6" d(rpM@٧S5Fӥ!l_ M/TsEFHKM4%D`SG,ɆWaԪm $}+ c8MFlE/ P)j6"%2Gg_C|*cˠZȴ)Į5ˏuNxybA׳TL:X@B jūz4 g$(0 0҃(‚ :{ތGPF+*C+am".^3]#! +Bopr髓=8= =!f@Gb7˂l9zNJ(zP)° x'drU_&`G%D$Xl5\ߛui[:3җZORZs}\V*D1VC+ 99 uƞS|ʿVЍ3r] uHCV_4s;" UrM0:,U2U>ޞ\EAsV=Nw]b'lKm<37@= y!,qfsbnRvJs +qv' #&.~qBo:ֱw˚Eȹ8aY=IPX5ODX%I5J $"<+E ؔ*J<*VXF\U1T󛳅SL43V3Ǫ"Ӣد,_ nVP%%mD& ú!Qp[%tm ~{ú?l!,C꯳]M?s.=ǕmN *h% "!-m<߹Zm N|-Ⱥpێ#ؽM k NP`+صeB;0LY: ~7ucfEGD qU+drW'4(o}S!Z_yG%} Z|`ksù\ bk)4M}ąʎ@ q+6Z Mo,y:q >+TSƌi۶c G^PQz7?CB#C%|?A~AW}m7<ϳ5g'@HSGo[iX~Q!C;JD*8&,ȴ IuqY".-lRg " )D7i,:U *&dPHnP7p{jj/ª-Cŀs 9ZRQr`0v)2(&zg8 yMߪh^ /mzjm ,BB^}cyXg]"gq#d.k)"R |Ѵ.ݺbxrj].{1יZ0FXp=?y !P%&\n5 ^Z|@e}yVWK۷xmŢbLuZmGӷ b vD`m(|s0 -(߰4>Ƀ9$yqPb@'"ӲAcc}ߺcvC⟶*ԄԺD u)ݙmpW/ߖYeغf$F\cm8\\*1v T˃E헌/FN]{J!Ne^u Z AҦ^p2*>|ZŰ̵j @A.,BucCɓʿٕ;첺E}E͓ktt>BP熑+F)=3"nLQ}/#3rTX~-UjNPP2Nk UeϦټʜU%iX *`(b-Rbb2Vt^`s2{'|; IDAT|U) ZfGkbElt5 RE@^寮45-K^BҲzճ^>2nv>}Œ+DFQ_{4%z&kuᅖ8>-ӀnoMqfyj'9gt"RrFe*H~Z|K- z5 sO Q9/*88l5ll{nLXuth GTsm;p;"* ZQ-SmA9wE0$B)QKkT;*f9mQۡ^Фt㡅eIj&Av?@5cth]!#d{Ģ…T,zQw.a9L.[MIzP-[W%{` 8j^`F`a[c-TTLŬ҄[^--@3=L@_NzLw.+9q#. j|.h{!1c@(%{>u.'$ ڗ1Yc+HEC-@ ilO }hj XXz=4Wc؇CWnb%puqx@,D" AWc MP?x9+CT)|8 +.:9C_[dZ'ԁZOʥzݳZ 3WCfF#^ɤ@̙mRnA)*HXd|ΡO:b/6yv9ta;(%7)I~|_oDDj/X'V;J^3PS!W[-uws&,9Yւk*V_`0`ZӰ~KgK,hP Oo5ČHEZw+t|MAsЪ6{% v%?g,@P",VE I K՝v!K a&Sp~y/lRIIJ",k 1xؿ` " P-Hgn[JH PkYU X8 N^,KkME\u_ȹx 9ׂY;j,.aR^ /ɦTYET(JևJzyU\UmtZMAj=b>H܄ʞ t;'󬆌 ,DJWs)  '"mǪۯB^ˏ%lc wI BSLETE6;s_Dt{"֣e>Bl7APIOn_u][:7b},C=Gؤ[CNEAV:x2l:r[np0$G遵j/\N`X R>1dS|x.r޷ZpYw&K|]3#NTYjM(`Aӛn UOVru_E|#Y_ QJ3gdmn&TjhXǾ.m"-0 %3 D_~ IdK^d4zmeլyt;>rQTϮ @̣R~$6<)-S`rԶ@Sn)|>]*1ozwHp|71E#+|1E/ay,DIS{`XZX4))yZvz(u(i2A`u+N&k(;?+9T_@vf&r$ݴ l0ZAKL> !ós OȲ m<E^X(фρ?{ID 0CN) VMȍ4Syπ:гC~͒wQpO< "El'AB>mc~g^̻YUT2Ķz\I#/+#D4d )Lp2Moww󀸳sȑM!pAݽp w0<|xkk#CDݽpŋ>L⯓% >S֕_Œvh>}Cc4 bW]{9m.s}JՅ\2Qԗ{)lN9u%}(N![%͚MSQs_$Aˆ_d-+=nZ{DrWuu5|?v9!Rγ&*2 2T Jkҥ C Z&|a Bz$\Ss.@E9ܜ&!}CeO3fQ}Si/?#{Y,P -Ъ>x+sJp@ߥ5FU~{2ujZYrtw_"3~0; 7NN4! "0 dPJ;|Ο=rXX$%;sg潽N>," P!r!͛'\D7 ΧF>H XA ;L#,I/N{KX\uH<>*nwںŬyCAЪaLVwE`\zO;ɉ &kf'9M@ qw5V dP2]%oڭ>nbyK 9f{2za Q78 8ܼwi:v<ϝ{@uēPCE5>AkJ $c%x"= mpH|{3Ȥ 56ІOJ,t }P\.y Xl>6ljK)$M`ݦ<[p@;̀ƈ Fz^z(hl'~VADI)ӞRa`%dSi\lX&e1F&=99vcOG|…uI؟lnA ܆,׷U: ֟u8,PCBu[2{WZ |IpTiy[9%$5ػu1tAObf ?B=3PYlȅnmmHxI);¶2Ypu跡^kal}RjUB˶ޥK{G7S?6ѣ.]]'a}8 lu7tq^oJlt2c yK(q+3 >;QN6q夭t*VZw ɓ,IOOzks!jHa:b ;Age)93s(Xv*9 @ygO RЅ Aχ/b杛0KhYlFnKLDXp؀ `+!OYGG#G=s&u$(ETY͠9e4l=b⍹րU?}dcCA&OpVtq@Y- x^jã&dpq"ҶO|AH!o`lb=g{1p$Ro̽$AF%0E) !8zzj$`xDӀ$>>aM΅9=e_`Ψҩռ lYb)(1$B"EDR8w܁4'J)ٟ%u*S,sӟ?lWJ[PuKΒ 0knCi1%JsgzF{?7H^6hK}ߋf$vHr*\M.R]3/^ǭ!rP9075haL 1Uȹl2w(ey 3~?B>Rlnqp`klcx]  fY얕{hY_{[Vksޙ+ &*8hRPFV@B j,SA1(| J(TqLkFj^uW^;sa]u|{Z@2Hm9 |>-"G{VZa|JV(`7x@lͺu<@Trk o0Q+OnsZ⮙n.;z@,3 Gy!]@;N e3Hv* Qe\I@SbUMOϵM[#ua܈HFaO7WoB53=<.`~=$APdN'"CUp;_/ekAs/.3ak.thfzk *py:z1̽^[I%~E:Pi䟵ҸK'ˇ=+njn[V;ցF#!{=FR`з{(@Csu_31;1R?S=DUp( ]߶JlnQ2A:0C} 7SEz}uXhxN) ݏ؟gmrw6cPdۀDC9/z1c#]V[ƳZ--<0ZϒMZuyYx;>[-;6h}ex2!͡> {^bq4 `erAv9>~v=)kt ނ"W<2aem-04(HnǾUO:u~WG~w|=dإƯ}g}Ƨ_W:ujwj>LJ /yч &E-_ RΓlMqX4KP 4PseyoT蛕Mz]C[7]j2i@-Z{hwOKY^5.o仕ÝJȲ;뱍 DME3sY\].Cm <8%ddmIdWjiv9O4!303^BЎڙKnx_gk%S!C>//z5=yϧ|ᓾͨN \K~~/WG?8Kp!}jSpDzà,mw'OÖGΜ@ɅGo^GPd{#f=^j*BjR $[Cks$SN1gG;€Dkw["9ӷ7<ԷK)f~+7 'iqkZWx=L u۴U![yxqs?s>[?ٳدo\Rxx8CAǧ?Q-{ғ'Gk?anwؕt+&DlBixse,7CXMЦ *M<>wOIlR J\?\c\ UR-a^}:oYVk~W١n(a e"d}AtZ~/WS7=OG~G>O[oZR}ӟo}w]K)/\8A{pu/q|ѫ_sWo˾䋿Y_'o{7r!xaEVߐ JuR%Wܟ Zqě]&JlӔj$Qi G]KC94zǫ. /;c>6kg>+}^^A! .I{:dfYY̞2|uCXAw/g_"{F\g?O{3_|".4c4 +oy˫~YR??+q'kj@8 IDAT$!Q)& z&/aPyE*H$@Cwr*rԢdp14EKgcx Pj~sBmFX!6GssɴP&D B'B!`{t/m]m]wt\/{w`5:k׼u6o>_x=/V@hXޣ23BTh*I)y}ͳ j_O"Pi}S6h&Ho%xB.6@uiϮx/ЍYuથtd5&=Ajxe*(@x#r%73*gǫ'yuƛn|7ӋwR{g|Ͽ˯]wԇ |o/w:Y2t$|K^䪂^訔BL &WJDj{F>+@}6|H\d&MQBͥ2:-flQ7:OHD.L$Mrm &,nP!sͿ] 1QGmmA@ءê xd^via3<IE! qeDPU81Nw@o|7Ӌ>c?5f"'.7dGv[6,oX1Æ6ɡ~M]S~3^s B'W_&hn-|ܘQ5r5!2H:XA^֞JDHſ7YV85ؙrޮAdm`Qy‡A;ec5 cۻi3 `7bs)-2C"ymnMN؂ǖ0 D~4pej*nJ+8K]^wxUB|3G}<>~Ӟ^s͕W\wz/)x=71x~ۆn/O   c^]))[z1D֞0\+K]JX:/ +VLD (F{X$+Ȱ^j:"5H$B}T F\Q]Җ0h4?^CkΝ;KoՓ5XTQ$fݳ]{cVUE};9ZAbFOsʣq̕-1żGYBH Q: uQBᾈmQª5.Б=}z)HX9R˙ KV:XPpaBd--̯|㯿'_*n_?~տKCy?qAx#/}ٷ~7<+׽ 򠫯~|3{ޓ5"B$T`>AQ8;CE_0s_9䠇Чk\ 9˶BAI7, A]t\[̘#H*WvICo IEaX 'nK_E,9ڐ:=+פiRDA=9<۾ko4 IP: 6aweyH*bkIS>15`70wfCذl;beƒ\)~es;W|ٗ> oyw|t{Koyw}%5棣gΜAݓ9;_g<>5ٳ?/7ʘM<Ϯg 4=J5 a/$ZMFj#.VZ8/}8S!SE**Nh="Wi;Aw G Қ4 | ?# x9ifZ"?zA*\F@ B1z )'J}GϒX!.}dN`Rkk~ti9js&q?yԧ|?_]WkZ# '=ʟA?}7_=/AhJBZzԪ \F 9CjKˢ1hҰ jRTU!?|Lu ,&wVZ Do"bppkWW`egnGS `#] dw)n69em!`TY {Lo(Ǒ_Byf@idȸBy#?ztppˎ?q_/o[/:W?O'>?7-r |WA o_~UWArUZ]Mw-=K|og[Ox, `mczkQt7[aD}9W:5=u9K]CC5/ >lUU!ag .!&M!Qf}3zE0k8a`=|A1^DJhV/-hc-hf}dRCÁs^R!P ' y]8 yWFJ1Ǒ݇rn:,9÷J4@äʈ^~o7,>  4qU28qpx ]mAk'zt.F@E|,^i@/P!8 /a)Nc#)\1EpQ[ʡh=\JW$"=AN.xVT N 0$f)Y]QJ+pdYI-RyvptĢ': ٭avu<`#;E* .D5=pm y]"p]"]o ¥x, \hat^fMH935'kΌ\S} yt_i@ޡAF"p #hr0%J r22R S3?bx8sϟ&A:jΟ?СmAQXH0MT&,N V97C+LS㣩7^ld YJQ &@I&{*[WW8J3(YHw!0]R98{DCe2Of;tD. RNMV}*.[MAß({kpW!IۿU姖ܬ0xwɷvYH>M [LUyjzo6*@Ro)Esn#3jLGPа0J1Hm+h(DCٵV 0 P,<$|X%(t8E몔B4 bp{-Td 2*-NZCMcɻ TU.};o@Dn喃=n B.q]3BEMmJJQ fEޓ'dԻK>:]i zAg!C!o[J־֋3KĂ\)]G-e-"tFP:jK>hT`Y_4:rN .qQ)Sa?;JVΊZgX5b0pI;*[jo3$Ϝ)t-|E[n)ӴC lpLW \AD&DĪzl쪛ʰ'T/ MJZ1oE9?-;f҅zcr]r4(ㇰC\jגAAd#[yAl\+3.k N,o0 K(s1$Z/{!O GAX޴AS5ev0hMgF$jOc'"R2 g 3_~pԭ7<zuoi\~9c7QI {P69 mzi![%l͵>I:#4CH>תj_L aO_yn|ݾmĮ0G@0헥"gѐ}@HR_=X" ٽv6[ ([N '-溑q.+ߢ^JXG0t#~s\ve[o=}et_8w3N9A{89^]LXWͥGgUful|]L}lq9{^hS7N퇦U\o50Ċj Q% ]r#VLܒ \ImַZ $kah䱆>2 ̅h1 ʶ+$@!Y&+:>8^QJ+u<p/<."/\pႶ1 6;R`>K xeВW F7IޖN L!&* 5Y_$MLs1wrQ GȂ ֒[( {CkB_.or,oZAP(٭ݝ!B:79z#t i>` )Kf\mⰋ4$:G/^xR|AyZk=xzpx? l~k@"E7R/jSǶ"#bZ].#BqX+ J)J,%%lRKCjᖤ$ CW2l)1CSy5찭i-cR!j ̸w#h)8P?CPv(gTIpзF*BV}}|ttB>sfp 3 }\ ]5}X\alTF2<EՌ- Wuڲ/~KZ:zխ%/ӽ_|}178mE |Wcr<@ZRZ页CNU>/HRmZB_^6j L~ lvlh5F"8PSS$D,(̠Kes kwrogke3TZH10% #dCm=Ua2UV\\bYv⍕2-t3s***ۙ0"؇urt j>8>2Zշ"dP "BnëZҦ,تQwH k|͠Mti ÔD`DB)l@\V؎==86#E#!h!҃# Yu_%sr= "R#<:|Z^r~s >!, ŽPC߾23%4t fBrfs)/BmCJCc͡#IS]x]nPkz Xp!0mV^ގC; lk[5X7* Uk]kEUްKa )k^vx_8Qhj&&-T뵋nS#' A2Q-YvՄ[y 8K0Rv޽cBoˈdRd $DCg-Z6Eh2mCL~,}[h;c;.. ziY.H(8MEdn(,K)¨B"Z 4M^J6+ARdښXkL<715axG125%iIDAT A8;֊U+8aC8>{哽A).9xRn+ЗߎEʕ`d>b +P !J}߀T۱{{(^7kzUf"Kf]9\M܉yqocy&*HzZW|^{$ЀWV^$ 85 $r󺖳ԷQ`l962I̋&Bx5!s&lӟer#%Bd)C`B4:Gj( Ag΢iy8!|K Arfe6sFftc;>:QoZ+p:(L yKD%H aP zh& } w;×;K/e\X?k: KMޕ:jJw-FP)ٷp=͗Bo5V>seaаއ埆D`/nL3zx8 ;[faZ dP<vlva"Lb:!Tstk}Fmٰ; |佈hfa¢'L KWw=킏g[pao!B~hy#/#Aʆ2>"[$$@"AB~}j;c_DDF! >0>EPB0-,HfX;$>{*1(QH9$RL/͙[ f;Ȩh.GG\ɍ3TNobFR]tz']NlW; $rQoTn睥e^Lq:!m;c__exX$P0&) ‚fե[ 47!VbedҐ52U}aD1W14n# )G>h=x<צȟ1 e`5B ;uIHrmiWO^:%HF/4sVP2^v9ˇF!cثudK| ! Q'?\3MC%LsWz+Fa5-JWOǡ4ɑ  n>hlvl>F\gKP'|j_\KAfNŚXDYRtzi\KK.;Ka9YGg/lD9 !+4'm{leS%2yOYK~;+ދ$Wz!9q~;I_OF۱۱fOJ.y6amvMPpj@BBx0ĒBN1VJv/P؜asXaČPA,P he/e]vo݂rlfAf1wh-=P0P*q4xP| ?c,~2C؎ؓCaCz||g`,t}=fI|]CHMX /Ɔk/ Oͼ/`"贗;iU^bưU67Sʨo{s|<^wUAѱ@܂vlǞLdGФ !kîz{,.T̈j>Ԭ eZk6ZvD|"sԖiB:2W_Eiҿ*j9pK {X>ߥ*%n*vCeۏgXh CWzO%޴r03 `3Bi PYgìh%Ȩ ;\Hf=GԾȠ'Sgen: O"KNdAD۱{{tiPsᱠ{)d zlRŴhLA.2dü_OgVg$*Nn:> Z aC =iX&G 1ƃw$.}6nD!p81Q!Y?$;+7+cdmDА?1E8vI`{PW'}Hx`R`Td;c;>䇮Y -T"n!pc[ݫȆRӺ$ ~A}.;-s۱s\<yKΉs zz@X фNFbO~zug`=rh\oR{ ;&lzmv4806/:4pDyV }W~ۭ[ݎؓzW?;h5}(5*lM_&Q5]D$ B %i_CX- Ar6ɇ:2ϺY* .&/X $2n;,\Cbx(Woi-34/ z6 !}^>ec< ~í.DDŽ'V'@oF m/wݾ۱wAD}ik W]u<0,{ ADg:3"t$S[lo֊9Jaqrl%A/ԚQ%Mఔ$ɈBFtU߶H=!~.u yp杧!yW*ޭh)*7l/K;P ={&zJ^9Sцm! xPD|Ĉ zןec_4c+4;gDk -AD1/5$#M+EC(ʫwSYB $m{ |~bXһdZiH]m:UCjJF!mssùٽز D`8񌨀q(6pf4WQ`*lzxxptTyYនZAHP``2@`Zז|//rO]L Z{T-y efjaU+}~M Nj/XE@/r[^mnnss{87T5jɮm[a$7QC ]_ uqnɅiƙ|E IHiXE JyPJa w;@a]E\jZYޓH} mB4C,p V̗`jgJ([eMvYJí砒t0W@@'IIP +l駆 emn8-@W\V&/[320WuFnSk{kDED֩h7:@#ҫd"2iJ@ߐp (pgWK)keec9:ڵ">oc3oߦOYp3Բ7: z oN`u%2lssϹ9djW75[@HPbhf]W.42e. NZ^]C@@tgҼ2,t #V>{/;!z\&YycrWqYvy!#f WPFgh0nڇys^c!so#y bWP\67rnvoT bHhņHr@!bĸn;,L 7 4!Vahdf _@Rë#buR,i ]I6^QLgӯB,dȎw~_ 34Ħط.CJBD8Li uIUXSl"l}yR1x4#Yu@Bf,T*h2&4P LJ 9T  @| 9o[b67B,X:w0DKbGi_ g3J?=Ųk,֙ B%0Xh{!ZNvE9O&(=^kt(::kW_AZ ^m WiXq2vě&BEy@lD+$b}$7i,W`]q0_bTU hߜkCCQ^[Xn!U_i% I ?Ώ{o]GqḪ mr##Gٛ67OsӀH8 L޿Xvs]k7۾z0*b{1`iiR*}ZԙXz%&LqGH`˰.Q= }ؒ:uB"OC|JFh-w{ԍgjGx(.67Os7mb}#FXxe& Оn"^a+m1dXTk{FkJHH@ZH.['>FCZVSUpieldxLbNMUDŅ2׵ꠈFЪ RhD*IzKBBYY>5z&glB$x-0Q獸=ua@} S,X (Mb1e(RB%yaf0IENDB`gnome-shell-extensions-mediaplayer-3.5/meson.build000066400000000000000000000006131317622025500224270ustar00rootroot00000000000000project('gnome-shell-extensions-mediaplayer', meson_version: '>= 0.40.0' ) UUID = 'mediaplayer@patapon.info' EXTENSION_DIR = join_paths(get_option('prefix'), get_option('datadir'), 'gnome-shell/extensions/', UUID) LOCALE_DIR = join_paths(EXTENSION_DIR, 'locale') SCHEMA_DIR = join_paths(EXTENSION_DIR, 'schemas') subdir('po') subdir('src') meson.add_install_script('meson_post_install.py') gnome-shell-extensions-mediaplayer-3.5/meson_post_install.py000066400000000000000000000007631317622025500245610ustar00rootroot00000000000000#!/usr/bin/env python3 from os import environ, path from subprocess import call HOME = path.join(path.expanduser('~'), '.local/') PREFIX = environ.get('MESON_INSTALL_PREFIX', HOME) DATA_DIR = path.join(PREFIX, 'share') DEST_DIR = environ.get('DESTDIR', '') EXTENSION_DIR = path.join(DATA_DIR, "gnome-shell/extensions/mediaplayer@patapon.info") if not DEST_DIR: print("Installing new Schemas") call(['glib-compile-schemas', path.join(EXTENSION_DIR, 'schemas/')]) gnome-shell-extensions-mediaplayer-3.5/po/000077500000000000000000000000001317622025500207035ustar00rootroot00000000000000gnome-shell-extensions-mediaplayer-3.5/po/LINGUAS000066400000000000000000000001021317622025500217210ustar00rootroot00000000000000de es fr gl he it lt nb_NO nl pl pt_BR ro ru sk sr tr zh_CN zh_TW gnome-shell-extensions-mediaplayer-3.5/po/POTFILES.in000066400000000000000000000001361317622025500224600ustar00rootroot00000000000000src/ui.js src/settings.js src/prefs.js src/org.gnome.shell.extensions.mediaplayer.gschema.xml gnome-shell-extensions-mediaplayer-3.5/po/de.po000066400000000000000000000200651317622025500216360ustar00rootroot00000000000000# German Translation for gnome-shell-extension-mediaplayer package. # Copyright (C) 2012 gnome-shell-extension-mediaplayer package. # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Frederik Hahne , 2012. # Agilo Kern , 2012. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-07-14 18:13+0200\n" "PO-Revision-Date: 2017-07-14 18:53+0200\n" "Last-Translator: Martin Wurm , 2012. # Cristian Beroiza Rojas , 2013. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2012-02-25 19:20+0100\n" "Last-Translator: Cristian Beroiza \n" "Language-Team: Español;Castellano , \n" "Language: Spanish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volumen" #: ../src/ui.js:177 msgid "Playlists" msgstr "Listas de reproducción" #: ../src/settings.js:51 msgid "Stopped" msgstr "Detenido" #: ../src/settings.js:52 msgid "Playing" msgstr "Reproduciendo" #: ../src/settings.js:53 msgid "Paused" msgstr "Pausado" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Centro" #: ../src/prefs.js:43 msgid "Right" msgstr "Derecha" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Apariencia" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Íconos simbólicos" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Portada del álbum actual" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Texto de estado" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Artista, %b: Álbum, %t: Título. Compatible con sintaxis de Pango." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Texto de estado" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Permitir el inicio del reproductor multimedia por defecto" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Mostrar el ajuste deslizante de volumen del reproductor multimedia" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Mostrar el ajuste deslizante de posición del reproductor multimedia" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Mostrar las listas de reproducción del reproductor multimedia" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Mostrar el rating de la canción" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Mostrar el rating en una escala de 0 a 5 de la canción en reproducción" #~ msgid "Position in the top panel" #~ msgstr "Posición en el panel superior" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Reinicie la shell para que los cambios tengan efecto." #~ msgid "Volume menu integration" #~ msgstr "Integración con el menú de volumen" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "" #~ "Iniciar el reproductor multimedia por defecto haciendo click en el " #~ "indicador" #~ msgid "rating" #~ msgstr "rating" #~ msgid "by" #~ msgstr "por" #~ msgid "from" #~ msgstr "de" #~ msgid "Unknown Artist" #~ msgstr "Artista desconocido" #~ msgid "Unknown Album" #~ msgstr "Álbum desconocido" #~ msgid "Unknown Title" #~ msgstr "Título desconocido" #~ msgid "Album cover size" #~ msgstr "Tamaño de la portada del álbum" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "El tamaño de la portada mostrada en el menú. Por defecto son 80px de " #~ "ancho." #~ msgid "Schema \"%s\" not found." #~ msgstr "Esquema \"%s\" no encontrado." gnome-shell-extensions-mediaplayer-3.5/po/fr.po000066400000000000000000000052171317622025500216570ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2012-11-13 11:05+0100\n" "Last-Translator: Sébastien Maccagnoni-Munch \n" "Language-Team: Jean-Philippe Braun \n" "Language: French\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volume" #: ../src/ui.js:177 msgid "Playlists" msgstr "Listes de lecture" #: ../src/settings.js:51 msgid "Stopped" msgstr "Arrêté" #: ../src/settings.js:52 msgid "Playing" msgstr "Lecture en cours" #: ../src/settings.js:53 msgid "Paused" msgstr "En pause" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "Position de l'indicateur" #: ../src/prefs.js:42 msgid "Center" msgstr "Au centre" #: ../src/prefs.js:43 msgid "Right" msgstr "À droite" #: ../src/prefs.js:44 msgid "System menu" msgstr "Dans le menu système" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "Apparence de l'indicateur" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Icône symbolique" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Pochette de l'album" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "Texte de statut de l'indicateur" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" "{trackArtist}: Artiste, {trackAlbum}: Album, {trackTitle}: Titre. Formattage " "Pango supporté." #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "Taille du texte de statut" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" "La taille maximale que peut prendre le texte de statut avant d'être tronqué. " "300px par défaut." #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Permettre le lancement du lecteur multimédia par défaut" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Afficher le controle du volume du lecteur multimédia" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Afficher la barre de progression du lecteur multimédia" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Afficher les listes de lecture du lecteur multimédia" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Afficher la note de la chanson" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Afficher la note de la chanson en lecture sur une échelle de 0 à 5" gnome-shell-extensions-mediaplayer-3.5/po/gl.po000066400000000000000000000066441317622025500216570ustar00rootroot00000000000000# Galician translations for PACKAGE package. # Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Fran Dieguez , 2011, 2013. msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2013-10-10 12:36+0200\n" "Last-Translator: Fran Dieguez \n" "Language-Team: gnome-l10n-gl@gnome.org\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.7.1\n" "X-Project-Style: gnome\n" #: ../src/ui.js:165 msgid "Volume" msgstr "" #: ../src/ui.js:177 msgid "Playlists" msgstr "" #: ../src/settings.js:51 msgid "Stopped" msgstr "" #: ../src/settings.js:52 msgid "Playing" msgstr "" #: ../src/settings.js:53 msgid "Paused" msgstr "" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Centrado" #: ../src/prefs.js:43 msgid "Right" msgstr "Dereita" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Aparencia" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Icona simbólica" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Carátula do álbum actual" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Texto de estado" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Artista, %b: Álbum, %t: Título. Admítese marcado Pango." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Texto de estado" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Permitir iniciar o reprodutor multimedia predeterminado" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Mostrar o regulador de volume do reprodutor multimedia" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Mostrar o regulador de posición do reprodutor multimedia" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Mostrar as listas de reprodución do reprodutor" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Mostrar cualificacións de cancións" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "" "Mostrar a cualificación da canción en reprodución nunha escala de 0 a 5" #~ msgid "Position in the top panel" #~ msgstr "Posición no panel superior" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Reinicie o shell para aplicar esta configuración." #~ msgid "Volume menu integration" #~ msgstr "Integración do menú de son" #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "" #~ "Executa o reprodutor multimedia predeterminado ao premer no indicador ou " #~ "no menú" #~ msgid "Album cover size" #~ msgstr "Tamaño da carátula do álbum" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "O tamaño da carátula do álbum mostrada no menú. Por omisión é 80px de " #~ "ancho." gnome-shell-extensions-mediaplayer-3.5/po/he.po000066400000000000000000000074731317622025500216520ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # Matanya Moses , 2014. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2014-04-24 00:42+0300\n" "Last-Translator: Matanya Moses \n" "Language-Team: עברית <>\n" "Language: Hebrew\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.6\n" #: ../src/ui.js:165 msgid "Volume" msgstr "עוצמת קול" #: ../src/ui.js:177 msgid "Playlists" msgstr "רשימות השמעה" #: ../src/settings.js:51 msgid "Stopped" msgstr "נעצר" #: ../src/settings.js:52 msgid "Playing" msgstr "מתנגן" #: ../src/settings.js:53 msgid "Paused" msgstr "הושהה" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "מרכז" #: ../src/prefs.js:43 msgid "Right" msgstr "ימין" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "מראה" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "סמליל מייצג" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "עטיפת אלבום נוכחי" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "טקסט המצב" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: אמן, %b: אלבום, %t: כותר. תחביר Pango נתמך." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "טקסט המצב" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "אפשרות להפעיל את נגן ברירת המחדל של המערכת" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "הצגת בורר השמע של נגן המדיה" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "הצגת בורר מיקום השיר של נגן המדיה" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "הצגת רשימות ההשמעה של נגן המדיה" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "הצגת דירוג השיר" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "הצגת דירוג השיר המתנגן בסקלה של 0 עד 5" #~ msgid "Position in the top panel" #~ msgstr "מיקום בסרגל העליון" #~ msgid "Restart the shell to apply this setting." #~ msgstr "יש לאתחל את המעטפת על מנת להחיל את השינויים" #~ msgid "Volume menu integration" #~ msgstr "שילוב בתפריט השמע" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "מפעיל את נגן ברירת המחדל בלחיצה על הסמן" #~ msgid "rating" #~ msgstr "דירוג" #~ msgid "by" #~ msgstr "על ידי" #~ msgid "from" #~ msgstr "מאת" #~ msgid "Unknown Artist" #~ msgstr "אומן לא ידוע" #~ msgid "Unknown Album" #~ msgstr "אלבום לא ידוע" #~ msgid "Unknown Title" #~ msgstr "שיר לא ידוע" #~ msgid "Album cover size" #~ msgstr "גודל עטיפת האלבום" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "גודל האלבום המוצג בתפריט. ברירת המחדל היא רוחב 80px" gnome-shell-extensions-mediaplayer-3.5/po/it.po000066400000000000000000000060251317622025500216620ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2017-04-17 15:47+0200\n" "Last-Translator: Giuseppe Pignataro (Fastbyte01) \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: \n" "X-Generator: Poedit 2.0.1\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volume" #: ../src/ui.js:177 msgid "Playlists" msgstr "Playlist" #: ../src/settings.js:51 msgid "Stopped" msgstr "Fermato" #: ../src/settings.js:52 msgid "Playing" msgstr "In Riproduzione" #: ../src/settings.js:53 msgid "Paused" msgstr "In Pausa" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "Posizione indicatore" #: ../src/prefs.js:42 msgid "Center" msgstr "Centro" #: ../src/prefs.js:43 msgid "Right" msgstr "Destra" #: ../src/prefs.js:44 msgid "System menu" msgstr "Menu di sistema" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "Aspetto indicatore" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Icona simbolica" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Copertina album corrente" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "Testo indicatore di stato" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" "{trackArtist}: Artista, {trackAlbum}: Album, {trackTitle}: Titolo. Markup " "Pango supportato." #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "Larghezza testo indicatore di stato" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" "La larghezza massima prima che il testo dello stato diventino dei " "puntini. Default è 300px." #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Permette di avviare il media player di default" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Mostra il cursore del volume del media player" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Mostra la posizione del cursore del media player" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Mostra le playlist del media player" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Mostra il voto della canzone" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "" "Mostra il voto della canzone attualmente in esecuzione su una scala da 0 " "a 5" #~ msgid "by" #~ msgstr "di" #~ msgid "from" #~ msgstr "da" #~ msgid "Unknown Artist" #~ msgstr "Artista Sconosciuto" #~ msgid "Unknown Album" #~ msgstr "Album Sconosciuto" #~ msgid "Unknown Title" #~ msgstr "Titolo Sconosciuto" #~ msgid "Schema \"%s\" not found." #~ msgstr "Schema \"%s\" non trovato." gnome-shell-extensions-mediaplayer-3.5/po/lt.po000066400000000000000000000042531317622025500216660ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2011-10-31 15:10+0200\n" "Last-Translator: Mantas Mikulėnas \n" "Language-Team: Mantas Mikulėnas \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Garsumas" #: ../src/ui.js:177 #, fuzzy msgid "Playlists" msgstr "Atkuriama" #: ../src/settings.js:51 msgid "Stopped" msgstr "Sustabdyta" #: ../src/settings.js:52 msgid "Playing" msgstr "Atkuriama" #: ../src/settings.js:53 msgid "Paused" msgstr "Pristabdyta" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "" #: ../src/prefs.js:43 msgid "Right" msgstr "" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "" #: ../src/prefs.js:75 #, fuzzy msgid "Show the media player volume slider" msgstr "Atkuriama" #: ../src/prefs.js:79 #, fuzzy msgid "Show the media player position slider" msgstr "Atkuriama" #: ../src/prefs.js:83 #, fuzzy msgid "Show media player playlists" msgstr "Atkuriama" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "" #~ msgid "Unknown Artist" #~ msgstr "Nežinomas atlikėjas" #~ msgid "Unknown Album" #~ msgstr "Nežinomas albumas" #~ msgid "Unknown Title" #~ msgstr "Nežinomas pavadinimas" gnome-shell-extensions-mediaplayer-3.5/po/meson.build000066400000000000000000000001111317622025500230360ustar00rootroot00000000000000i18n = import('i18n') i18n.gettext(meson.project_name(), preset: 'glib') gnome-shell-extensions-mediaplayer-3.5/po/nb_NO.po000066400000000000000000000073501317622025500222430ustar00rootroot00000000000000# Norwegian Bokmål Translation for gnome-shell-extension-mediaplayer package. # Copyright (C) 2017 gnome-shell-extension-mediaplayer package. # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Lars Andre Landås , 2017. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-29 01:30+0100\n" "PO-Revision-Date: 2017-03-29 01:30+0100\n" "Last-Translator: Lars Andre Landås \n" "Language: nb_NO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: \n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volum" #: ../src/ui.js:177 msgid "Playlists" msgstr "Spilleliste" #: ../src/settings.js:51 msgid "Stopped" msgstr "Stoppet" #: ../src/settings.js:52 msgid "Playing" msgstr "Spiller" #: ../src/settings.js:53 msgid "Paused" msgstr "Pauset" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "Indikatorposisjon" #: ../src/prefs.js:42 msgid "Center" msgstr "Midten" #: ../src/prefs.js:43 msgid "Right" msgstr "Høyre" #: ../src/prefs.js:44 msgid "System menu" msgstr "System-meny" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "Indikatorutseende" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Symbolikon" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Spillende albumomslag" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "Statustekst på indikator" #: ../src/prefs.js:58 msgid "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango markup supported." msgstr "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Tittel. Pango markup er støttet." "unterstützt." #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "Bredde på statustekst på indikator" #: ../src/prefs.js:63 msgid "The the maximum width before the status text gets an ellipsis. Default is 300px." msgstr "Maksbredde på statustekst før ellipsen. Standard er 300px." #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Tillat å starte standard medieavspiller" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Vis medieavspillerens volumkontroller" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Vis medieavspillerens posisjonskontroller" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Vis medieavspilleren spilleliste" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Vis sangenes popularitet" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Vis nåværende sangs popularitet på en skala fra 0 til 5" #~ msgid "Position in the top panel" #~ msgstr "Posisjon i paneltopp" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Gnome shell trenger en omstart for å endre innstilling." #~ msgid "Volume menu integration" #~ msgstr "Integrer lydnivåmeny" #, fuzzy #~ msgid "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Starter standard medieavspiller når du klikker på indikatoren eller fra menyen" #~ msgid "rating" #~ msgstr "popularitet" #~ msgid "by" #~ msgstr "av" #~ msgid "from" #~ msgstr "fra" #~ msgid "Unknown Artist" #~ msgstr "Ukjent artist" #~ msgid "Unknown Album" #~ msgstr "Ukjent album" #~ msgid "Unknown Title" #~ msgstr "Ukjent tittel" #~ msgid "Album cover size" #~ msgstr "Størrelse på albumomslag" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "Størrelse på albumomslaget som vises i menyen. Standard er bredde på 80px." #~ msgid "Show the media player in the volume menu" #~ msgstr "Vis medieavspillerem i lydnivåmeny" gnome-shell-extensions-mediaplayer-3.5/po/nl.po000066400000000000000000000074151317622025500216630ustar00rootroot00000000000000# German Translation for gnome-shell-extension-mediaplayer package. # Copyright (C) 2012 gnome-shell-extension-mediaplayer package. # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Frederik Hahne , 2012. # Agilo Kern , 2012. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2013-04-03 20:47+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: VDTT \n" "Language: Dutch\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volume" #: ../src/ui.js:177 msgid "Playlists" msgstr "Afspeellijsten" #: ../src/settings.js:51 msgid "Stopped" msgstr "Gestopt" #: ../src/settings.js:52 msgid "Playing" msgstr "Bezig met afspelen" #: ../src/settings.js:53 msgid "Paused" msgstr "Gepauzeerd" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Midden" #: ../src/prefs.js:43 msgid "Right" msgstr "Rechts" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Uiterlijk" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Symbolisch pictogram" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Huidige albumhoes" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Statustekst" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Artiest, %b: Album, %t: Titel. Pango-opmaak wordt ondersteund." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Statustekst" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Sta toe om de standaard mediaspeler te starten" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Toon de volumeschuif van de mediaspeler" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Toon de positieschuif van de mediaspeler" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Toon de afspeellijsten van de mediaspeler" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Toon nummerwaardering" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "" "Toon de waardering van het huidige afspelende nummer op een schaal van 0 tot " "5" #~ msgid "Position in the top panel" #~ msgstr "Locate op paneel" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Herstart Shell om deze instellingen toe te passen." #~ msgid "Volume menu integration" #~ msgstr "Geïntegreerd in volumemenu" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Start de standaard mediaspeler door te klikken op de indicator" #~ msgid "rating" #~ msgstr "waardering" #~ msgid "by" #~ msgstr "door" #~ msgid "from" #~ msgstr "van" #~ msgid "Unknown Artist" #~ msgstr "Onbekende artiest" #~ msgid "Unknown Album" #~ msgstr "Onbekend album" #~ msgid "Unknown Title" #~ msgstr "Onbekende titel" #~ msgid "Album cover size" #~ msgstr "Grootte van albumhoes" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "De grootte van de getoonde hoes in het menu. Standaardwaarde is 80px " #~ "breed." #~ msgid "Show the media player in the volume menu" #~ msgstr "Mediaplayer im Lautstärkemenü anzeigen" gnome-shell-extensions-mediaplayer-3.5/po/pl.po000066400000000000000000000063651317622025500216700ustar00rootroot00000000000000# Polish translation for gnome-shell-extension-mediaplayer. # Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Piotr Sokół , 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2012-04-08 11:54+0200\n" "Last-Translator: Piotr Sokół \n" "Language-Team: polski <>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bits\n" "Plural-Forms: nplurals=3; plural=((n==1) ? 0 : ((n%10>=2 && n%10<=4 && (n" "%100<10 || n%100>=20)) ? 1 : 2));\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Głośność" #: ../src/ui.js:177 msgid "Playlists" msgstr "Listy odtwarzania" #: ../src/settings.js:51 msgid "Stopped" msgstr "Zatrzymano" #: ../src/settings.js:52 msgid "Playing" msgstr "Odtwarzanie" #: ../src/settings.js:53 msgid "Paused" msgstr "Wstrzymano" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "" #: ../src/prefs.js:43 msgid "Right" msgstr "" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Uruchamianie domyślnego odtwarzacza" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Wyświetlanie suwaka głośności odtwarzacza" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Wyświetlanie suwaka pozycji odtwarzania" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Wyświetlanie list odtwarzania" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "" #~ "Uruchamia domyślny odtwarzacz multimediów po kliknięciu w ikonę stanu " #~ "rozszerzenia" #~ msgid "by" #~ msgstr "w wykonaniu" #~ msgid "from" #~ msgstr "z albumu" #~ msgid "Unknown Artist" #~ msgstr "Nieznany wykonawca" #~ msgid "Unknown Album" #~ msgstr "Nieznany album" #~ msgid "Unknown Title" #~ msgstr "Nieznany tytuł" #~ msgid "Album cover size" #~ msgstr "Rozmiar okładki albumu" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "Określa rozmiar okładki wyświetlanej w menu. Domyślna szerokość obrazu " #~ "wynosi 80 pikseli." #~ msgid "Show the media player in the volume menu" #~ msgstr "Wyświetlanie rozszerzenia w menu głośności" gnome-shell-extensions-mediaplayer-3.5/po/pt_BR.po000066400000000000000000000144711317622025500222600ustar00rootroot00000000000000# Brazilian Portuguese Translation for gnome-shell-extension-mediaplayer. # Copyright (C) 2012 - gnome-shell-extension-mediaplayer # This file is distributed under the same license as the # gnome-shell-extension-mediaplayer package. # Elder Marco , 2012. # Fábio Nogueira , 2016. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-31 10:05-0300\n" "PO-Revision-Date: 2017-03-31 10:27-0300\n" "Last-Translator: Fábio Nogueira \n" "Language-Team: \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.8.11\n" #: prefs.js:39 msgid "Indicator position" msgstr "Posição do indicador" #: prefs.js:41 msgid "Center" msgstr "Centro" #: prefs.js:42 msgid "Right" msgstr "Direita" #: prefs.js:43 msgid "System menu" msgstr "Menu do sistema" #: prefs.js:48 msgid "Indicator appearance" msgstr "Aparência do indicador" #: prefs.js:50 msgid "Symbolic icon" msgstr "Ícone simbólico" #: prefs.js:51 msgid "Current album cover" msgstr "Capa atual" #: prefs.js:56 msgid "Indicator status text" msgstr "Texto de status do indicador" #: prefs.js:57 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" "{trackArtist}: Artista, {trackAlbum}: Álbum, {trackTitle}: Título. Marcação " "Pango suportada." #: prefs.js:61 msgid "Indicator status text width" msgstr "Tamanho do texto de status do indicador" #: prefs.js:62 msgid "" "The maximum width before the status text gets an ellipsis. Default is 300px." msgstr "" "A largura máxima antes do texto de status recebe uma reticência. O padrão é " "300px." #: prefs.js:70 msgid "Large cover size" msgstr "Tamanho da capa grande" #: prefs.js:71 msgid "The size of the cover when zoomed. Default is 192px." msgstr "O tamanho da capa ao aplicar o zoom. O padrão é 192px." #: prefs.js:79 msgid "Small cover size" msgstr "Tamanho da capa pequena" #: prefs.js:80 msgid "The size of the cover when not zoomed. Default is 48px." msgstr "O tamanho da capa sem zoom. O padrão é 48px." #: prefs.js:88 msgid "Allow the starting of the default media player" msgstr "Permitir iniciar o reprodutor padrão de mídia" #: prefs.js:92 msgid "Show the media player volume slider" msgstr "Exibir o controle de volume do reprodutor" #: prefs.js:96 msgid "Show the media player position slider" msgstr "Exibir o controle de posição do reprodutor" #: prefs.js:100 msgid "Show the media player playlists" msgstr "Exibir playlists do reprodutor de mídia" #: prefs.js:101 msgid "Few players currently support the Mpris Playlist Interface." msgstr "Atualmente poucos reprodutores suportam a interface Mpris de Playlist." #: prefs.js:105 msgid "Show the media player tracklist" msgstr "Mostrar a lista de faixas do reprodutor de mídia" #: prefs.js:106 msgid "Very few players currently support the Mpris Tracklist Interface." msgstr "" "Atualmente muito poucos reprodutores suportam a interface Mpris de Playlist." #: prefs.js:110 msgid "Display the current song's rating" msgstr "Exibir a avaliação da música atual" #: prefs.js:111 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Mostra a avaliação da música que está tocando em uma escala de 0 a 5" #: prefs.js:115 msgid "Display song ratings in the tracklist" msgstr "Exibe avalições das músicas na lista de faixas" #: prefs.js:116 msgid "Display the ratings of the songs in tracklist on a 0 to 5 scale" msgstr "" "Exibe as avaliações das músicas na lista de faixas numa escala de 0 a 5" #: prefs.js:120 msgid "Enable Indicator scroll events" msgstr "Habilitar indicador de evetos de rolagem" #: prefs.js:121 msgid "Enables track changes on scrolling the Indicator." msgstr "Permite alterações da faixa no deslocamento do indicador." #: prefs.js:128 msgid "Hide the built-in Mpris controls (Experimental)" msgstr "Ocultar os controles internos Mpris (Experimental)" #: prefs.js:129 msgid "" "Whether to hide the built-in Mpris controls.\n" "This depends on implementation details within GNOME Shell that may change." msgstr "" "Se permite ocultar os controles internos Mpris.\n" "Isto depende dos detalhes de implementação no GNOME Shell que podem ser " "alterados." #: settings.js:60 msgid "Stopped" msgstr "Parado" #: settings.js:61 msgid "Playing" msgstr "Reproduzindo" #: settings.js:62 msgid "Paused" msgstr "Pausado" #: settings.js:73 msgid "Stations" msgstr "Estações" #: settings.js:77 msgid "Current Playlist" msgstr "Playlist atual" #: ui.js:539 msgid "Playlists" msgstr "Playlists" #: ui.js:554 msgid "Tracks" msgstr "Faixas" #~ msgid "Volume" #~ msgstr "Volume" #~ msgid "" #~ "The the maximum width before the status text gets an ellipsis. Default is " #~ "300px." #~ msgstr "" #~ "O tamanho máximo antes do texto de status recebe reticências. O padrão é " #~ "300px." #~ msgid "Allow to start the default media player" #~ msgstr "Permitir iniciar o reprodutor padrão" #~ msgid "Show media player playlists" #~ msgstr "Exibir playlists do reprodutor" #~ msgid "Display song rating" #~ msgstr "Mostrar a avaliação da música" #~ msgid "Position in the top panel" #~ msgstr "Posição no painel superior" #~ msgid "Restart the shell to apply this setting." #~ msgstr "É necessário reiniciar o GNOME Shell" #~ msgid "Volume menu integration" #~ msgstr "Integração com o menu de volume" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Executa o reprodutor padrão através de um clique no indicador" #~ msgid "rating" #~ msgstr "avaliação" #~ msgid "by" #~ msgstr "por" #~ msgid "from" #~ msgstr "de" #~ msgid "Unknown Artist" #~ msgstr "Artista Deconhecido" #~ msgid "Unknown Album" #~ msgstr "Álbum Desconhecido" #~ msgid "Unknown Title" #~ msgstr "Título Desconhecido" #~ msgid "Album cover size" #~ msgstr "Tamanho da capa do álbum" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "O tamanho da capa exibida no menu. Por padrão, 80 pixels de largura." #~ msgid "Show the media player in the volume menu" #~ msgstr "Exibir o media player no menu de volume" #~ msgid "Schema \"%s\" not found." #~ msgstr "Esquema \"%s\" não encontrado." gnome-shell-extensions-mediaplayer-3.5/po/ro.po000066400000000000000000000063621317622025500216720ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2012-11-13 11:05+0100\n" "Last-Translator: Darius Berghe \n" "Language: Romanian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Volum" #: ../src/ui.js:177 msgid "Playlists" msgstr "Listă de redare" #: ../src/settings.js:51 msgid "Stopped" msgstr "Oprit" #: ../src/settings.js:52 msgid "Playing" msgstr "În redare" #: ../src/settings.js:53 msgid "Paused" msgstr "Suspendat" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Centru" #: ../src/prefs.js:43 msgid "Right" msgstr "Dreapta" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Iconița extensiei în panou" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Iconiță simbolică" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Imaginea de album" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Text de stare" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Artist, %b: Album, %t: Titlu. Formatarea Pango e suportată." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Lățimea textului de stare" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Permite pornirea programului media implicit" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Arată bara de control a volumului" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Arată bara de redare a poziției" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Arată listele de redare" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Arată nota melodiei" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Arată nota pentru melodia în curs de rulare pe o scară de la 0 la 5" #~ msgid "Position in the top panel" #~ msgstr "Poziția în panou" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Reporniți interfața Gnome pentru a aplica setările." #~ msgid "Volume menu integration" #~ msgstr "Integrarea în meniul volumului" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Pornește programul media implicit la apăsarea indicatorului" #~ msgid "rating" #~ msgstr "notă" #~ msgid "by" #~ msgstr "de" #~ msgid "from" #~ msgstr "de la" #~ msgid "Unknown Artist" #~ msgstr "Artist necunoscut" #~ msgid "Unknown Album" #~ msgstr "Album necunoscut" #~ msgid "Unknown Title" #~ msgstr "Titlu necunoscut" #~ msgid "Album cover size" #~ msgstr "Mărimea imaginii de album" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "Mărimea imaginii de album afișată în meniu. (Implicit: 80px lățime)" gnome-shell-extensions-mediaplayer-3.5/po/ru.po000066400000000000000000000076271317622025500217050ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2012-11-11 12:25+0300\n" "Last-Translator: Pavel Vasin\n" "Language-Team: Aries-Soft Roman aries-soft@mail.ru\n" "Language: Russian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Громкость" #: ../src/ui.js:177 msgid "Playlists" msgstr "Плейлисты" #: ../src/settings.js:51 msgid "Stopped" msgstr "Остановлено" #: ../src/settings.js:52 msgid "Playing" msgstr "Проигрывается" #: ../src/settings.js:53 msgid "Paused" msgstr "Пауза" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "По центру" #: ../src/prefs.js:43 msgid "Right" msgstr "Справа" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Внешний вид" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Иконка" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Обложка альбома" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Текст статуса" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" "%a: исполнитель, %b: альбом, %t: название. Поддерживается разметка Pango." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Текст статуса" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Разрешить запуск плеера по умолчанию" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Показывать регулятор громкости" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Показывать регулятор позиции" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Показывать плейлисты" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Показывать оценку песен" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Показывать оценку текущей песни по шкале от 0 до 5" #~ msgid "Position in the top panel" #~ msgstr "Расположение на панели" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Для этой опции требуется перезапуск gnome-shell." #~ msgid "Volume menu integration" #~ msgstr "В индикаторе громкости" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Запуск плеера кликом по иконке индикатора" #~ msgid "rating" #~ msgstr "оценка" #~ msgid "by" #~ msgstr "исполняет" #~ msgid "from" #~ msgstr "из альбома" #~ msgid "Unknown Artist" #~ msgstr "Неизвестный исполнитель" #~ msgid "Unknown Album" #~ msgstr "Неизвестный альбом" #~ msgid "Unknown Title" #~ msgstr "Неизвестное название" #~ msgid "Album cover size" #~ msgstr "Размер обложки альбома" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "Размер обложки, отображаемой в меню. По умолчанию 80 пикселей." #~ msgid "Show the media player in the volume menu" #~ msgstr "Встроить в индикатор громкости" gnome-shell-extensions-mediaplayer-3.5/po/sk.po000066400000000000000000000071721317622025500216670ustar00rootroot00000000000000# Slovak translation for gnome-shell-extension-mediaplayer. # Copyright (C) 2015 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Juraj Fiala , 2015. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-20 18:00+0100\n" "PO-Revision-Date: 2015-10-20 18:00+0100\n" "Last-Translator: Juraj Fiala \n" "Language-Team: Slovak\n" "Language: Slovak\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bits\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Hlasitosť" #: ../src/ui.js:177 msgid "Playlists" msgstr "Zoznamy skladieb" #: ../src/settings.js:51 msgid "Stopped" msgstr "Zastavené" #: ../src/settings.js:52 msgid "Playing" msgstr "Prehráva sa" #: ../src/settings.js:53 msgid "Paused" msgstr "Pozastavené" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "Umiestnenie indikátora" #: ../src/prefs.js:42 msgid "Center" msgstr "Stred" #: ../src/prefs.js:43 msgid "Right" msgstr "Vpravo" #: ../src/prefs.js:44 msgid "System menu" msgstr "Sytémové menu" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "Vzhľad indikátora" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Symbolická ikona" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Obal aktuálneho albumu" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "Text stavu indikátora" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Interpret, %b: Album, %t: Skladba. Pango syntax podporovaný." #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "Šírka textu stavu indikátora" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" "Maximálna šírka pred skrátením stavového textu. Predvolená hodnota je " "300px." #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Povoliť zapnutie predvoleného prehrávača médií" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Ukázať ovládanie hlasitosi prehrávača médií" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Ukázať posuvník na časovej osi prehrávača médií" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Ukázať zoznamy skladieb prehrávača médií" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Ukázať hodnotenie skladby" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Ukázať hodnotenie práve hranej skladby na stupnici od 0 po 5" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "" #~ "Spustí predvolený prehrávač médií pri kliknutí na indikátor alebo z menu" #~ msgid "by" #~ msgstr "od interpreta" #~ msgid "from" #~ msgstr "z albumu" #~ msgid "Unknown Artist" #~ msgstr "Neznámy interpret" #~ msgid "Unknown Album" #~ msgstr "Neznámy album" #~ msgid "Unknown Title" #~ msgstr "Neznáma skladba" #~ msgid "Album cover size" #~ msgstr "Velkosť obalu albumu" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "" #~ "Veľkosť obalu zobrazeného v menu. Predvolená hodnota je 80px šírka." #~ msgid "Show the media player in the volume menu" #~ msgstr "Ukázať prehrávač médií v menu hlasitosti" gnome-shell-extensions-mediaplayer-3.5/po/sr.po000066400000000000000000000111021317622025500216620ustar00rootroot00000000000000# Serbian Translation for gnome-shell-extension-mediaplayer package # Преводи на српски језик за пакет „gnome-shell-extension-mediaplayer“. # Copyright (C) 2012 gnome-shell-extension-mediaplayer package. # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Марко М. Костић , 2015. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2015-06-11 13:33+0200\n" "Last-Translator: Марко М. Костић (Marko M. Kostić) \n" "Language-Team: Serbian\n" "Language: Serbian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Јачина звука" #: ../src/ui.js:177 msgid "Playlists" msgstr "Списак песама" #: ../src/settings.js:51 msgid "Stopped" msgstr "Заустављено" #: ../src/settings.js:52 msgid "Playing" msgstr "Пуштено" #: ../src/settings.js:53 msgid "Paused" msgstr "Паузирано" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Центар" #: ../src/prefs.js:43 msgid "Right" msgstr "Десно" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Изглед" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Симболичка иконица" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Тренутни омот албума" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Статусни текст" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Извођач, %b: Албум, %t: Наслов. Панго маркап је подржан." #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Статусни текст" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Омогући покретање подразумеваног пуштача музике" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Покажи клизач за промену јачине звука" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Покажи клизач за премотавање песме" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Прикажи списак са песмама" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Прикажи оцену песме" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Прикажи оцену на скали од 0 до 5 за тренутно пуштену песму" #~ msgid "Position in the top panel" #~ msgstr "Положај у горњој траци" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Поново покрените Гномову шкољку да би се подешавање применило." #~ msgid "Volume menu integration" #~ msgstr "Угради у показивач јачине звука" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Покреће подразумевани пуштач музике приликом кликтања на показивач" #~ msgid "rating" #~ msgstr "оцена" #~ msgid "by" #~ msgstr "изводи" #~ msgid "from" #~ msgstr "са албума" #~ msgid "Unknown Artist" #~ msgstr "Непознат извођач" #~ msgid "Unknown Album" #~ msgstr "Непознат албум" #~ msgid "Unknown Title" #~ msgstr "Непознат наслов" #~ msgid "Album cover size" #~ msgstr "Величина омота албума" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "Величина омота приказаног у менију. Подразумевано је 80 пиксела." #~ msgid "Show the media player in the volume menu" #~ msgstr "Прикажи пуштач музике у показивачу јачине звука" gnome-shell-extensions-mediaplayer-3.5/po/tr.po000066400000000000000000000074311317622025500216750ustar00rootroot00000000000000# Turkish Translation for gnome-shell-extension-mediaplayer package. # Copyright (C) 2012 gnome-shell-extension-mediaplayer package. # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # Osman Karagöz , 2013 # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2013-04-03 13:23+0200\n" "Last-Translator: Osman Karagöz \n" "Language-Team: \n" "Language: Turkish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 1.5.4\n" "X-Poedit-SourceCharset: UTF-8\n" #: ../src/ui.js:165 msgid "Volume" msgstr "Ses" #: ../src/ui.js:177 msgid "Playlists" msgstr "Çalma Listesi" #: ../src/settings.js:51 msgid "Stopped" msgstr "Durduruldu" #: ../src/settings.js:52 msgid "Playing" msgstr "Çalıyor" #: ../src/settings.js:53 msgid "Paused" msgstr "Duraklatıldı" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "Merkez" #: ../src/prefs.js:43 msgid "Right" msgstr "Sağ" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "Görünüm" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "Sembolik simge" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "Şuanki albüm kapağı" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "Durum iletisi" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a: Sanatçı, %b: Albüm, %t: Başlık. Pango biçimlendirme desteği" #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "Durum iletisi" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "Öntanımlı medya oynatıcısını başlatmaya izin ver" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "Medya oynatıcı ses ayarlayıcısını göster" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "Medya oynatıcı konum ayarlayıcısını göster" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "Medya oynatıcı oynatma listesi göster" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "Şarkı popülerliğini göster" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "Çalan şarkının popülerliğini 0-5 aralığında göster" #~ msgid "Position in the top panel" #~ msgstr "Üst paneldeki konum" #~ msgid "Restart the shell to apply this setting." #~ msgstr "Bu ayarları uygulamak için kabuğu tekrar başlatın" #~ msgid "Volume menu integration" #~ msgstr "Ses ayar menüsüne yerleş" #, fuzzy #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "Eklenti düğmesi öntanımlı medya oynatıcıyı başlatır" #~ msgid "rating" #~ msgstr "popülerlik" #~ msgid "by" #~ msgstr "sanatçı" #~ msgid "from" #~ msgstr "albüm" #~ msgid "Unknown Artist" #~ msgstr "Bilinmeyen Sanatçı" #~ msgid "Unknown Album" #~ msgstr "Bilinmeyen Albüm" #~ msgid "Unknown Title" #~ msgstr "Bilinmeyen Başlık" #~ msgid "Album cover size" #~ msgstr "Albüm kapak boyutu" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "Menüde görünen kapak boyutudur. Öntanımlı genişlik 80 piksel" #~ msgid "Show the media player in the volume menu" #~ msgstr "Medya oynatıcısını ses menüsünde göster" gnome-shell-extensions-mediaplayer-3.5/po/zh_CN.po000066400000000000000000000074621317622025500222550ustar00rootroot00000000000000# Chinese translations for gnome-shell-extension-mediaplayer package # gnome-shell-extension-mediaplayer 软件包的简体中文翻译. # Copyright (C) 2013 THE gnome-shell-extension-mediaplayer'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell-extension-mediaplayer package. # 绿色圣光 , 2013, 2014, 2015. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-04 17:37+0800\n" "PO-Revision-Date: 2015-09-05 23:10+0800\n" "Last-Translator: 绿色圣光 \n" "Language-Team: Chinese (Simplified) <>\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Gtranslator 2.91.7\n" #: ../src/ui.js:163 msgid "Volume" msgstr "音量" #: ../src/ui.js:175 msgid "Playlists" msgstr "播放列表" #: ../src/settings.js:52 msgid "Stopped" msgstr "已停止" #: ../src/settings.js:53 msgid "Playing" msgstr "播放中" #: ../src/settings.js:54 msgid "Paused" msgstr "已暂停" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "指示器位置" #: ../src/prefs.js:42 msgid "Center" msgstr "中央" #: ../src/prefs.js:43 msgid "Right" msgstr "右边" #: ../src/prefs.js:44 msgid "System menu" msgstr "系统菜单" #: ../src/prefs.js:49 msgid "Indicator appearance" msgstr "指示器外观" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "符号图标" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "当前专辑封面" #: ../src/prefs.js:57 msgid "Indicator status text" msgstr "指示器状态文本" #: ../src/prefs.js:58 msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "" "{trackArtist}:艺人,{trackAlbum}:专辑,{trackTitle}:标题。支持 Pango " "markup 标签语法。" #: ../src/prefs.js:62 msgid "Indicator status text width" msgstr "指示器状态文本宽度" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "状态文本的最大宽度,超出的部分会以省略号代替。默认为 300px。" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "允许启动默认的媒体播放器" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "显示媒体播放器的音量控制" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "显示媒体播放器的进度控制" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "显示媒体播放器的播放列表" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "显示歌曲评分" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "从零到五星,展示当前正在播放的歌曲的评分。" #~ msgid "Position in the top panel" #~ msgstr "顶端面板中的位置" #~ msgid "Restart the shell to apply this setting." #~ msgstr "请重新启动 Gnome Shell 以应用设定值。" #~ msgid "Volume menu integration" #~ msgstr "整合到音量菜单" #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "点击菜单或状态图标时,启动默认的媒体播放器。" #~ msgid "Album cover size" #~ msgstr "专辑封面大小" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "在菜单中显示的封面大小。默认值为 80 个像素宽。" #~ msgid "rating" #~ msgstr "评分" #~ msgid "Unknown Title" #~ msgstr "未知标题" #~ msgid "by" #~ msgstr "艺人:" #~ msgid "Unknown Artist" #~ msgstr "未知艺人" #~ msgid "from" #~ msgstr "专辑:" #~ msgid "Unknown Album" #~ msgstr "未知专辑" gnome-shell-extensions-mediaplayer-3.5/po/zh_TW.po000066400000000000000000000070261317622025500223030ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: gnome-shell-extension-mediaplayer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-08-27 18:32+0200\n" "PO-Revision-Date: 2013-05-13 15:52+0800\n" "Last-Translator: 盧瑞元 (Ruei-Yuan Lu) \n" "Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n>1);\n" "X-Poedit-Language: Traditional Chinese\n" "X-Poedit-Country: TAIWAN\n" #: ../src/ui.js:165 msgid "Volume" msgstr "音量" #: ../src/ui.js:177 msgid "Playlists" msgstr "播放清單" #: ../src/settings.js:51 msgid "Stopped" msgstr "已停止" #: ../src/settings.js:52 msgid "Playing" msgstr "播放中" #: ../src/settings.js:53 msgid "Paused" msgstr "已暫停" #: ../src/prefs.js:40 msgid "Indicator position" msgstr "" #: ../src/prefs.js:42 msgid "Center" msgstr "中央" #: ../src/prefs.js:43 msgid "Right" msgstr "右邊" #: ../src/prefs.js:44 msgid "System menu" msgstr "" #: ../src/prefs.js:49 #, fuzzy msgid "Indicator appearance" msgstr "外觀" #: ../src/prefs.js:51 msgid "Symbolic icon" msgstr "符號圖示" #: ../src/prefs.js:52 msgid "Current album cover" msgstr "目前的專輯封面" #: ../src/prefs.js:57 #, fuzzy msgid "Indicator status text" msgstr "狀態文字訊息" #: ../src/prefs.js:58 #, fuzzy msgid "" "{trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango " "markup supported." msgstr "%a:藝人,%b:專輯,%t:標題。支援 Pango markup 標籤語法。" #: ../src/prefs.js:62 #, fuzzy msgid "Indicator status text width" msgstr "狀態文字訊息" #: ../src/prefs.js:63 msgid "" "The the maximum width before the status text gets an ellipsis. Default is " "300px." msgstr "" #: ../src/prefs.js:71 msgid "Allow to start the default media player" msgstr "允許啟動預設的媒體播放器" #: ../src/prefs.js:75 msgid "Show the media player volume slider" msgstr "顯示媒體播放器的音量控制" #: ../src/prefs.js:79 msgid "Show the media player position slider" msgstr "顯示媒體播放器的進度控制" #: ../src/prefs.js:83 msgid "Show media player playlists" msgstr "顯示媒體播放器的播放清單" #: ../src/prefs.js:87 msgid "Display song rating" msgstr "顯示歌曲評價" #: ../src/prefs.js:88 msgid "Display the currently playing song's rating on a 0 to 5 scale" msgstr "以零到五星表示目前正在播放的歌曲的評價。" #~ msgid "Position in the top panel" #~ msgstr "頂端面板中的位置" #~ msgid "Restart the shell to apply this setting." #~ msgstr "請重新啟動 Gnome Shell 以套用設定值。" #~ msgid "Volume menu integration" #~ msgstr "整合至音量選單" #~ msgid "" #~ "Runs the default mediaplayer by clicking on the indicator or from the menu" #~ msgstr "點選選單或狀態圖示時,啟動預設的媒體播放器。" #~ msgid "Album cover size" #~ msgstr "專輯封面大小" #~ msgid "The size of the cover displayed in the menu. Default is 80px width." #~ msgstr "在選單中顯示的封面大小。預設值為 80 個像素寬。" #~ msgid "rating" #~ msgstr "評價" #~ msgid "by" #~ msgstr "藝人:" #~ msgid "from" #~ msgstr "專輯:" #~ msgid "Unknown Artist" #~ msgstr "未知的藝人" #~ msgid "Unknown Album" #~ msgstr "未知的專輯" #~ msgid "Unknown Title" #~ msgstr "未知的標題" gnome-shell-extensions-mediaplayer-3.5/src/000077500000000000000000000000001317622025500210545ustar00rootroot00000000000000gnome-shell-extensions-mediaplayer-3.5/src/dbus.js000066400000000000000000000240401317622025500223470ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint multistr: true */ /** 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, see . **/ const Gio = imports.gi.Gio; const Lang = imports.lang; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Settings = Me.imports.settings; const DBusIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const DBusProxy = Gio.DBusProxy.makeProxyWrapper(DBusIface); const PropertiesIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const PropertiesProxy = Gio.DBusProxy.makeProxyWrapper(PropertiesIface); const MediaServer2Iface = '\ \ \ \ \ \ \ \ \ \ '; const MediaServer2Proxy = Gio.DBusProxy.makeProxyWrapper(MediaServer2Iface); // For some reason the Nuvola dev was told to scab in a non-spec // prop and method for the support of setting ratings instead of // making a seperate ratings extension interface. // Oh well, they really don't hurt anything. No other player will try // to use them anyway... const MediaServer2PlayerIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const MediaServer2PlayerProxy = Gio.DBusProxy.makeProxyWrapper(MediaServer2PlayerIface); const MediaServer2PlaylistsIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const MediaServer2PlaylistsProxy = Gio.DBusProxy.makeProxyWrapper(MediaServer2PlaylistsIface); const MediaServer2TracklistIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const MediaServer2TracklistProxy = Gio.DBusProxy.makeProxyWrapper(MediaServer2TracklistIface); const PithosRatingsIface = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const PithosRatingsProxy = Gio.DBusProxy.makeProxyWrapper(PithosRatingsIface); const RatingsExtensionIface = '\ \ \ \ \ \ \ \ '; const RatingsExtensionProxy = Gio.DBusProxy.makeProxyWrapper(RatingsExtensionIface); const Rhythmbox3Iface = '\ \ \ \ \ \ \ '; const Rhythmbox3Proxy = Gio.DBusProxy.makeProxyWrapper(Rhythmbox3Iface); function DBus() { return new DBusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus'); } function Properties(owner, callback) { new PropertiesProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2', callback); } function MediaServer2(owner, callback) { new MediaServer2Proxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2', callback); } function MediaServer2Player(owner, callback) { new MediaServer2PlayerProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2', callback); } function MediaServer2Playlists(owner, callback) { new MediaServer2PlaylistsProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2', callback); } function MediaServer2Tracklist(owner, callback) { new MediaServer2TracklistProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2', callback); } function PithosRatings(owner, callback) { if (owner != 'org.mpris.MediaPlayer2.pithos') { callback(false); } else { let proxy = new PithosRatingsProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2'); if (proxy.HasPithosExtension) { callback(proxy); } else { callback(false); } } } function RatingsExtension(owner, callback) { if (Settings.SUPPORTS_RATINGS_EXTENSION.indexOf(owner) == -1) { callback(false); } else { let proxy = new RatingsExtensionProxy(Gio.DBus.session, owner, '/org/mpris/MediaPlayer2'); if (proxy.HasRatingsExtension) { callback(proxy); } else { callback(false); } } } function RhythmboxRatings(owner) { if (owner != 'org.mpris.MediaPlayer2.rhythmbox') { return false; } else { return new Rhythmbox3Proxy(Gio.DBus.session, "org.gnome.Rhythmbox3", "/org/gnome/Rhythmbox3/RhythmDB"); } } gnome-shell-extensions-mediaplayer-3.5/src/extension.js000066400000000000000000000064101317622025500234270ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /* global global: false */ /** 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, see . **/ const Main = imports.ui.main; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const Me = imports.misc.extensionUtils.getCurrentExtension(); const Lib = Me.imports.lib; const Manager = Me.imports.manager; const Panel = Me.imports.panel; const Settings = Me.imports.settings; /* global values */ let manager; let indicator; let _stockMpris; let _stockMprisOldShouldShow; function init() { Lib.initTranslations(Me); Lib.addIcon(Me); Settings.init(); if (Settings.MINOR_VERSION > 19) { //Monkey patch _stockMpris = Main.panel.statusArea.dateMenu._messageList._mediaSection; _stockMprisOldShouldShow = _stockMpris._shouldShow; } Settings.gsettings.connect("changed::" + Settings.MEDIAPLAYER_INDICATOR_POSITION_KEY, function() { _reset(); }); } function _reset() { if (manager) { disable(); enable(); } } function enable() { let position = Settings.gsettings.get_enum(Settings.MEDIAPLAYER_INDICATOR_POSITION_KEY), menu, desiredMenuPosition; if (position == Settings.IndicatorPosition.VOLUMEMENU) { indicator = new Panel.AggregateMenuIndicator(); menu = Main.panel.statusArea.aggregateMenu.menu; desiredMenuPosition = Main.panel.statusArea.aggregateMenu.menu._getMenuItems().indexOf(Main.panel.statusArea.aggregateMenu._rfkill.menu); } else { indicator = new Panel.PanelIndicator(); menu = indicator.menu; desiredMenuPosition = 0; } manager = new Manager.PlayerManager(menu, desiredMenuPosition); if (position == Settings.IndicatorPosition.LEFT) { Main.panel.addToStatusArea('mediaplayer', indicator, 999, 'left'); } else if (position == Settings.IndicatorPosition.RIGHT) { Main.panel.addToStatusArea('mediaplayer', indicator); } else if (position == Settings.IndicatorPosition.CENTER) { Main.panel.addToStatusArea('mediaplayer', indicator, 999, 'center'); } else { Main.panel.statusArea.aggregateMenu._indicators.insert_child_below(indicator.indicators, Main.panel.statusArea.aggregateMenu._screencast.indicators); } indicator.manager = manager; } function disable() { manager.destroy(); manager = null; if (indicator instanceof Panel.PanelIndicator) { indicator.destroy(); } else { indicator.indicators.destroy(); } indicator = null; if (Settings.MINOR_VERSION > 19) { //Revert Monkey patch _stockMpris._shouldShow = _stockMprisOldShouldShow; if (_stockMpris._shouldShow()) { _stockMpris.actor.show(); } } } gnome-shell-extensions-mediaplayer-3.5/src/lib.js000066400000000000000000000032171317622025500221630ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /** 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, see . **/ const Gtk = imports.gi.Gtk; const Gio = imports.gi.Gio; const Gettext = imports.gettext; function getSettings(extension) { let schemaName = 'org.gnome.shell.extensions.mediaplayer'; let schemaDir = extension.dir.get_child('schemas').get_path(); let schemaSource = Gio.SettingsSchemaSource.new_from_directory(schemaDir, Gio.SettingsSchemaSource.get_default(), false); let schema = schemaSource.lookup(schemaName, false); return new Gio.Settings({ settings_schema: schema }); } function initTranslations(extension) { let localeDir = extension.dir.get_child('locale').get_path(); Gettext.bindtextdomain('gnome-shell-extensions-mediaplayer', localeDir); } function addIcon(extension) { let iconPath = extension.dir.get_path(); Gtk.IconTheme.get_default().append_search_path(iconPath); } gnome-shell-extensions-mediaplayer-3.5/src/manager.js000066400000000000000000000273131317622025500230320ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /* global global: false */ /** 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, see . **/ const Mainloop = imports.mainloop; const Lang = imports.lang; const Signals = imports.signals; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Panel = Me.imports.panel; const Player = Me.imports.player; const Settings = Me.imports.settings; const DBusIface = Me.imports.dbus; const UI = Me.imports.ui; var PlayerManager = new Lang.Class({ Name: 'PlayerManager', _init: function(menu, desiredMenuPosition) { this._disabling = false; // the menu this.menu = menu; this._settings = Settings.gsettings; this.desiredMenuPosition = desiredMenuPosition; this.menu.connect('open-state-changed', Lang.bind(this, function(menu, open) { let keepActiveOpen = this._settings.get_boolean(Settings.MEDIAPLAYER_KEEP_ACTIVE_OPEN_KEY); if (open && keepActiveOpen) { this.showActivePlayer(); } })); this._settingChangeId = this._settings.connect("changed::" + Settings.MEDIAPLAYER_KEEP_ACTIVE_OPEN_KEY, Lang.bind(this, function(settings, key) { if (settings.get_boolean(key)) { this.showActivePlayer(); } else { this.closeAllPlayers(); } })); // players list this._players = {}; this._addPlayerTimeOutIds = {}; // player shown in the panel this._activePlayer = null; this._activePlayerId = null; // the DBus interface this._dbus = new DBusIface.DBus(); // player DBus name pattern let name_regex = /^org\.mpris\.MediaPlayer2\./; // load players this._dbus.ListNamesRemote(Lang.bind(this, function(names) { let playerNames = []; for (let n in names[0]) { let name = names[0][n]; if (name_regex.test(name)) { playerNames.push(name); } } playerNames.sort(); for (let i in playerNames) { let player = playerNames[i]; this._dbus.GetNameOwnerRemote(player, Lang.bind(this, function(owner) { if (!this._disabling) { this._addPlayer(player, owner); } })); } })); // watch players this._ownerChangedId = this._dbus.connectSignal('NameOwnerChanged', Lang.bind(this, function(proxy, sender, [name, old_owner, new_owner]) { if (name_regex.test(name)) { if (!this._disabling) { if (new_owner && !old_owner) { this._addPlayer(name, new_owner); } else if (old_owner && !new_owner) { this._removePlayerFromMenu(name, old_owner); } else { this._changePlayerOwner(name, old_owner, new_owner); } } } } )); }, get activePlayer() { return this._activePlayer; }, set activePlayer(player) { if (player == this._activePlayer) return; if (player === null) { this._activePlayerId = null; this._activePlayer = null; this.emit('player-active-remove'); return; } if (this._activePlayerId) { this._activePlayer.disconnect(this._activePlayerId); this._activePlayerId = null; } this._activePlayer = player; this._activePlayerId = this._activePlayer.connect('player-update', Lang.bind(this, this._onActivePlayerUpdate)); let keepActiveOpen = this._settings.get_boolean(Settings.MEDIAPLAYER_KEEP_ACTIVE_OPEN_KEY); if (keepActiveOpen) { this.showActivePlayer(); } this.emit('player-active-update', player.state); }, showActivePlayer: function() { if (!this._activePlayer || !this.menu.actor.visible) { return; } for (let owner in this._players) { if (this._players[owner].player == this._activePlayer && this._players[owner].ui.menu) { this._players[owner].ui.menu.open(); break; } } }, closeAllPlayers: function() { for (let owner in this._players) { if (this._players[owner].ui.menu) { this._players[owner].ui.menu.close(); } } }, nbPlayers: function() { return Object.keys(this._players).length; }, getPlayersByStatus: function(status, preference) { // Return a list of running players by status and preference // preference is a player instance, if found in the list // it will be put in the first position return Object.keys(this._players).map(Lang.bind(this, function(owner) { return this._players[owner].player; })) .filter(function(player) { if (player && player.state.status == status) return true; else return false; }) .sort(function(a, b) { if (a == preference) return -1; else if (b == preference) return 1; return 0; }); }, _isInstance: function(busName) { // MPRIS instances are in the form // org.mpris.MediaPlayer2.name.instanceXXXX // ...except for VLC, which to this day uses // org.mpris.MediaPlayer2.name-XXXX return busName.split('.').length > 4 || /^org\.mpris\.MediaPlayer2\.vlc-\d+$/.test(busName); }, _addPlayer: function(busName, owner) { // Give players 1 sec to populate their interfaces before actually adding them. if (this._addPlayerTimeOutIds[busName] && this._addPlayerTimeOutIds[busName] !== 0) { Mainloop.source_remove(this._addPlayerTimeOutIds[busName]); this._addPlayerTimeOutIds[busName] = 0; } this._addPlayerTimeOutIds[busName] = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() { this._addPlayerTimeOutIds[busName] = 0; if (this._players[owner]) { let prevName = this._players[owner].player.busName; // HAVE: ADDING: ACTION: // master master reject, cannot happen // master instance upgrade to instance // instance master reject, duplicate // instance instance reject, cannot happen if (this._isInstance(busName) && !this._isInstance(prevName)) this._players[owner].player.busName = busName; else return false; } else if (owner) { let player = new Player.MPRISPlayer(busName, owner); let ui = new UI.PlayerUI(player); this._players[owner] = { player: player, ui: ui, signals: [], signalsUI: [] }; if (this.nbPlayers() === 1) { this.emit('connect-signals'); } let playerItem = this._players[owner]; let playerUpdateId = playerItem.player.connect('player-update', Lang.bind(this, this._onPlayerUpdate)); playerItem.signals.push(playerUpdateId); this._addPlayerToMenu(owner); } return false; })); }, _onPlayerUpdate: function(player, newState) { if (newState.status) this._refreshActivePlayer(player); }, _onActivePlayerUpdate: function(player, newState) { this.emit('player-active-update', newState); }, _addPlayerToMenu: function(owner) { let actualPos = this.desiredMenuPosition + this.nbPlayers(); let playerItem = this._players[owner]; this.menu.addMenuItem(playerItem.ui, actualPos); this._refreshActivePlayer(playerItem.player); }, _getMenuItem: function(position) { let items = this.menu.box.get_children().map(function(actor) { return actor._delegate; }); if (items[position]) return items[position]; else return null; }, _removeMenuItem: function(position) { let item = this._getMenuItem(position); if (item) item.destroy(); }, _getPlayerMenuPosition: function(ui) { let items = this.menu.box.get_children().map(function(actor) { return actor._delegate; }); for (let i in items) { if (items[i] == ui) return i; } return null; }, _removePlayerFromMenu: function(busName, owner) { if (this._players[owner]) { for (let id in this._players[owner].signals) this._players[owner].player.disconnect(this._players[owner].signals[id]); for (let id in this._players[owner].signalsUI) this._players[owner].ui.disconnect(this._players[owner].signalsUI[id]); if (this._players[owner].ui) this._players[owner].ui.destroy(); if (this._players[owner].player) this._players[owner].player.destroy(); delete this._players[owner]; } this._refreshActivePlayer(null); if (this.nbPlayers() === 0) { this.emit('disconnect-signals'); } }, _changePlayerOwner: function(busName, oldOwner, newOwner) { if (this._players[oldOwner] && busName == this._players[oldOwner].player.busName) { this._players[newOwner] = this._players[oldOwner]; this._players[newOwner].player.owner = newOwner; delete this._players[oldOwner]; } this._refreshActivePlayer(this._players[newOwner].player); }, _refreshActivePlayer: function(player) { // Display current status in the top panel if (this.nbPlayers() > 0) { // Get the first player // with status PLAY or PAUSE // else all players are stopped this.activePlayer = [] .concat( this.getPlayersByStatus(Settings.Status.PLAY, player), this.getPlayersByStatus(Settings.Status.PAUSE, player), this.getPlayersByStatus(Settings.Status.STOP, player) )[0] || null; } else { this.activePlayer = null; } }, destroy: function() { this._disabling = true; this._settings.disconnect(this._settingChangeId); if (this._ownerChangedId) this._dbus.disconnectSignal(this._ownerChangedId); for (let owner in this._players) this._removePlayerFromMenu(null, owner); // Cancel all pending timeouts. Wouldn't want to try to add a player if we're disabled. for (let busName in this._addPlayerTimeOutIds) { if (this._addPlayerTimeOutIds[busName] !== 0) { Mainloop.source_remove(this._addPlayerTimeOutIds[busName]); this._addPlayerTimeOutIds[busName] = 0; } } } }); Signals.addSignalMethods(PlayerManager.prototype); gnome-shell-extensions-mediaplayer-3.5/src/meson.build000066400000000000000000000006211317622025500232150ustar00rootroot00000000000000install_data([ 'dbus.js', 'extension.js', 'lib.js', 'manager.js', 'panel.js', 'player.js', 'prefs.js', 'settings.js', 'stylesheet.css', 'ui.js', 'util.js', 'widget.js', 'metadata.json', 'mpi-symbolic.svg' ], install_dir: EXTENSION_DIR ) install_data( 'org.gnome.shell.extensions.mediaplayer.gschema.xml', install_dir : SCHEMA_DIR ) gnome-shell-extensions-mediaplayer-3.5/src/metadata.json000066400000000000000000000004711317622025500235310ustar00rootroot00000000000000{ "uuid": "mediaplayer@patapon.info", "name": "Media Player Indicator", "description": "Control MPRIS Version 2 Capable Media Players.", "original-author": "eon@patapon.info", "shell-version": ["3.18", "3.20", "3.22", "3.24", "3.26"], "url": "https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/" } gnome-shell-extensions-mediaplayer-3.5/src/mpi-symbolic.svg000066400000000000000000000036511317622025500242060ustar00rootroot00000000000000 image/svg+xml gnome-shell-extensions-mediaplayer-3.5/src/org.gnome.shell.extensions.mediaplayer.gschema.xml000066400000000000000000000124131317622025500326770ustar00rootroot00000000000000 'volume-menu' Where to display the indicator (center, right, volume menu) false Always hide the indicator in the system menu Whether to always hide the indicator in the system menu. false Show the current tracks cover in the panel Show the current tracks cover in the panel instead of an icon. 'small' The style of the play control buttons false Show the media player volume slider Whether to show the volume control of the mediaplayers. true Show the media player position slider Whether to show the position slider of the mediaplayers. false Show media player playlists Whether to show the playlists of the mediaplayers. false Show the current Playlist Title in the main trackbox Whether to show the current Playlist Title in the main trackbox. false Show media player tracklist Whether to show the tracklist of the mediaplayers. false Display song rating Display the currently playing song's rating on a 0 to 5 scale false Display song ratings in the tracklist Display the ratings of the songs in tracklist on a 0 to 5 scale '' The indicator status text 300 Status text size The the maximum width before the title gets an ellipsis. Default is 300px. true Show a play state icon in the indicator Show a play state icon in the indicator. true Show the media player in the volume menu Whether to show the mediaplayer controls in the volume menu. true Enable Indicator scroll events Enables track changes on scrolling the Indicator. false Hide the built-in Mpris applet Whether to hide the built-in Mpris applet. true Always keep the active player menu open Always keep the active player menu open. false Always show a Stop Button in the Player controls Always show a Stop Button in the Player controls. false Show shuffle and repeat buttons Show shuffle and repeat buttons. false Show a Play Status Icon for each Player Show a Play Status Icon for each Player. gnome-shell-extensions-mediaplayer-3.5/src/panel.js000066400000000000000000000261051317622025500225150ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /** 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, see . **/ 'use strict'; const Lang = imports.lang; const Clutter = imports.gi.Clutter; const Pango = imports.gi.Pango; const St = imports.gi.St; const PanelMenu = imports.ui.panelMenu; const Signals = imports.signals; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Settings = Me.imports.settings; const Util = Me.imports.util; const IndicatorMixin = { set manager(manager) { this._manager = manager; this.manager.connect('player-active-update', Lang.bind(this, this._commonOnActivePlayerUpdate)); this.manager.connect('player-active-remove', Lang.bind(this, this._commonOnActivePlayerRemove)); this.manager.connect('connect-signals', Lang.bind(this, this._connectSignals)); this.manager.connect('disconnect-signals', Lang.bind(this, this._disconnectSignals)); }, get manager() { return this._manager; }, get state() { if (this.manager.activePlayer) { return this.manager.activePlayer.state; } return {}; }, _onScrollEvent: function(actor, event) { if (Settings.gsettings.get_boolean(Settings.MEDIAPLAYER_ENABLE_SCROLL_EVENTS_KEY)) { switch (event.get_scroll_direction()) { case Clutter.ScrollDirection.UP: this.manager.activePlayer.previous(); break; case Clutter.ScrollDirection.DOWN: this.manager.activePlayer.next(); break; } } }, _onButtonEvent: function(actor, event) { if (event.type() == Clutter.EventType.BUTTON_PRESS) { let button = event.get_button(); if (button == 2 && this.manager.activePlayer) { this.manager.activePlayer.playPause(); return Clutter.EVENT_STOP; } } return Clutter.EVENT_PROPAGATE; }, _connectSignals: function() { this._signalsId.push(this._settings.connect("changed::" + Settings.MEDIAPLAYER_COVER_STATUS_KEY, Lang.bind(this, function(settings, key) { this._useCoverInPanel = settings.get_boolean(key); this._updatePanel(); }))); this._signalsId.push(this._settings.connect("changed::" + Settings.MEDIAPLAYER_STATUS_TEXT_KEY, Lang.bind(this, function(settings, key) { this._stateTemplate = settings.get_string(key); this._updatePanel(); }))); this._signalsId.push(this._settings.connect("changed::" + Settings.MEDIAPLAYER_STATUS_SIZE_KEY, Lang.bind(this, function(settings, key) { this._prefWidth = settings.get_int(key); this._updatePanel(); }))); this._signalsId.push(this._settings.connect("changed::" + Settings.MEDIAPLAYER_PLAY_STATE_ICON_KEY, Lang.bind(this, function(settings, key) { this._showPlayStateIcon = settings.get_boolean(key); this._updatePanel(); }))); }, _disconnectSignals: function() { for (let id in this._signalsId) { this._settings.disconnect(this._signalsId[id]); } this._signalsId = []; }, // method binded to classes below _commonOnActivePlayerUpdate: function() { this._updatePanel(); this._onActivePlayerUpdate(this.state); }, _updatePanel: function() { let state = this.state; if (state.status && this._showPlayStateIcon) { if (state.status == Settings.Status.PLAY) { this._secondaryIndicator.icon_name = "media-playback-start-symbolic"; } else if (state.status == Settings.Status.PAUSE) { this._secondaryIndicator.icon_name = "media-playback-pause-symbolic"; } else if (state.status == Settings.Status.STOP) { this._secondaryIndicator.icon_name = "media-playback-stop-symbolic"; } this._secondaryIndicator.show(); this._secondaryIndicator.set_width(-1); this.indicators.show(); } else { this._secondaryIndicator.hide(); } if(this._stateTemplate.length === 0 || state.status == Settings.Status.STOP) { this._thirdIndicator.clutter_text.set_markup(''); this._statusTextWidth = 0; this._stateText = ''; this._thirdIndicator.hide(); } else if (state.playerName || state.trackTitle || state.trackArtist || state.trackAlbum) { let stateText = this.compileTemplate(this._stateTemplate, state); if (this._stateText != stateText) { this._thirdIndicator.clutter_text.set_markup(stateText); this._thirdIndicator.set_width(-1); this._statusTextWidth = this._thirdIndicator.get_width(); } let desiredwidth = Math.min(this._prefWidth, this._statusTextWidth); let currentWidth = this._thirdIndicator.get_width(); if (currentWidth != desiredwidth) { this._thirdIndicator.set_width(desiredwidth); } this._thirdIndicator.show(); } if (state.trackCoverUrl || state.desktopEntry) { let fallbackIcon = this.getPlayerSymbolicIcon(state.desktopEntry, 'mpi-symbolic'); if (this._useCoverInPanel) { this.setCoverIconAsync(this._primaryIndicator, state.trackCoverUrl, fallbackIcon, true); } else if (this._primaryIndicator.icon_name != fallbackIcon) { this._primaryIndicator.icon_name = fallbackIcon; } } }, _commonOnActivePlayerRemove: function() { this._primaryIndicator.icon_name = 'audio-x-generic-symbolic'; this._thirdIndicator.clutter_text.set_markup(''); this._thirdIndicator.set_width(0); this._secondaryIndicator.set_width(0); this._thirdIndicator.hide(); this._secondaryIndicator.hide(); this._onActivePlayerRemove(); } }; var PanelIndicator = new Lang.Class({ Name: 'PanelIndicator', Extends: PanelMenu.Button, _init: function() { this.parent(0.0, "mediaplayer"); this._manager = null; this.actor.add_style_class_name('panel-status-button'); this.menu.actor.add_style_class_name('aggregate-menu panel-media-indicator'); this.compileTemplate = Util.compileTemplate; this.setCoverIconAsync = Util.setCoverIconAsync; this.getPlayerSymbolicIcon = Util.getPlayerSymbolicIcon; this._settings = Settings.gsettings; this._useCoverInPanel = this._settings.get_boolean(Settings.MEDIAPLAYER_COVER_STATUS_KEY); this._stateTemplate = this._settings.get_string(Settings.MEDIAPLAYER_STATUS_TEXT_KEY); this._prefWidth = this._settings.get_int(Settings.MEDIAPLAYER_STATUS_SIZE_KEY); this._showPlayStateIcon = this._settings.get_boolean(Settings.MEDIAPLAYER_PLAY_STATE_ICON_KEY); this._statusTextWidth = 0; this._stateText = ''; this._signalsId = []; this.indicators = new St.BoxLayout({vertical: false, style_class: 'system-status-icon'}); this._primaryIndicator = new St.Icon({icon_name: 'audio-x-generic-symbolic', style_class: 'system-status-icon no-padding'}); this._secondaryIndicator = new St.Icon({icon_name: 'media-playback-stop-symbolic', style_class: 'system-status-icon no-padding'}); this._secondaryIndicator.hide(); this._thirdIndicator = new St.Label({style_class: 'system-status-icon no-padding'}); this._thirdIndicator.clutter_text.ellipsize = Pango.EllipsizeMode.END; this._thirdIndicatorBin = new St.Bin({child: this._thirdIndicator}); this._thirdIndicator.hide(); this.indicators.add(this._primaryIndicator); this.indicators.add(this._secondaryIndicator); this.indicators.add(this._thirdIndicatorBin); this.actor.add_actor(this.indicators); this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); this.actor.hide(); }, // Override PanelMenu.Button._onEvent _onEvent: function(actor, event) { if (this._onButtonEvent(actor, event) == Clutter.EVENT_PROPAGATE) this.parent(actor, event); }, _onActivePlayerUpdate: function(state) { if (this.manager.activePlayer) { this.actor.show(); } }, _onActivePlayerRemove: function() { this.actor.hide(); } }); Util._extends(PanelIndicator, IndicatorMixin); var AggregateMenuIndicator = new Lang.Class({ Name: 'AggregateMenuIndicator', Extends: PanelMenu.SystemIndicator, _init: function() { this.parent(); this._manager = null; this.compileTemplate = Util.compileTemplate; this.setCoverIconAsync = Util.setCoverIconAsync; this.getPlayerSymbolicIcon = Util.getPlayerSymbolicIcon; this._settings = Settings.gsettings; this._useCoverInPanel = this._settings.get_boolean(Settings.MEDIAPLAYER_COVER_STATUS_KEY); this._stateTemplate = this._settings.get_string(Settings.MEDIAPLAYER_STATUS_TEXT_KEY); this._prefWidth = this._settings.get_int(Settings.MEDIAPLAYER_STATUS_SIZE_KEY); this._statusTextWidth = 0; this._stateText = ''; this._signalsId = []; this._primaryIndicator = this._addIndicator(); this._primaryIndicator.icon_name = 'audio-x-generic-symbolic'; this._primaryIndicator.style_class = 'system-status-icon no-padding'; this._secondaryIndicator = this._addIndicator(); this._secondaryIndicator.icon_name = 'media-playback-stop-symbolic'; this._secondaryIndicator.style_class = 'system-status-icon no-padding'; this._secondaryIndicator.hide(); this._thirdIndicator = new St.Label({style_class: 'system-status-icon no-padding'}); this._thirdIndicator.clutter_text.ellipsize = Pango.EllipsizeMode.END; this._thirdIndicator.hide(); this._thirdIndicatorBin = new St.Bin({child: this._thirdIndicator}); this.indicators.add_actor(this._thirdIndicatorBin); this.indicators.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); this.indicators.connect('button-press-event', Lang.bind(this, this._onButtonEvent)); this.indicators.hide(); this._settings.connect("changed::" + Settings.MEDIAPLAYER_HIDE_AGGINDICATOR_KEY, Lang.bind(this, function() { let alwaysHide = this._settings.get_boolean(Settings.MEDIAPLAYER_HIDE_AGGINDICATOR_KEY); if (alwaysHide) { this.indicators.hide(); } else if (this.manager.activePlayer && this.manager.activePlayer.state.status != Settings.Status.STOP) { this.indicators.show(); } })); }, _onActivePlayerUpdate: function(state) { let alwaysHide = this._settings.get_boolean(Settings.MEDIAPLAYER_HIDE_AGGINDICATOR_KEY); if (state.status && state.status === Settings.Status.STOP || alwaysHide) { this.indicators.hide(); } else if (state.status && !alwaysHide) { this.indicators.show(); } }, _onActivePlayerRemove: function() { this.indicators.hide(); } }); Util._extends(AggregateMenuIndicator, IndicatorMixin); gnome-shell-extensions-mediaplayer-3.5/src/player.js000066400000000000000000001174601317622025500227170ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /* global global: false */ /** 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, see . **/ 'use strict'; const Mainloop = imports.mainloop; const Gio = imports.gi.Gio; const Lang = imports.lang; const Shell = imports.gi.Shell; const Signals = imports.signals; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const _ = Gettext.gettext; const Me = imports.misc.extensionUtils.getCurrentExtension(); const DBusIface = Me.imports.dbus; const Settings = Me.imports.settings; const Util = Me.imports.util; var PlayerState = new Lang.Class({ Name: 'PlayerState', _init: function(params) { this.update(params || {}); }, update: function(state) { for (let key in state) { if (state[key] !== null) this[key] = state[key]; } }, playerName: null, desktopEntry: null, status: null, playlistObj: null, playlists: null, playlistCount: null, orderings: null, trackListMetaData: null, trackTime: null, trackDuration: null, trackPosition: null, trackTitle: null, trackAlbum: null, trackArtist: null, trackUrl: null, trackCoverUrl: null, trackLength: null, trackObj: null, trackRating: null, fallbackIcon: null, showPlaylist: null, showTracklist: null, showRating: null, showVolume: null, showPosition: null, hideStockMpris: null, buttonIconStyle: null, showStopButton: null, showLoopStatus: null, showPlayStatusIcon: null, showTracklistRating: null, updatedMetadata: null, updatedPlaylist: null, hasTrackList: null, canPlay: null, canPause: null, canSeek: null, canGoNext: null, canGoPrevious: null, volume: null, showPlaylistTitle: null, playlistTitle: null, getPlaylists: null, isRhythmboxStream: null, shuffle: null, loopStatus: null, timeFresh: null, emitSignal: null, }); var MPRISPlayer = new Lang.Class({ Name: 'MPRISPlayer', _init: function(busName, owner) { let baseName = busName.split('.')[3]; this.state = new PlayerState(); this.owner = owner; this.busName = busName; this.isClementine = this.busName == 'org.mpris.MediaPlayer2.clementine'; this.playerIsBroken = Settings.BROKEN_PLAYERS.indexOf(this.busName) != -1; this.noLoopStatusSupport = Settings.NO_LOOP_STATUS_SUPPORT.indexOf(this.busName) != -1; this.hasWrongVolumeScaling = Settings.WRONG_VOLUME_SCALING.indexOf(this.busName) != -1; this.app = null; // Guess the name based on the dbus path // Should be overriden by the Identity property this._identity = baseName.charAt(0).toUpperCase() + baseName.slice(1); this._trackTime = 0; this._wantedSeekValue = 0; this._timerId = 0; this._playlistTimeOutId = 0; this._tracklistTimeOutId = 0; this._settings = Settings.gsettings; this.parseMetadata = Util.parseMetadata; this._signalsId = []; this._tracklistSignalsId = []; this._trackIds = []; this._mediaServer = null; this._mediaServerPlayer = null; this._mediaServerPlaylists = null; this._mediaServerTracklist = null; this._prop = null; this._pithosRatings = null; this._ratingsExtension = null; new DBusIface.MediaServer2(busName, Lang.bind(this, function(proxy) { this._mediaServer = proxy; this._init2(); })); new DBusIface.MediaServer2Player(busName, Lang.bind(this, function(proxy) { this._mediaServerPlayer = proxy; this._init2(); })); new DBusIface.MediaServer2Playlists(busName, Lang.bind(this, function(proxy) { this._mediaServerPlaylists = proxy; this._init2(); })); new DBusIface.MediaServer2Tracklist(busName, Lang.bind(this, function(proxy) { this._mediaServerTracklist = proxy; this._init2(); })); new DBusIface.PithosRatings(busName, Lang.bind(this, function(proxy) { this._pithosRatings = proxy; this._init2(); })); new DBusIface.RatingsExtension(busName, Lang.bind(this, function(proxy) { this._ratingsExtension = proxy; this._init2(); })); new DBusIface.Properties(busName, Lang.bind(this, function(proxy) { this._prop = proxy; this._init2(); })); this.connect('update-player-state', Lang.bind(this, function(player, state) { //global.log(JSON.stringify(state)); this.state.update(state); if (state.status) this._onStatusChange(state); this.emit('player-update', state); })); }, _init2: function() { // Wait for all DBus callbacks to continue if (this._mediaServer !== null && this._mediaServerPlayer !== null && this._mediaServerPlaylists !== null && this._mediaServerTracklist !== null && this._pithosRatings !== null && this._ratingsExtension !== null && this._prop !== null) { this._init3(); } }, _init3: function() { if (Settings.MINOR_VERSION > 19) { // Versions before 3.20 don't have Mpris built-in. // hideStockMpris setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_HIDE_STOCK_MPRIS_KEY, Lang.bind(this, function(settings, key) { this.emit('update-player-state', new PlayerState({hideStockMpris: settings.get_boolean(key)})); })) ); } // showVolume setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_VOLUME_KEY, Lang.bind(this, function(settings, key) { if (this.state.showVolume !== this.showVolume) { if (this.showVolume) { let newState = new PlayerState(); this._refreshProperties(newState); } else { this.emit('update-player-state', new PlayerState({showVolume: false})); } } })) ); // showPosition setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_POSITION_KEY, Lang.bind(this, function(settings, key) { if (this.state.showPosition !== this.showPosition) { if (this.showPosition) { let newState = new PlayerState(); this.parseMetadata(this._mediaServerPlayer.Metadata, newState); newState.emitSignal = true; this._refreshProperties(newState); } else { this.emit('update-player-state', new PlayerState({showPosition: false})); } } })) ); // showRating setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_RATING_KEY, Lang.bind(this, function(settings, key) { if (this.state.showRating !== this.showRating) { this.emit('update-player-state', new PlayerState({showRating: this.showRating})); } })) ); // showPlayStatusIcon setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_PLAY_STATUS_ICON_KEY, Lang.bind(this, function(settings, key) { this.emit('update-player-state', new PlayerState({showPlayStatusIcon: settings.get_boolean(key)})); })) ); // showTracklistRating setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_TRACKLIST_RATING_KEY, Lang.bind(this, function(settings, key) { if (this.state.showTracklistRating !== this.showTracklistRating) { this.emit('update-player-state', new PlayerState({showTracklistRating: this.showTracklistRating})); } })) ); // showStopButton setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_STOP_BUTTON_KEY, Lang.bind(this, function(settings, key) { if (this.state.showStopButton !== this.showStopButton) { this.emit('update-player-state', new PlayerState({showStopButton: this.showStopButton})); } })) ); // showLoopStatus setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_LOOP_STATUS_KEY, Lang.bind(this, function(settings, key) { if (this.state.showLoopStatus !== this.showLoopStatus) { this.emit('update-player-state', new PlayerState({showLoopStatus: this.showLoopStatus})); } })) ); // player controls buttonIconStyle setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_BUTTON_ICON_STYLE_KEY, Lang.bind(this, function(settings, key) { this.emit('update-player-state', new PlayerState({buttonIconStyle: settings.get_enum(key)})); })) ); // showPlaylists setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_PLAYLISTS_KEY, Lang.bind(this, function(settings, key) { if (this.state.showPlaylist !== this.showPlaylist) { if (this.showPlaylist) { if (this.state.playlistCount > 0) { this._getPlaylists(this.state.orderings); } } else { this.emit('update-player-state', new PlayerState({showPlaylist: false})); } } })) ); // showPlaylistTitle setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_PLAYLIST_TITLE_KEY, Lang.bind(this, function(settings, key) { if (this.state.showPlaylistTitle !== this.showPlaylistTitle) { this.emit('update-player-state', new PlayerState({showPlaylistTitle: this.showPlaylistTitle})); } })) ); // showTracklist setting this._signalsId.push( this._settings.connect("changed::" + Settings.MEDIAPLAYER_TRACKLIST_KEY, Lang.bind(this, function(settings, key) { if (this.showTracklist && this.state.hasTrackList) { this._getTracklist(); } else { this.emit('update-player-state', new PlayerState({showTracklist: false})); } })) ); this._tracklistSignalsId.push( this._mediaServerTracklist.connectSignal('TrackListReplaced', Lang.bind(this, function(proxy, sender, [trackIds, currentTrackId]) { this._trackIds = this._checkTrackIds(trackIds); this._getTracklist(); })) ); this._tracklistSignalsId.push( this._mediaServerTracklist.connectSignal('TrackAdded', Lang.bind(this, function(proxy, sender, [trackMetadata, afterTrackId]) { let insertIndex = -1; if (afterTrackId === 'org/mpris/MediaPlayer2/TrackList/NoTrack') { insertIndex = 0; } else { let afterTrackIdIndex = this._trackIds.indexOf(afterTrackId); if (afterTrackIdIndex !== -1) { insertIndex = afterTrackIdIndex + 1; } } if (insertIndex !== -1) { let metadata = {}; this.parseMetadata(trackMetadata, metadata); if (metadata.trackObj !== 'org/mpris/MediaPlayer2/TrackList/NoTrack') { this._trackIds.splice(insertIndex, 0, metadata.trackObj); this._getTracklist(); } } })) ); this._tracklistSignalsId.push( this._mediaServerTracklist.connectSignal('TrackRemoved', Lang.bind(this, function(proxy, sender, [trackId]) { let removedTrackIndex = this._trackIds.indexOf(trackId); if (removedTrackIndex !== -1) { this._trackIds.splice(removedTrackIndex, 1); this._getTracklist(); } })) ); this._tracklistSignalsId.push( this._mediaServerTracklist.connectSignal('TrackMetadataChanged', Lang.bind(this, function(proxy, sender, [trackId, updatedMetadata]) { this.emit('update-player-state', new PlayerState({updatedMetadata: updatedMetadata})); })) ); this._playlistsId = this._mediaServerPlaylists.connectSignal('PlaylistChanged', Lang.bind(this, function(proxy, sender, [updatedPlaylist]) { this.emit('update-player-state', new PlayerState({updatedPlaylist: updatedPlaylist})); })); this._propChangedId = this._prop.connectSignal('PropertiesChanged', Lang.bind(this, function(proxy, sender, [iface, props]) { let newState = new PlayerState(); if (props.Volume) { let volume = props.Volume.unpack(); if (this.hasWrongVolumeScaling) { volume = Math.pow(volume, 1 / 3); } if (this.state.volume !== volume) { newState.volume = volume; newState.emitSignal = true; } } if (props.Shuffle) { let shuffle = props.Shuffle.unpack(); if (this.state.shuffle !== shuffle) { newState.shuffle = shuffle; newState.emitSignal = true; } } if (props.LoopStatus) { let loopStatus = props.LoopStatus.unpack(); if (this.state.loopStatus !== loopStatus) { newState.loopStatus = loopStatus; newState.emitSignal = true; } } if (props.CanPlay) { let canPlay = props.CanPlay.unpack(); if (this.state.canPlay !== canPlay) { newState.canPlay = canPlay; newState.emitSignal = true; } } if (props.CanPause) { let canPause = props.CanPause.unpack(); if (this.state.canPause !== canPause) { newState.canPause = canPause; newState.emitSignal = true; } } if (props.CanGoNext) { let canGoNext = props.CanGoNext.unpack(); if (this.state.canGoNext !== canGoNext) { newState.canGoNext = canGoNext; newState.emitSignal = true; } } if (props.CanGoPrevious) { let canGoPrevious = props.CanGoPrevious.unpack(); if (this.state.canGoPrevious !== canGoPrevious) { newState.canGoPrevious = canGoPrevious; newState.emitSignal = true; } } if (props.HasTrackList) { let hasTrackList = props.HasTrackList.unpack(); if (this.state.hasTrackList !== hasTrackList) { newState.hasTrackList = hasTrackList; newState.emitSignal = true; } } if (props.CanSeek) { let canSeek = props.CanSeek.unpack(); if (this.state.canSeek !== canSeek) { newState.canSeek = canSeek; newState.emitSignal = true; } } if (props.Orderings) { let orderings = this._checkOrderings(props.Orderings.deep_unpack()); if (JSON.stringify(orderings) != JSON.stringify(this.state.orderings)) { newState.orderings = orderings; newState.emitSignal = true; newState.getPlaylists = true; } } if (props.PlaylistCount) { let playlistCount = props.PlaylistCount.unpack(); if (this.state.playlistCount !== playlistCount) { newState.playlistCount = playlistCount; newState.emitSignal = true; if (playlistCount > 0) { newState.getPlaylists = true; } else { newState.getPlaylists = null; } } } if (props.ActivePlaylist) { let [playlistObj, playlistTitle] = props.ActivePlaylist.deep_unpack()[1]; if (this.state.playlistObj !== playlistObj) { newState.playlistObj = playlistObj; newState.emitSignal = true; } if (this.state.playlistTitle !== playlistTitle) { newState.playlistTitle = playlistTitle; newState.emitSignal = true; } } if (props.PlaybackStatus) { let status = props.PlaybackStatus.unpack(); if (this.state.status != status) { newState.status = status; newState.emitSignal = true; } } if (props.Metadata) { this.parseMetadata(props.Metadata.deep_unpack(), newState); newState.trackDuration = this._formatTime(newState.trackLength) newState.emitSignal = true; if (newState.trackUrl !== this.state.trackUrl || newState.trackObj !== this.state.trackObj) { this._refreshProperties(newState); } else { this.emit('update-player-state', newState); if (newState.getPlaylists) { let _orderings = newState.orderings || this.state.orderings; this._getPlaylists(_orderings); } } } else if (newState.emitSignal) { this.emit('update-player-state', newState); if (newState.getPlaylists) { let _orderings = newState.orderings || this.state.orderings; this._getPlaylists(_orderings); } } })); this._seekedId = this._mediaServerPlayer.connectSignal('Seeked', Lang.bind(this, function(proxy, sender, [value]) { if (value > 0) { this.trackTime = Math.round(value / 1000000); this._wantedSeekValue = 0; } // Banshee is buggy and always emits Seeked(0). See #34, #183, // also . else { // If we caused the seek, just use the expected position. // This is actually needed because even Get("Position") // sometimes returns 0 immediately after seeking! *grumble* if (this._wantedSeekValue > 0) { this.trackTime = this._wantedSeekValue / 1000000; this._wantedSeekValue = 0; } // If the seek was initiated by the player itself, query it // for the new position. else { let newState = new PlayerState(); this._refreshProperties(newState); } } })); if (this.desktopEntry) { let appSys = Shell.AppSystem.get_default(); this.app = appSys.lookup_app(this.desktopEntry + ".desktop"); } this.populate(); }, populate: function() { // The Tracks prop value is never updated so it's value is only good // for right after the player is created after that we rely on // the TrackListReplaced, TrackAdded, and TrackRemoved signals // to keep our trackIds current as per spec. this._trackIds = this._checkTrackIds(this._mediaServerTracklist.Tracks); let newState = new PlayerState({ canGoNext: this.canGoNext, canGoPrevious: this.canGoPrevious, canPlay: this.canPlay, canPause: this.canPause, canSeek: this.canSeek, hasTrackList: this.hasTrackList, volume: this.volume, status: this.playbackStatus, playerName: this.identity, desktopEntry: this.desktopEntry, playlistCount: this.playlistCount, orderings: this.orderings, loopStatus: this.loopStatus, shuffle: this.shuffle, showPlayStatusIcon: this.showPlayStatusIcon, showLoopStatus: this.showLoopStatus, showStopButton: this.showStopButton, buttonIconStyle: this.buttonIconStyle, showVolume: this.showVolume, showPosition: this.showPosition, showRating: this.showRating, showPlaylist: this.showPlaylist, showPlaylistTitle: this.showPlaylistTitle, showTracklist: this.showTracklist, showTracklistRating: this.showTracklistRating }); [newState.playlistObj, newState.playlistTitle] = this.activePlaylist; if (Settings.MINOR_VERSION > 19) { newState.hideStockMpris = this.hideStockMpris; } this.parseMetadata(this._mediaServerPlayer.Metadata, newState); //Delay calls 1 sec because some players make the interface available without data available in the beginning if (newState.playlistCount > 0 && newState.playlistTitle) { this._playlistTimeOutId = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() { this._playlistTimeOutId = 0; this._getPlaylists(this.state.orderings); return false; })); } else { newState.showPlaylist = false; } let isDummyTracklist = this._trackIds.length == 1 && this._trackIds[0] == '/org/mpris/MediaPlayer2/TrackList/NoTrack'; if (newState.hasTrackList && !isDummyTracklist) { this._tracklistTimeOutId = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() { this._tracklistTimeOutId = 0; this._getTracklist(); return false; })); } else { newState.showTracklist = false; } this.emit('update-player-state', newState); }, _checkTrackIds: function(trackIds) { if (!trackIds || !Array.isArray(trackIds)) { trackIds = []; } return trackIds; }, _checkOrderings: function(orderings) { if (!orderings || !Array.isArray(orderings) || orderings.length < 1) { orderings = ['Alphabetical']; } return orderings; }, set trackTime(value) { // Assume that if our trackTime is equal to or greater than // the trackLength the song must have started over. let trackLength = this.state.trackLength || 0; if (this._trackTime >= trackLength) { value = 0; } let newState = new PlayerState(); this._trackTime = value; newState.trackTime = value; newState.trackPosition = this._formatTime(value); this.emit('update-player-state', newState); }, get trackTime() { return this._trackTime; }, get canGoNext() { let canGoNext = this._mediaServerPlayer.CanGoNext; if (canGoNext === null) { canGoNext = true; } return canGoNext; }, get canGoPrevious() { let canGoPrevious = this._mediaServerPlayer.CanGoPrevious; if (canGoPrevious === null) { canGoPrevious = true; } return canGoPrevious; }, get canPlay() { let canPlay = this._mediaServerPlayer.CanPlay; if (canPlay === null) { canPlay = true; } return canPlay; }, get canPause() { let canPause = this._mediaServerPlayer.CanPause; if (canPause === null) { canPause = true; } return canPause; }, get canQuit() { return this._mediaServer.CanQuit || false; }, get canSeek() { return this._mediaServerPlayer.CanSeek || false; }, get hasTrackList() { return this._mediaServer.HasTrackList || false; }, get volume() { let volume = this._mediaServerPlayer.Volume; if (volume === null) { volume = 0.0; } else if (this.hasWrongVolumeScaling) { volume = Math.pow(volume, 1 / 3); } return volume; }, set volume(volume) { if (this.hasWrongVolumeScaling) { volume = Math.pow(volume, 3); } this._mediaServerPlayer.Volume = volume; }, get shuffle() { return this._mediaServerPlayer.Shuffle || false; }, set shuffle(shuffle) { if (this._mediaServerPlayer.Shuffle !== null) { this._mediaServerPlayer.Shuffle = shuffle; } }, get loopStatus() { return this._mediaServerPlayer.LoopStatus || 'None'; }, set loopStatus(loopStatus) { if (this._mediaServerPlayer.LoopStatus !== null) { this._mediaServerPlayer.LoopStatus = loopStatus; } }, get shouldShowLoopStatus() { return this._mediaServerPlayer.LoopStatus !== null && this._mediaServerPlayer.Shuffle !== null; }, get playbackStatus() { return this._mediaServerPlayer.PlaybackStatus || Settings.Status.STOP; }, get identity() { return this._mediaServer.Identity || this._identity; }, get desktopEntry() { return (this._mediaServer.DesktopEntry || ''); }, get activePlaylist() { let activePlaylist = this._mediaServerPlaylists.ActivePlaylist; if (activePlaylist === null || !activePlaylist || !activePlaylist[1]) { activePlaylist = [null, null]; } else { activePlaylist = activePlaylist[1]; } return activePlaylist; }, get playlistCount() { return this._mediaServerPlaylists.PlaylistCount || 0; }, get orderings() { return this._checkOrderings(this._mediaServerPlaylists.Orderings); }, get showPlayStatusIcon() { return this._settings.get_boolean(Settings.MEDIAPLAYER_PLAY_STATUS_ICON_KEY); }, get showLoopStatus() { return this._settings.get_boolean(Settings.MEDIAPLAYER_LOOP_STATUS_KEY) && this.shouldShowLoopStatus && !this.noLoopStatusSupport; }, get showStopButton() { return this._settings.get_boolean(Settings.MEDIAPLAYER_STOP_BUTTON_KEY) && !this.playerIsBroken; }, get buttonIconStyle() { return this._settings.get_enum(Settings.MEDIAPLAYER_BUTTON_ICON_STYLE_KEY); }, get showVolume() { return this._settings.get_boolean(Settings.MEDIAPLAYER_VOLUME_KEY) && !this.playerIsBroken; }, get showPosition() { return this._settings.get_boolean(Settings.MEDIAPLAYER_POSITION_KEY) && !this.playerIsBroken; }, get showRating() { return this._settings.get_boolean(Settings.MEDIAPLAYER_RATING_KEY) && !this.playerIsBroken; }, get showPlaylist() { return this._settings.get_boolean(Settings.MEDIAPLAYER_PLAYLISTS_KEY) && !this.playerIsBroken && !this.isClementine; }, get showPlaylistTitle() { return this._settings.get_boolean(Settings.MEDIAPLAYER_PLAYLIST_TITLE_KEY) && !this.playerIsBroken && !this.isClementine; }, get showTracklist() { return this._settings.get_boolean(Settings.MEDIAPLAYER_TRACKLIST_KEY) && !this.playerIsBroken && !this.isClementine; }, get showTracklistRating() { return this._settings.get_boolean(Settings.MEDIAPLAYER_TRACKLIST_RATING_KEY) && !this.playerIsBroken; }, get hideStockMpris() { return this._settings.get_boolean(Settings.MEDIAPLAYER_HIDE_STOCK_MPRIS_KEY); }, next: function() { this._mediaServerPlayer.NextRemote(); }, previous: function() { this._mediaServerPlayer.PreviousRemote(); }, playPause: function() { this._mediaServerPlayer.PlayPauseRemote(); }, stop: function() { this._mediaServerPlayer.StopRemote(); }, seek: function(value) { let time = value * this.state.trackLength; this._wantedSeekValue = Math.round(time * 1000000); this._mediaServerPlayer.SetPositionRemote(this.state.trackObj, this._wantedSeekValue); }, playPlaylist: function(playlistObj) { this._mediaServerPlaylists.ActivatePlaylistRemote(playlistObj); }, playTrack: function(track) { // GNOME Music crashes if you call the GoTo method. //https://bugzilla.gnome.org/show_bug.cgi?id=779052 if (this.busName !== 'org.mpris.MediaPlayer2.GnomeMusic') { this._mediaServerTracklist.GoToRemote(track); } }, raise: function() { if (this.app) { this.app.activate_full(-1, 0); } else if (this._mediaServer.CanRaise) { this._mediaServer.RaiseRemote(); } }, quit: function() { if (this.canQuit) { this._mediaServer.QuitRemote(); } }, _refreshProperties: function(newState) { // Check properties // Many players have a habit of changing properties without emitting // a PropertiesChanged signal as they should. This is basically CYA. // In a perfect world this would be redundant and unnecessary. this._prop.GetAllRemote('org.mpris.MediaPlayer2', Lang.bind(this, function([props], err) { if (!err) { if (newState.hasTrackList === null && props.HasTrackList) { let hasTrackList = props.HasTrackList.unpack(); if (this.state.hasTrackList !== hasTrackList) { newState.hasTrackList = hasTrackList; newState.emitSignal = true; } } } this._prop.GetAllRemote('org.mpris.MediaPlayer2.Player', Lang.bind(this, function([props], err) { if (!err) { if (newState.canPlay === null && props.CanPlay) { let canPlay = props.CanPlay.unpack(); if (this.state.canPlay !== canPlay) { newState.canPlay = canPlay; newState.emitSignal = true; } } if (newState.canPause === null && props.CanPause) { let canPause = props.CanPause.unpack(); if (this.state.canPause !== canPause) { newState.canPause = canPause; newState.emitSignal = true; } } if (newState.canGoNext === null && props.CanGoNext) { let canGoNext = props.CanGoNext.unpack(); if (this.state.canGoNext !== canGoNext) { newState.canGoNext = canGoNext; newState.emitSignal = true; } } if (newState.canGoPrevious === null && props.CanGoPrevious) { let canGoPrevious = props.CanGoPrevious.unpack(); if (this.state.canGoPrevious !== canGoPrevious) { newState.canGoPrevious = canGoPrevious; newState.emitSignal = true; } } if (newState.canSeek === null && props.CanSeek) { let canSeek = props.CanSeek.unpack(); if (this.state.canSeek !== canSeek) { newState.canSeek = canSeek; newState.emitSignal = true; } } if (newState.shuffle === null && props.Shuffle) { let shuffle = props.Shuffle.unpack(); if (this.state.shuffle !== shuffle) { newState.shuffle = shuffle; newState.emitSignal = true; } } if (newState.loopStatus === null && props.LoopStatus) { let loopStatus = props.LoopStatus.unpack(); if (this.state.loopStatus !== loopStatus) { newState.loopStatus = loopStatus; newState.emitSignal = true; } } if (newState.status === null && props.PlaybackStatus) { let status = props.PlaybackStatus.unpack(); if (this.state.status != status) { newState.status = status; newState.emitSignal = true; } } if (props.Volume) { let volume = props.Volume.unpack(); if (this.hasWrongVolumeScaling) { volume = Math.pow(volume, 1 / 3); } if (this.state.volume !== volume) { newState.volume = volume; newState.emitSignal = true; } if (this.state.showVolume == false && this.showVolume) { // Reenable showVolume after error newState.showVolume = true; newState.emitSignal = true; } } else if (this.state.showVolume) { newState.showVolume = false; newState.emitSignal = true; } if (props.Position) { let position = Math.round(props.Position.unpack() / 1000000); newState.timeFresh = true; if (this.trackTime !== position) { this._trackTime = position; newState.trackTime = position; newState.emitSignal = true; } if (this.state.showPosition == false && this.showPosition) { // Reenable showPosition after error newState.showPosition = true; newState.emitSignal = true; } } else if (this.state.showPosition) { newState.showPosition = false; newState.emitSignal = true; } } if (newState.emitSignal) { this.emit('update-player-state', newState); } })); })); }, _getPlaylists: function(orderings) { // A player may have trigger the fetching of a playlist // before our initial startup timeout happens. if (this._playlistTimeOutId !== 0) { Mainloop.source_remove(this._playlistTimeOutId); this._playlistTimeOutId = 0; } // Use Alphabetical as the playlist ordering // unless Alphabetical is not in the Orderings, // in that case use the 1st available ordering in the array. let ordering = "Alphabetical"; if (orderings.indexOf(ordering) === -1) ordering = orderings[0]; this._mediaServerPlaylists.GetPlaylistsRemote(0, 100, ordering, false, Lang.bind(this, function([playlists]) { if (playlists && Array.isArray(playlists)) { if (this.state.showPlaylist == false && this.showPlaylist) { //Reenable showPlaylist after error this.emit('update-player-state', new PlayerState({showPlaylist: true})); } this.emit('update-player-state', new PlayerState({playlists: playlists})); } else { this.emit('update-player-state', new PlayerState({showPlaylist: false})); } })); }, _getTracklist: function() { // A player may have trigger the fetching of a tracklist // before our initial startup timeout happens. if (this._tracklistTimeOutId !== 0) { Mainloop.source_remove(this._tracklistTimeOutId); this._tracklistTimeOutId = 0; } if (this._trackIds.length === 0) { this.emit('update-player-state', new PlayerState({showTracklist: false})); } else { this._mediaServerTracklist.GetTracksMetadataRemote(this._trackIds, Lang.bind(this, function([trackListMetaData]) { if (trackListMetaData && Array.isArray(trackListMetaData)) { if (this.state.showTracklist == false && this.showTracklist) { //Reenable showTracklist after error this.emit('update-player-state', new PlayerState({showTracklist: true})); } this.emit('update-player-state', new PlayerState({trackListMetaData: trackListMetaData})); } else { this.emit('update-player-state', new PlayerState({showTracklist: false})); } })); } }, _onStatusChange: function(newState) { // If the player is broken (Spotify you suck...) we'll never see the // position slider any way. No need to waste CPU cycles // on a timer... if (this.playerIsBroken) { return; } // sync track time // If the time is fresh we just came from a // properties refresh and don't need to do it again. if (!newState.timeFresh) { let newState = new PlayerState(); this._refreshProperties(newState); } if (this.state.status == Settings.Status.PLAY) { this._startTimer(); } else if (this.state.status == Settings.Status.PAUSE) { this._stopTimer(); } else if (this.state.status == Settings.Status.STOP) { this._stopTimer(); this.trackTime = 0; } }, _startTimer: function() { if (this._timerId === 0) { this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() { return this.trackTime += 1; })); } }, _stopTimer: function() { if (this._timerId !== 0) { Mainloop.source_remove(this._timerId); this._timerId = 0; } }, _formatTime: function(s) { if (Number.isNaN(s) || s < 0) { return '0:00' } let h = Math.floor(s / 3600); let m = Math.floor((s % 3600) / 60); s = s % 60; s = s < 10 ? '0' + s : s; m = m < 10 && h > 0 ? '0' + m + ':' : m + ':'; h = h > 0 ? h + ':' : ''; return h + m + s; }, destroy: function() { // Cancel all pending timeouts. this._stopTimer(); if (this._playlistTimeOutId !== 0) { Mainloop.source_remove(this._playlistTimeOutId); this._playlistTimeOutId = 0; } if (this._tracklistTimeOutId !== 0) { Mainloop.source_remove(this._tracklistTimeOutId); this._tracklistTimeOutId = 0; } // Disconnect all signals. if (this._propChangedId) { this._prop.disconnectSignal(this._propChangedId); } if (this._playlistsId) { this._mediaServerPlaylists.disconnectSignal(this._playlistsId); } if (this._seekedId) { this._mediaServerPlayer.disconnectSignal(this._seekedId); } for (let id in this._tracklistSignalsId) { if (id) { this._mediaServerTracklist.disconnectSignal(this._tracklistSignalsId[id]); } } for (let id in this._signalsId) { this._settings.disconnect(this._signalsId[id]); } }, toString: function() { return "".format(this.info.identity); } }); Signals.addSignalMethods(MPRISPlayer.prototype); gnome-shell-extensions-mediaplayer-3.5/src/prefs.js000066400000000000000000000500641317622025500225360ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* global imports: false */ /** 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, see . **/ 'use strict'; const Gtk = imports.gi.Gtk; const GObject = imports.gi.GObject; const Gio = imports.gi.Gio; const Lang = imports.lang; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const _ = Gettext.gettext; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Lib = Me.imports.lib; const Gsettings = Lib.getSettings(Me); const GNU_SOFTWARE = '' + 'This program comes with absolutely no warranty.\n' + 'See the ' + 'GNU General Public License, version 2 or later for details.' + ''; const CC_BY_SA = '' + 'All artwork released under the Creative Commons Attribution-ShareAlike 4.0 International license.\n' + 'See the ' + 'CC BY-SA 4.0 for details.' + ''; const Creators = [ {label: 'Jonas Wielicki', url: 'https://github.com/horazont'}, {label: 'Jean-Philippe Braun', url: 'https://github.com/eonpatapon'}, {label: 'Mantas Mikulėnas', url: 'https://github.com/grawity'}, {label: 'Jason Gray', url: 'https://github.com/JasonLG1979'}, {label: 'Bilal Elmoussaoui', url: 'https://github.com/bil-elmoussaoui'}, {label: 'Alexander Rüedlinger', url: 'https://github.com/lexruee'} ]; const Artists = [ {label: 'LinxGem33', url: 'https://github.com/LinxGem33'}, {label: 'Jason Gray', url: 'https://github.com/JasonLG1979'} ]; const Documenters = [ {label: 'Jean-Philippe Braun', url: 'https://github.com/eonpatapon'}, {label: 'Jason Gray', url: 'https://github.com/JasonLG1979'} ]; const Settings = { 'indicator-position': { type: "e", tab: "i", label: _("Indicator Position"), list: [ {nick: 'left', name: _("Left"), id: 3}, {nick: 'center', name: _("Center"), id: 0}, {nick: 'right', name: _("Right"), id: 1}, {nick: 'volume-menu', name: _("System menu"), id: 2} ] }, 'status-text': { type: "s", tab: "i", placeholder_text: "{trackArtist - }{trackTitle}", label: _("Indicator Status Text"), help: _("{playerName}: Player Name, {trackArtist}: Artist, {trackAlbum}: Album, {trackTitle}: Title. Pango markup supported.") }, 'status-size': { type: "i", tab: "i", label: _("Indicator Status Text Width"), help: _("The maximum width before the status text gets an ellipsis. Default is 300px."), min: 100, max: 900, step: 5, default: 300 }, 'play-state-icon': { type: "b", tab: "i", label: _("Show the Indicator Player State Icon") }, 'button-icon-style': { type: "e", tab: "p", label: _("Player Button Style"), list: [ {nick: 'circular', name: _("Circular"), id: 0}, {nick: 'small', name: _("Small"), id: 1}, {nick: 'medium', name: _("Medium"), id: 2}, {nick: 'large', name: _("Large"), id: 3} ] }, 'cover-status': { type: "b", tab: "i", label: _("Show the Current Song's Cover in the Panel"), help: _("If no cover is available the Media Player's symbolic icon is shown or a generic audio mime type icon.") }, 'playstatus': { type: "b", tab: "i", label: _("Show a Play Status Icon for each Media Player") }, 'hide-aggindicator': { type: "b", tab: "i", label: _("Always hide the Indicator in the System Menu") }, 'volume': { type: "b", tab: "v", label: _("Show the Media Player's Volume Slider") }, 'position': { type: "b", tab: "v", label: _("Show the Media Player's Position Slider") }, 'playlists': { type: "b", tab: "v", label: _("Show the Media Player's Playlists"), help: _("Few Media Players currently support the MPRIS Playlist Interface.") }, 'playlist-title': { type: "b", tab: "v", label: _("Show the Current Playlist Title in the main Trackbox"), help: _("Few Media Players currently support the MPRIS Playlist Interface.") }, 'tracklist': { type: "b", tab: "v", label: _("Show the Media Player's Tracklist"), help: _("Very few Media Players currently support the MPRIS Tracklist Interface.") }, 'rating': { type: "b", tab: "v", label: _("Display the Current Song's Rating"), }, 'tracklist-rating': { type: "b", tab: "v", label: _("Display Song Ratings in the Tracklist"), help: _("Very few Media Players currently support the MPRIS Tracklist Interface.") }, 'enable-scroll': { type: "b", tab: "i", label: _("Enable Indicator Scroll Events"), help: _("Enables track changes on scrolling the Indicator.") }, 'active-open': { type: "b", tab: "i", label: _("Always keep the Active Media Player Open"), help: _("Always keep the Active Media Player when you open the indicator or system menu.") }, 'stop-button': { type: "b", tab: "p", label: _("Always show a Stop Button in the Player Controls"), help: _("Otherwise a Stop Button is only shown if the Media Player is Playing but can't be Paused.") }, 'loop-status': { type: "b", tab: "p", label: _("Show Shuffle and Repeat Buttons in the Player Controls"), help: _("Very few Media players implement this correctly, if at all.") }, 'hide-stockmpris': { type: "b", tab: "i", label: _("Hide the built-in GNOME Shell MPRIS Controls") }, }; const Frame = new GObject.Class({ Name: 'Frame', GTypeName: 'Frame', Extends: Gtk.Box, _init: function(title) { this.parent({ orientation: Gtk.Orientation.VERTICAL, margin_bottom: 6, margin_start: 6, margin_end: 6, hexpand: true, vexpand: true }); } }); const Notebook = new GObject.Class({ Name: 'Notebook', GTypeName: 'Notebook', Extends: Gtk.Notebook, _init: function() { this.parent({ margin_top: 6, margin_bottom: 6, margin_start: 6, margin_end: 6, hexpand: true, vexpand: true }); }, append_page: function(notebookPage) { Gtk.Notebook.prototype.append_page.call( this, notebookPage, notebookPage.getTitleLabel() ); } }); const NotebookPage = new GObject.Class({ Name: 'NotebookPage', GTypeName: 'NotebookPage', Extends: Gtk.Box, _init: function(title) { this.parent({ orientation: Gtk.Orientation.VERTICAL, homogeneous: false }); this._title = new Gtk.Label({ label: title, }); }, getTitleLabel: function() { return this._title; }, addSettingsBox: function(settingsBox) { this.pack_start(settingsBox, false, false, 0); let sep = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL, margin_start: 6, margin_end: 6 }); this.add(sep); } }); const SettingsLabel = new GObject.Class({ Name: 'SettingsLabel', GTypeName: 'SettingsLabel', Extends: Gtk.Label, _init: function(label) { this.parent({ label: label, valign: Gtk.Align.CENTER, halign: Gtk.Align.START }); } }); const SettingsBox = new GObject.Class({ Name: 'SettingsBox', GTypeName: 'SettingsBox', Extends: Gtk.Box, _init: function(setting) { this.parent({ orientation: Gtk.Orientation.HORIZONTAL, margin_top: 6, margin_bottom: 6, margin_start: 12, margin_end: 12 }); let label = new SettingsLabel(Settings[setting].label); let toolTip = Settings[setting].help; if (toolTip) { this.set_tooltip_text(toolTip); } let widget; if (Settings[setting].type == 's') { widget = new SettingsEntry(setting); } if (Settings[setting].type == "i") { widget = new SettingsSpinButton(setting); } if (Settings[setting].type == "b") { widget = new SettingsSwitch(setting); } if (Settings[setting].type == "e") { widget = new SettingsCombo(setting); } this.pack_start(label, true, true, 0); this.pack_end(widget, true, true, 0); } }); const SettingsSwitch = new GObject.Class({ Name: 'SettingsSwitch', GTypeName: 'SettingsSwitch', Extends: Gtk.Switch, _init: function(setting) { let active = Gsettings.get_boolean(setting); this.parent({ valign: Gtk.Align.CENTER, halign: Gtk.Align.END, active: active }); Gsettings.bind( setting, this, 'active', Gio.SettingsBindFlags.DEFAULT ); } }); const SettingsCombo = new GObject.Class({ Name: 'SettingsCombo', GTypeName: 'SettingsCombo', Extends: Gtk.ComboBoxText, _init: function(setting) { this.parent({ valign: Gtk.Align.CENTER, halign: Gtk.Align.END }); Settings[setting].list.forEach(Lang.bind(this, function(item) { this.append(item.nick, item.name); if (item.id == Gsettings.get_enum(setting)) { this.set_active(item.id); } })); Gsettings.bind( setting, this, 'active-id', Gio.SettingsBindFlags.DEFAULT ); } }); const SettingsEntry = new GObject.Class({ Name: 'SettingsEntry', GTypeName: 'SettingsEntry', Extends: Gtk.Entry, _init: function(setting) { let text = Gsettings.get_string(setting); this.parent({ valign: Gtk.Align.CENTER, halign: Gtk.Align.END, width_chars: 30, text: text }); let placeholder_text = Settings[setting].placeholder_text; if (placeholder_text) { this.set_placeholder_text(placeholder_text); } Gsettings.bind( setting, this, 'text', Gio.SettingsBindFlags.DEFAULT ); } }); const SettingsSpinButton = new GObject.Class({ Name: 'SettingsSpinButton', GTypeName: 'SettingsSpinButton', Extends: Gtk.SpinButton, _init: function(setting) { let adjustment = new Gtk.Adjustment({ lower: Settings[setting].min, upper: Settings[setting].max, step_increment: Settings[setting].step }); let value = Gsettings.get_int(setting); this.parent({ valign: Gtk.Align.CENTER, halign: Gtk.Align.END, climb_rate: 1.0, snap_to_ticks: true, value: value, adjustment: adjustment }); Gsettings.bind( setting, this, 'value', Gio.SettingsBindFlags.DEFAULT ); } }); const CreditBox = new GObject.Class({ Name: 'CreditBox', GTypeName: 'CreditBox', Extends: Gtk.Box, _init: function() { this.parent({ orientation: Gtk.Orientation.VERTICAL, margin_top: 6, margin_bottom: 6, hexpand: true, vexpand: true }); let viewPort = new Gtk.Viewport({ shadow_type: Gtk.ShadowType.NONE, margin_start: 12, margin_end: 12, hexpand: true, vexpand: true }); let scrolledWindow = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER, shadow_type: Gtk.ShadowType.IN, margin_start: 12, margin_end: 12, hexpand: true, vexpand: true }); let innerCreditBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, margin_top: 0, margin_bottom: 0, margin_start: 0, margin_end: 0, hexpand: true, vexpand: true }); let creatorLabel = new Gtk.Label({ label: _("Created By"), halign: Gtk.Align.CENTER, margin_top: 0, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(creatorLabel); Creators.forEach(function(creator) { let creatorLinkButton = new Gtk.LinkButton({ label: creator.label, uri: creator.url, halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, margin_top: 0, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(creatorLinkButton); }); let artistLabel = new Gtk.Label({ label: _("Artwork By"), halign: Gtk.Align.CENTER, margin_top: 12, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(artistLabel); Artists.forEach(function(artist) { let artistLinkButton = new Gtk.LinkButton({ label: artist.label, uri: artist.url, halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, margin_top: 0, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(artistLinkButton); }); let documenterLabel = new Gtk.Label({ label: _("Documentation By"), halign: Gtk.Align.CENTER, margin_top: 12, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(documenterLabel); Documenters.forEach(function(documenter) { let documenterButton = new Gtk.LinkButton({ label: documenter.label, uri: documenter.url, halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, margin_top: 0, margin_bottom: 0, margin_start: 12, margin_end: 12, hexpand: true, vexpand: false }); innerCreditBox.add(documenterButton); }); viewPort.add(innerCreditBox); scrolledWindow.add(viewPort); this.add(scrolledWindow); } }); const AboutPage = new Lang.Class({ Name: 'AboutPage', Extends: NotebookPage, _init: function(settings) { this.parent(_('About')); let releaseVersion = Me.metadata['version'] ? _('Version ') + Me.metadata['version'] : 'git-master'; let projectName = Me.metadata['name']; let projectDescription = Me.metadata['description']; let projectUrl = Me.metadata['url']; let icon = new Gtk.Image({ icon_name: 'mpi-symbolic', pixel_size: 32, margin_top: 12, margin_start: 12, margin_end: 12, margin_bottom: 3 }); let nameLabel = new Gtk.Label({ label: '' + projectName + '', use_markup: true, margin_top: 3, margin_start: 12, margin_end: 12, margin_bottom: 3 }); let versionLabel = new Gtk.Label({ label: releaseVersion, margin_top: 3, margin_start: 12, margin_end: 12, margin_bottom: 3 }); let projectDescriptionLabel = new Gtk.Label({ label: projectDescription, margin_top: 3, margin_start: 12, margin_end: 12, margin_bottom: 3 }); let projectLinkButton = new Gtk.LinkButton({ label: _('Website'), uri: projectUrl, margin_top: 3, margin_start: 12, margin_end: 12, margin_bottom: 6 }); let creditLabel = new Gtk.Label({ label: '' + _("Credits") + '', use_markup: true, margin_start: 12, margin_end: 12 }); let gnuSofwareLabel = new Gtk.Label({ label: GNU_SOFTWARE, use_markup: true, justify: Gtk.Justification.CENTER, margin_top: 0, margin_start: 12, margin_end: 12, margin_bottom: 6 }); let cLabel = new Gtk.Label({ label: CC_BY_SA, use_markup: true, justify: Gtk.Justification.CENTER, margin_top: 0, margin_start: 12, margin_end: 12, margin_bottom: 12 }); this.add(icon); this.add(nameLabel); this.add(versionLabel); this.add(projectDescriptionLabel); this.add(projectLinkButton); this.add(creditLabel); this.add(new CreditBox()); this.add(gnuSofwareLabel); this.add(cLabel); } }); const PrefsWidget = new GObject.Class({ Name: 'PrefsWidget', GTypeName: 'PrefsWidget', Extends: Frame, _init: function() { this.parent(); this._notebook = new Notebook(); this._indicatorPage = new NotebookPage(_("Indicator")); this._notebook.append_page(this._indicatorPage); this._playerControlsPage = new NotebookPage(_("Player Controls")); this._notebook.append_page(this._playerControlsPage); this._visibleWidgetsPage = new NotebookPage(_("Visible Widgets")); this._notebook.append_page(this._visibleWidgetsPage); this._notebook.append_page(new AboutPage()); this.pack_start(this._notebook, true, true, 0); let settingsBox; for (let setting in Settings) { settingsBox = new SettingsBox(setting); if (Gtk.get_minor_version() < 20 && setting == "hide-stockmpris") { settingsBox.set_sensitive(false); } if (Settings[setting].tab == "i") { this._indicatorPage.addSettingsBox(settingsBox); } if (Settings[setting].tab == "p") { this._playerControlsPage.addSettingsBox(settingsBox); } if (Settings[setting].tab == "v") { this._visibleWidgetsPage.addSettingsBox(settingsBox); } } } }); function init() { Lib.initTranslations(Me); Lib.addIcon(Me); } function buildPrefsWidget() { let widget = new PrefsWidget(); widget.show_all(); return widget; } gnome-shell-extensions-mediaplayer-3.5/src/settings.js000066400000000000000000000060661317622025500232620ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /** 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, see . **/ 'use strict'; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Lib = Me.imports.lib; const Config = imports.misc.config; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const _ = Gettext.gettext; var MEDIAPLAYER_INDICATOR_POSITION_KEY = 'indicator-position'; var MEDIAPLAYER_COVER_STATUS_KEY = 'cover-status'; var MEDIAPLAYER_STATUS_TEXT_KEY = 'status-text'; var MEDIAPLAYER_STATUS_SIZE_KEY = 'status-size'; var MEDIAPLAYER_PLAY_STATE_ICON_KEY = 'play-state-icon'; var MEDIAPLAYER_VOLUME_KEY = 'volume'; var MEDIAPLAYER_HIDE_AGGINDICATOR_KEY = 'hide-aggindicator'; var MEDIAPLAYER_POSITION_KEY = 'position'; var MEDIAPLAYER_PLAYLISTS_KEY = 'playlists'; var MEDIAPLAYER_STOP_BUTTON_KEY = 'stop-button'; var MEDIAPLAYER_BUTTON_ICON_STYLE_KEY = 'button-icon-style'; var MEDIAPLAYER_PLAYLIST_TITLE_KEY = 'playlist-title'; var MEDIAPLAYER_TRACKLIST_KEY = 'tracklist'; var MEDIAPLAYER_TRACKLIST_RATING_KEY = 'tracklist-rating'; var MEDIAPLAYER_LOOP_STATUS_KEY = 'loop-status'; var MEDIAPLAYER_RATING_KEY = 'rating'; var MEDIAPLAYER_ENABLE_SCROLL_EVENTS_KEY = 'enable-scroll'; var MEDIAPLAYER_HIDE_STOCK_MPRIS_KEY = 'hide-stockmpris'; var MEDIAPLAYER_KEEP_ACTIVE_OPEN_KEY = 'active-open'; var MEDIAPLAYER_PLAY_STATUS_ICON_KEY = 'playstatus'; var MINOR_VERSION = parseInt(Config.PACKAGE_VERSION.split(".")[1]); var IndicatorPosition = { LEFT: 3, CENTER: 0, RIGHT: 1, VOLUMEMENU: 2 }; var ButtonIconStyles = { CIRCULAR: 0, SMALL: 1, MEDIUM: 2, LARGE: 3 }; var Status = { STOP: "Stopped", PLAY: "Playing", PAUSE: "Paused" }; var ValidPlaybackStatuses = [ 'Stopped', 'Playing', 'Paused' ]; var SUPPORTS_RATINGS_EXTENSION = [ "org.mpris.MediaPlayer2.Lollypop" ]; var WRONG_VOLUME_SCALING = [ "org.mpris.MediaPlayer2.quodlibet" ]; var ALTERNATIVE_PLAYLIST_TITLES = [ {"org.mpris.MediaPlayer2.pithos": _("Stations")} ]; var ALTERNATIVE_TRACKLIST_TITLES = [ {"org.mpris.MediaPlayer2.pithos": _("Current Playlist")} ]; var BROKEN_PLAYERS = [ "org.mpris.MediaPlayer2.spotify" ]; var NO_LOOP_STATUS_SUPPORT = [ "org.mpris.MediaPlayer2.quodlibet", "org.mpris.MediaPlayer2.pithos", "org.mpris.MediaPlayer2.spotify" ]; var gsettings; function init() { gsettings = Lib.getSettings(Me); } gnome-shell-extensions-mediaplayer-3.5/src/stylesheet.css000066400000000000000000000013211317622025500237540ustar00rootroot00000000000000.player-buttons { spacing: 2px; } .medium-player-button { padding: 0 6px; } .large-player-button { padding: 0 8px; } .panel-media-indicator { min-width: 288px; max-width: 288px; } .large-cover-icon { icon-size: 128px !important; } .small-cover-icon { icon-size: 48px !important; } .no-padding { padding: 0px; } .no-padding-top { padding-top: 0px; } .no-padding-bottom { padding-bottom: 0px; } .pithos-rating-box { spacing: 6px; } .track-info-artist { font-weight: bold; } .track-info-title { font-size: small; } .track-info-album { font-size: x-small; } .star-icon { padding-top: 0px; padding-bottom: 0px; padding-left: 0.5px; padding-right: 0.5px; } gnome-shell-extensions-mediaplayer-3.5/src/ui.js000066400000000000000000000450761317622025500220430ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /* global global: false */ /** 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, see . **/ 'use strict'; const Gio = imports.gi.Gio; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const St = imports.gi.St; const Main = imports.ui.main; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const _ = Gettext.gettext; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Widget = Me.imports.widget; const Settings = Me.imports.settings; const Util = Me.imports.util; var PlayerUI = new Lang.Class({ Name: 'PlayerUI', Extends: Widget.PlayerMenu, _init: function(player) { this.parent('', true); this.hidePlayStatusIcon(); this.player = player; this.setCoverIconAsync = Util.setCoverIconAsync; this._updateId = player.connect('player-update', Lang.bind(this, this.update)); this.oldShouldShow = null; this.playlistTitle = new Widget.PlaylistTitle(); this.playlistTitle.connect('activate', Lang.bind(this.player, this.player.raise)); this.addMenuItem(this.playlistTitle); this.playlistTitle.hide(); this.trackCover = new Widget.TrackCover(new St.Icon({icon_name: 'audio-x-generic-symbolic', style_class: 'large-cover-icon'})); this.trackCover.connect('activate', Lang.bind(this.player, this.player.raise)); this.addMenuItem(this.trackCover); this.trackCover.hide(); this.trackRatings = new Widget.TrackRating(this.player); this.trackRatings.connect('activate', Lang.bind(this.player, this.player.raise)); this.addMenuItem(this.trackRatings); this.trackRatings.hide(); this.info = new Widget.Info(); this.info.connect('activate', Lang.bind(this.player, this.player.raise)); this.addMenuItem(this.info); this.trackControls = new Widget.PlayerButtons(); this.trackControls.connect('activate', Lang.bind(this.player, this.player.raise)); this.prevButton = new Widget.PlayerButton('media-skip-backward-symbolic', Lang.bind(this.player, this.player.previous)); this.trackControls.addButton(this.prevButton); this.playButton = new Widget.PlayerButton('media-playback-start-symbolic', Lang.bind(this.player, this.player.playPause)); this.trackControls.addButton(this.playButton); this.stopButton = new Widget.PlayerButton('media-playback-stop-symbolic', Lang.bind(this.player, this.player.stop)); this.trackControls.addButton(this.stopButton); this.nextButton = new Widget.PlayerButton('media-skip-forward-symbolic', Lang.bind(this.player, this.player.next)); this.trackControls.addButton(this.nextButton); this.addMenuItem(this.trackControls); this.shuffleLoopStatus = new Widget.ShuffleLoopStatus(this.player); this.shuffleLoopStatus.connect('activate', Lang.bind(this.player, this.player.raise)); this.addMenuItem(this.shuffleLoopStatus); this.shuffleLoopStatus.hide(); this.position = new Widget.SliderItem('document-open-recent-symbolic'); this.position.connect('activate', Lang.bind(this.player, this.player.raise)); this.position.sliderConnect('value-changed', Lang.bind(this, function(item) { this.player.seek(item._value); })); this.addMenuItem(this.position); this.position.hide(); this.volume = new Widget.SliderItem('audio-volume-high-symbolic'); this.volume.connect('activate', Lang.bind(this.player, this.player.raise)); this.volume.sliderConnect('value-changed', Lang.bind(this, function(item) { if (this.player.volume != item._value) { this.player.volume = item._value; } })); this.addMenuItem(this.volume); this.volume.hide(); this.tracklist = this._createTracklistWidget(); this.addMenuItem(this.tracklist); this.tracklist.hide(); this.playlists = this._createPlaylistWidget(); this.addMenuItem(this.playlists); this.playlists.hide(); this.tracklist.menu.connect('open-state-changed', Lang.bind(this, function(menu, open) { if (open) { this.playlists.menu.close(); } })); this.playlists.menu.connect('open-state-changed', Lang.bind(this, function(menu, open) { if (open) { this.tracklist.menu.close(); } })); if (Settings.MINOR_VERSION > 19) { this.stockMpris = Main.panel.statusArea.dateMenu._messageList._mediaSection; //Monkey patch this.stockMprisOldShouldShow = this.stockMpris._shouldShow; } }, get state() { return this.player.state; }, update: function(player, newState) { if (newState.desktopEntry !== null) { this.icon.icon_name = Util.getPlayerSymbolicIcon(this.state.desktopEntry); } if (newState.playerName !== null) { this.label.text = this.state.playerName; } if (newState.hideStockMpris !== null) { if (this.stockMpris) { if (newState.hideStockMpris) { this.stockMpris._shouldShow = function() {return false;}; this.stockMpris.actor.hide(); } else { this.stockMpris._shouldShow = this.stockMprisOldShouldShow; if (this.stockMpris._shouldShow()) { this.stockMpris.actor.show(); } } } } if (newState.isRhythmboxStream !== null) { if (this.state.isRhythmboxStream) { this.trackCover.hideAnimate(); this.trackRatings.hideAnimate(); this.position.hideAnimate(); this.shuffleLoopStatus.hideAnimate(); } if (this.state.showLoopStatus && !this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.shuffleLoopStatus.showAnimate(); } } if (newState.showPlayStatusIcon !== null) { if (this.state.showPlayStatusIcon) { this.showPlayStatusIcon(); } else { this.hidePlayStatusIcon(); } } if (newState.showRating !== null) { if (this.player.showRating && this.state.showRating !== 'no rating' && !this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.trackRatings.showAnimate(); } else { this.trackRatings.hideAnimate(); } } if (newState.showVolume !== null) { if (this.state.showVolume) { this.volume.showAnimate(); } else { this.volume.hideAnimate(); } } if (newState.buttonIconStyle !== null) { this.prevButton.setIconSize(this.state.buttonIconStyle); this.playButton.setIconSize(this.state.buttonIconStyle); this.stopButton.setIconSize(this.state.buttonIconStyle); this.nextButton.setIconSize(this.state.buttonIconStyle); } if (newState.shuffle !== null) { this.shuffleLoopStatus.setShuffle(this.state.shuffle); } if (newState.loopStatus !== null) { this.shuffleLoopStatus.setLoopStaus(this.state.loopStatus); } if (newState.showLoopStatus !== null) { if (!this.state.showLoopStatus) { this.shuffleLoopStatus.hideAnimate(); } if (this.state.showLoopStatus && !this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.shuffleLoopStatus.showAnimate(); } } if (newState.showPlaylistTitle !== null) { if (this.state.showPlaylistTitle && this.state.playlistTitle) { this.playlistTitle.showAnimate(); } else { this.playlistTitle.hideAnimate(); } } if (newState.playlistTitle !== null) { if (this.state.showPlaylistTitle && this.state.playlistTitle) { this.playlistTitle.update(this.state.playlistTitle); this.playlistTitle.showAnimate(); } else { this.playlistTitle.hideAnimate(); } } if (newState.showPosition !== null) { if (this.state.showPosition && this.state.trackLength !== 0 && this.state.status !== Settings.Status.STOP && !this.state.isRhythmboxStream) { this.position.showAnimate(); } else { this.position.hideAnimate(); } } if (newState.playlistCount !== null) { if (this.state.showPlaylist && this.state.playlistCount > 0) { this.playlists.showAnimate(); } else { this.playlists.hideAnimate(); } } if (newState.showPlaylist !== null) { if (this.state.showPlaylist && this.state.playlistCount > 0) { this.playlists.showAnimate(); } else { this.playlists.hideAnimate(); } } if (newState.showTracklist !== null) { if (this.state.hasTrackList && this.state.showTracklist) { this.tracklist.showAnimate(); } else { this.tracklist.hideAnimate(); } } if (newState.hasTrackList !== null) { if (this.state.hasTrackList && this.state.showTracklist) { this.tracklist.showAnimate(); } else { this.tracklist.hideAnimate(); } } if (newState.trackRating !== null) { if (this.state.trackRating !== 'no rating') { this.trackRatings.rate(this.state.trackRating); if (this.state.showRating && !this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.trackRatings.showAnimate(); } } else { let dummyRating = this.player._pithosRatings ? '' : 0; this.trackRatings.rate(dummyRating); this.trackRatings.hideAnimate(); } } if (newState.trackArtist !== null) { this.info.update(this.state); } if (newState.volume !== null) { // Adapted from https://github.com/GNOME/gnome-shell/blob/master/js/ui/status/volume.js // So that our icon changes match the system volume icon changes. let value = this.state.volume, volumeIcon; if (value === 0) { volumeIcon = 'audio-volume-muted-symbolic'; } else { let n = Math.floor(3 * value) + 1; if (n < 2) { volumeIcon = 'audio-volume-low-symbolic'; } else if (n >= 3) { volumeIcon = 'audio-volume-high-symbolic'; } else { volumeIcon = 'audio-volume-medium-symbolic'; } } this.volume.setIcon(volumeIcon); this.volume.setValue(value); } if (newState.canGoNext !== null) { if (this.state.canGoNext) { this.nextButton.enable(); } else { this.nextButton.disable(); } if (!this.state.canGoNext && !this.state.canGoPrevious && !this.state.canPlay && !this.state.canPause) { this.stopButton.disable(); } else { this.stopButton.enable(); } } if (newState.canGoPrevious !== null) { if (this.state.canGoPrevious) { this.prevButton.enable(); } else { this.prevButton.disable(); } if (!this.state.canGoNext && !this.state.canGoPrevious && !this.state.canPlay && !this.state.canPause) { this.stopButton.disable(); } else { this.stopButton.enable(); } } if (newState.canSeek !== null) { this.position.setReactive(this.state.canSeek); } if (newState.canPlay !== null) { if (this.state.status !== Settings.Status.PLAY) { if (this.state.canPlay) { this.playButton.enable(); } else { this.playButton.disable(); } } if (!this.state.canGoNext && !this.state.canGoPrevious && !this.state.canPlay && !this.state.canPause) { this.stopButton.disable(); } else { this.stopButton.enable(); } } if (newState.canPause !== null) { if (this.state.status === Settings.Status.PLAY) { if (this.state.canPause) { this.playButton.enable(); if (!this.state.showStopButton) { this.stopButton.hide(); } } else if (!this.player.playerIsBroken) { // If we're playing, we can't pause, and the player isn't broken // we should show the stop button no matter what. this.stopButton.show(); this.playButton.disable(); } } if (!this.state.canGoNext && !this.state.canGoPrevious && !this.state.canPlay && !this.state.canPause) { this.stopButton.disable(); } else { this.stopButton.enable(); } } if (newState.trackTime !== null) { if (this.state.trackLength === 0) { this.position.hideAnimate(); } else if (this.state.status !== Settings.Status.STOP && this.state.showPosition && !this.state.isRhythmboxStream) { this.position.setValue(this.state.trackTime / this.state.trackLength); this.position.showAnimate(); } } if (newState.status !== null) { if (this.state.status === Settings.Status.PLAY) { this.setPlayStatusIcon('media-playback-start-symbolic'); this.playButton.setIcon('media-playback-pause-symbolic'); if (this.state.canPause) { this.playButton.enable(); if (!this.showStopButton) { this.stopButton.hide(); } } else if (!this.player.playerIsBroken) { // If we're playing, we can't pause, and the player isn't broken // we should show the stop button no matter what. this.stopButton.show(); this.playButton.disable(); } } else { this.playButton.setIcon('media-playback-start-symbolic'); if (this.state.canPlay) { this.playButton.enable(); } else { this.playButton.disable(); } } if (this.state.status === Settings.Status.STOP) { this.setPlayStatusIcon('media-playback-stop-symbolic'); this.stopButton.hide(); this.position.hideAnimate(); this.shuffleLoopStatus.hideAnimate(); this.trackRatings.hideAnimate(); this.info.hideAnimate(); this.trackCover.hideAnimate(); } else { if (this.state.showStopButton) { this.stopButton.show(); } if (!this.state.isRhythmboxStream) { this.trackCover.showAnimate(); } if (this.state.showRating && !this.state.isRhythmboxStream && this.state.trackRating !== 'no rating') { this.trackRatings.showAnimate(); } this.info.showAnimate(); if (this.state.showPosition && this.state.trackLength !== 0 && !this.state.isRhythmboxStream) { this.position.showAnimate(); } } if (this.state.status === Settings.Status.PAUSE) { this.setPlayStatusIcon('media-playback-pause-symbolic'); } if (this.state.showLoopStatus && !this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.shuffleLoopStatus.showAnimate(); } } if (newState.showStopButton !== null) { if (this.state.showStopButton && this.state.status !== Settings.Status.STOP) { this.stopButton.show(); } else if (this.state.status === Settings.Status.PLAY && this.state.canPause) { this.stopButton.hide(); } } if (newState.trackCoverUrl !== null) { if (!this.state.isRhythmboxStream && this.state.status !== Settings.Status.STOP) { this.trackCover.showAnimate(); } this.setCoverIconAsync(this.trackCover.icon, this.state.trackCoverUrl, '', this.player.isClementine, this.trackCover.animating); } if (newState.playlists !== null) { this.playlists.loadPlaylists(this.state.playlists); } if (newState.trackListMetaData !== null) { this.tracklist.loadTracklist(this.state.trackListMetaData, this.state.showTracklistRating); } if (newState.showTracklistRating !== null) { this.tracklist.showRatings(this.state.showTracklistRating); } if (newState.playlistObj !== null) { this.playlists.setObjectActive(this.state.playlistObj); } if (newState.updatedPlaylist !== null) { let [playlistObj, playlistTitle] = this.state.updatedPlaylist; if (playlistObj == this.playlists.activeObject) { this.playlistTitle.update(playlistTitle); } this.playlists.updatePlaylist(this.state.updatedPlaylist); } if (newState.trackObj !== null) { this.tracklist.setObjectActive(this.state.trackObj); } if (newState.updatedMetadata !== null) { this.tracklist.updateMetadata(this.state.updatedMetadata); } }, _createPlaylistWidget: function() { let playlistTitle = _("Playlists"); let altPlaylistTitles = Settings.ALTERNATIVE_PLAYLIST_TITLES; for (let i = 0; i < altPlaylistTitles.length; i++) { let obj = altPlaylistTitles[i]; for (let key in obj){ if (key == this.player.busName) { playlistTitle = obj[key]; break; } } } return new Widget.Playlists(playlistTitle, this.player); }, _createTracklistWidget: function() { let tracklistTitle = _("Tracks"); let altTracklistTitles = Settings.ALTERNATIVE_TRACKLIST_TITLES; for (let i = 0; i < altTracklistTitles.length; i++) { let obj = altTracklistTitles[i]; for (let key in obj){ if (key == this.player.busName) { tracklistTitle = obj[key]; break; } } } return new Widget.TrackList(tracklistTitle, this.player); }, toString: function() { return '[object PlayerUI(%s)]'.format(this.player.busName); }, destroy: function() { if (this._updateId) { this.player.disconnect(this._updateId); } this.parent(); } }); gnome-shell-extensions-mediaplayer-3.5/src/util.js000066400000000000000000000127711317622025500223770ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* global imports: false */ /** 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, see . **/ const Mainloop = imports.mainloop; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; const Pango = imports.gi.Pango; const Tweener = imports.ui.tweener; function setCoverIconAsync(icon, coverUrl, fallback_icon_name, dontAnimate, delay) { fallback_icon_name = (fallback_icon_name || 'audio-x-generic-symbolic'); if (coverUrl) { let file = Gio.File.new_for_uri(coverUrl); file.load_contents_async(null, function(source, result) { try { let bytes = source.load_contents_finish(result)[1]; let newIcon = Gio.BytesIcon.new(bytes); if (!newIcon.equal(icon.gicon)) { if (dontAnimate) { icon.gicon = newIcon; } else if (delay) { Mainloop.timeout_add(250, function() { animateChange(icon, 'gicon', newIcon); return false; }); } else { animateChange(icon, 'gicon', newIcon); } } } catch(err) { if (icon.icon_name != fallback_icon_name) { if (dontAnimate) { icon.icon_name = fallback_icon_name; } else if (delay) { Mainloop.timeout_add(250, function() { animateChange(icon, 'icon_name', fallback_icon_name); return false; }); } else { animateChange(icon, 'icon_name', fallback_icon_name); } } } }); } else if (icon.icon_name != fallback_icon_name) { if (dontAnimate) { icon.icon_name = fallback_icon_name; } else { animateChange(icon, 'icon_name', fallback_icon_name); } } } function animateChange(actor, prop, value) { Tweener.addTween(actor, { opacity: 0, time: 0.125, onComplete: function() { actor[prop] = value; Tweener.addTween(actor, { opacity: 255, time: 0.125 }); } }); } function getPlayerSymbolicIcon(desktopEntry, fallback) { if (desktopEntry) { //The Player's symbolic Icon name *should* be it's //Desktop Entry + '-symbolic'. //For example, Pithos: //Desktop Entry - 'io.github.Pithos' //Symbolic Icon Name - 'io.github.Pithos-symbolic' let possibleIconName = desktopEntry + '-symbolic'; let currentIconTheme = Gtk.IconTheme.get_default(); let IconExists = currentIconTheme.has_icon(possibleIconName); if (IconExists) { return possibleIconName; } } return fallback || 'audio-x-generic-symbolic'; } function parseMetadata(metadata, state) { // Pragha sends a metadata dict with one value on stop if (!metadata || Object.keys(metadata).length < 2) { metadata = {}; } state.trackUrl = metadata["xesam:url"] ? metadata["xesam:url"].unpack() : ""; state.trackArtist = metadata["xesam:artist"] ? metadata["xesam:artist"].deep_unpack().join('/') : ""; state.trackArtist = metadata["rhythmbox:streamTitle"] ? metadata["rhythmbox:streamTitle"].unpack() : state.trackArtist; state.trackAlbum = metadata["xesam:album"] ? metadata["xesam:album"].unpack() : ""; state.trackTitle = metadata["xesam:title"] ? metadata["xesam:title"].unpack() : ""; state.trackLength = metadata["mpris:length"] ? Math.round(metadata["mpris:length"].unpack() / 1000000) : 0; state.trackObj = metadata["mpris:trackid"] ? metadata["mpris:trackid"].unpack() : "/org/mpris/MediaPlayer2/TrackList/NoTrack"; state.trackCoverUrl = metadata["mpris:artUrl"] ? metadata["mpris:artUrl"].unpack() : ""; state.trackRating = metadata["xesam:userRating"] ? parseInt(metadata["xesam:userRating"].unpack() * 5) : 'no rating'; state.trackRating = metadata["pithos:rating"] ? metadata["pithos:rating"].unpack() : state.trackRating; state.isRhythmboxStream = metadata["rhythmbox:streamTitle"] ? true : false; } var compileTemplate = function(template, playerState) { let escapedText = template.replace(/{(\w+)\|?([^}]*)}/g, function(match, fieldName, appendText) { let text = ""; if (playerState[fieldName] && playerState[fieldName].toString() !== "") { text = playerState[fieldName].toString() + appendText; text = GLib.markup_escape_text(text, -1); } return text; }); //Validate Pango markup. try { let validMarkup = Pango.parse_markup(escapedText, -1, '')[0]; if (!validMarkup) { escapedText = 'Invalid Syntax'; } } catch(err) { escapedText = 'Invalid Syntax'; } return escapedText; }; var _extends = function(object1, object2) { Object.getOwnPropertyNames(object2).forEach(function(name, index) { let desc = Object.getOwnPropertyDescriptor(object2, name); if (! desc.writable) Object.defineProperty(object1.prototype, name, desc); else { object1.prototype[name] = object2[name]; } }); }; gnome-shell-extensions-mediaplayer-3.5/src/widget.js000066400000000000000000001215751317622025500227100ustar00rootroot00000000000000/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* jshint esnext: true */ /* jshint -W097 */ /* jshint multistr: true */ /* global imports: false */ /** 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, see . **/ 'use strict'; const Mainloop = imports.mainloop; const St = imports.gi.St; const Atk = imports.gi.Atk; const BoxPointer = imports.ui.boxpointer; const PopupMenu = imports.ui.popupMenu; const Slider = imports.ui.slider; const GLib = imports.gi.GLib; const Clutter = imports.gi.Clutter; const Gtk = imports.gi.Gtk; const Gio = imports.gi.Gio; const Lang = imports.lang; const Tweener = imports.ui.tweener; const Gettext = imports.gettext.domain('gnome-shell-extensions-mediaplayer'); const _ = Gettext.gettext; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Settings = Me.imports.settings; const Util = Me.imports.util; const DBusIface = Me.imports.dbus; var SubMenu = new Lang.Class({ Name: 'SubMenu', Extends: PopupMenu.PopupMenuBase, _init: function(sourceActor, sourceArrow, isPlayerMenu) { this.parent(sourceActor); this._isPlayerMenu = isPlayerMenu; this._arrow = sourceArrow; this.actor = new St.ScrollView({style_class: 'popup-sub-menu', hscrollbar_policy: Gtk.PolicyType.NEVER, vscrollbar_policy: Gtk.PolicyType.NEVER}); this.actor.add_actor(this.box); this.actor._delegate = this; this.actor.clip_to_allocation = true; this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); this.actor.hide(); }, _needsScrollbar: function() { let topMenu = this._getTopMenu(); let [topMinHeight, topNaturalHeight] = topMenu.actor.get_preferred_height(-1); let topThemeNode = topMenu.actor.get_theme_node(); let topMaxHeight = topThemeNode.get_max_height(); return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight; }, getSensitive: function() { return this._sensitive && this.sourceActor._delegate.getSensitive(); }, open: function() { if (this.isOpen || this.isEmpty()) return; this.isOpen = true; this.emit('open-state-changed', true); this.actor.show(); let needsScrollbar = this._needsScrollbar(); if (needsScrollbar && !this._isPlayerMenu) { this.actor.vscrollbar_policy = Gtk.PolicyType.ALWAYS; this.actor.add_style_pseudo_class('scrolled'); } else { this.actor.vscrollbar_policy = Gtk.PolicyType.NEVER; this.actor.remove_style_pseudo_class('scrolled'); } this._arrow.rotation_angle_z = this.actor.text_direction == Clutter.TextDirection.RTL ? -90 : 90; }, close: function() { if (!this.isOpen || this.isEmpty()) return; this.isOpen = false; this.emit('open-state-changed', false); if (this._activeMenuItem) this._activeMenuItem.setActive(false); this._arrow.rotation_angle_z = 0; this.actor.hide(); }, _onKeyPressEvent: function(actor, event) { // Move focus back to parent menu if the user types Left. if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) { this.close(BoxPointer.PopupAnimation.FULL); this.sourceActor._delegate.setActive(true); return Clutter.EVENT_STOP; } return Clutter.EVENT_PROPAGATE; } }); var PlayerMenu = new Lang.Class({ Name: 'PlayerMenu', Extends: PopupMenu.PopupSubMenuMenuItem, _init: function(label, wantIcon) { this.parent(label, wantIcon); this._playStatusIcon = new St.Icon({style_class: 'popup-menu-icon'}); this.actor.insert_child_at_index(this._playStatusIcon, 3); this.menu = new SubMenu(this.actor, this._triangle, true); this.menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); }, addMenuItem: function(item) { this.menu.addMenuItem(item); }, setPlayStatusIcon: function(icon) { this._playStatusIcon.icon_name = icon; }, hidePlayStatusIcon: function() { this._playStatusIcon.hide(); }, showPlayStatusIcon: function() { this._playStatusIcon.show(); } }); var BaseContainer = new Lang.Class({ Name: "BaseContainer", Extends: PopupMenu.PopupBaseMenuItem, _init: function(parms) { this.parent(parms); this._hidden = false; this._animating = false; //We don't want our BaseContainers to be highlighted when clicked, //they're not really menu items in the traditional sense. //We want to maintain the illusion that they are normal UI containers, //and that our main track UI area is one big container. this.actor.add_style_pseudo_class = function() {return null;}; }, get hidden() { return this._hidden; }, set hidden(value) { this._hidden = value; }, get animating() { return this._animating; }, set animating(value) { this._animating = value; }, hide: function() { this.actor.hide(); this.actor.opacity = 0; this.actor.set_height(0); this.hidden = true; }, show: function() { this.actor.show(); this.actor.opacity = 255; this.actor.set_height(-1); this.hidden = false; }, showAnimate: function() { if (!this.actor.get_stage() || !this._hidden || this.animating) { return; } this.animating = true; this.actor.set_height(-1); let [minHeight, naturalHeight] = this.actor.get_preferred_height(-1); this.actor.set_height(0); this.actor.show(); Tweener.addTween(this.actor, { opacity: 255, height: naturalHeight, time: 0.25, onComplete: function() { this.show(); this.animating = false; }, onCompleteScope: this }); }, hideAnimate: function() { if (!this.actor.get_stage() || this._hidden || this.animating) { return; } this.animating = true; Tweener.addTween(this.actor, { opacity: 0, height: 0, time: 0.25, onComplete: function() { this.hide(); this.animating = false; }, onCompleteScope: this }); } }); var PlayerButtons = new Lang.Class({ Name: 'PlayerButtons', Extends: BaseContainer, _init: function() { this.parent({hover: false}); this.box = new St.BoxLayout({style_class: 'no-padding-bottom player-buttons'}); this.actor.add(this.box, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); }, addButton: function(button) { this.box.add(button.actor, {expand: false}); } }); var ShuffleLoopStatus = new Lang.Class({ Name: 'PlayerButtons', Extends: BaseContainer, _init: function(player) { this.parent({hover: false}); this._player = player; this.box = new St.BoxLayout({style_class: 'no-padding-bottom no-padding-top'}); this.actor.add(this.box, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); let shuffleIcon = new St.Icon({icon_name: 'media-playlist-shuffle-symbolic', style_class: 'popup-menu-icon'}); this._shuffleButton = new St.Button({child: shuffleIcon}); this._setButtonActive(this._shuffleButton, false); this._shuffleButton.connect('notify::hover', Lang.bind(this, this._onButtonHover)); this._shuffleButton.connect('clicked', Lang.bind(this, function() { this._player.shuffle = this._player.state.shuffle ? false : true; })); this.box.add(this._shuffleButton); let repeatIcon = new St.Icon({icon_name: 'media-playlist-repeat-song-symbolic', style_class: 'popup-menu-icon'}); this._repeatButton = new St.Button({child: repeatIcon}); this._setButtonActive(this._repeatButton, false); this._repeatButton.connect('notify::hover', Lang.bind(this, this._onButtonHover)); this._repeatButton.connect('clicked', Lang.bind(this, function() { this._player.loopStatus = this._player.state.loopStatus == 'Track' ? 'None' : 'Track'; })); this.box.add(this._repeatButton); let repeatAllIcon = new St.Icon({icon_name: 'media-playlist-repeat-symbolic', style_class: 'popup-menu-icon'}); this._repeatAllButton = new St.Button({child: repeatAllIcon}); this._setButtonActive(this._repeatAllButton, false); this._repeatAllButton.connect('notify::hover', Lang.bind(this, this._onButtonHover)); this._repeatAllButton.connect('clicked', Lang.bind(this, function() { this._player.loopStatus = this._player.state.loopStatus == 'Playlist' ? 'None' : 'Playlist'; })); this.box.add(this._repeatAllButton); }, setLoopStaus: function (loopStatus) { if (loopStatus == 'None') { this._setButtonActive(this._repeatButton, false); this._setButtonActive(this._repeatAllButton, false); } else if (loopStatus == 'Track') { this._setButtonActive(this._repeatButton, true); this._setButtonActive(this._repeatAllButton, false); } else if (loopStatus == 'Playlist') { this._setButtonActive(this._repeatButton, false); this._setButtonActive(this._repeatAllButton, true); } }, setShuffle: function (shuffle) { this._setButtonActive(this._shuffleButton, shuffle); }, _setButtonActive: function (button, active) { button._isActive = active; button.opacity = active ? 204 : 102; }, _onButtonHover: function (button) { button.opacity = button.hover ? 255 : button._isActive ? 204 : 102; } }); var PlaylistTitle = new Lang.Class({ Name: 'PlaylistTitle', Extends: BaseContainer, _init: function () { this.parent({hover: false, style_class: 'no-padding-bottom'}); this._label = new St.Label({style_class: 'track-info-artist'}); this.actor.add(this._label, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); }, update: function(name) { if (name && this._label.text != name) { this._label.text = name; } } }); var PlayerButton = new Lang.Class({ Name: "PlayerButton", _init: function(icon, callback) { this.actor = new St.Button({child: new St.Icon({icon_name: icon})}); this.actor.opacity = 204; this.actor._delegate = this; this.actor.connect('clicked', callback); this.actor.connect('notify::hover', Lang.bind(this, function(button) { this.actor.opacity = button.hover ? 255 : 204; })); }, setIcon: function(icon) { this.actor.child.icon_name = icon; }, setIconSize: function(style) { if (style == Settings.ButtonIconStyles.CIRCULAR) { this.actor.child.style_class = null; this.actor.style_class = 'system-menu-action'; } else if (style == Settings.ButtonIconStyles.SMALL) { this.actor.style_class = null; this.actor.child.style_class = 'popup-menu-icon'; } else if (style == Settings.ButtonIconStyles.MEDIUM) { this.actor.style_class = null; this.actor.child.style_class = 'nm-dialog-header-icon medium-player-button'; } else if (style == Settings.ButtonIconStyles.LARGE) { this.actor.style_class = null; this.actor.child.style_class = 'shell-mount-operation-icon large-player-button'; } }, enable: function() { this.actor.reactive = true; this.actor.opacity = 204; }, disable: function() { this.actor.reactive = false; this.actor.opacity = 102; }, hide: function() { this.actor.hide(); }, show: function() { this.actor.show(); } }); var SliderItem = new Lang.Class({ Name: "SliderItem", Extends: BaseContainer, _init: function(icon) { this.parent({hover: false}); this._icon = new St.Icon({style_class: 'popup-menu-icon', icon_name: icon}); this._slider = new Slider.Slider(0); this.actor.add(this._icon); this.actor.add(this._slider.actor, {expand: true}); }, setReactive: function(reactive) { this._slider.actor.reactive = reactive; }, setValue: function(value) { this._slider.setValue(value); }, setIcon: function(icon) { this._icon.icon_name = icon; }, sliderConnect: function(signal, callback) { this._slider.connect(signal, callback); } }); var TrackCover = new Lang.Class({ Name: "TrackBox", Extends: BaseContainer, _init: function(icon) { this.parent({hover: false, style_class: 'no-padding-bottom'}); this.icon = icon; this.actor.add(this.icon, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); } }); var Info = new Lang.Class({ Name: "SecondaryInfo", Extends: BaseContainer, _init: function() { this.parent({hover: false, style_class: 'no-padding-bottom'}); this._animateChange = Util.animateChange; this.infos = new St.BoxLayout({vertical: true}); this._artistLabel = new St.Label({style_class: 'track-info-artist'}); this._titleLabel = new St.Label({style_class: 'track-info-title'}); this._albumLabel = new St.Label({style_class: 'track-info-album'}); this.infos.add(this._artistLabel, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); this.infos.add(this._titleLabel, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); this.infos.add(this._albumLabel, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); this.actor.add(this.infos, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); }, update: function(state) { this._setInfoText(this._artistLabel, state.trackArtist); this._setInfoText(this._titleLabel, state.trackTitle); this._setInfoText(this._albumLabel, state.trackAlbum); }, _setInfoText: function(actor, text) { if (text) { if (actor.text != text) { this._showAnimateInfoItem(actor, text); } } else { this._hideAnimateInfoItem(actor, text); } }, _hideInfoItem: function(actor) { actor.hide(); actor.opacity = 0; actor.set_height(0); }, _showInfoItem: function(actor) { actor.show(); actor.opacity = 255; actor.set_height(-1); }, _showAnimateInfoItem: function(actor, text) { if (actor.visible && !this.animating) { this._animateChange(actor, 'text', text); } else if (this.animating) { actor.text = text; this._showInfoItem(actor); } else { actor.text = text; actor.set_height(-1); let [minHeight, naturalHeight] = actor.get_preferred_height(-1); actor.set_height(0); actor.show(); Tweener.addTween(actor, { height: naturalHeight, time: 0.25, opacity: 255, onComplete: function() { this._showInfoItem(actor); }, onCompleteScope: this }); } }, _hideAnimateInfoItem: function(actor, text) { if (!actor.visible && !this.animating) { actor.text = text; } else if (this.animating) { this._hideInfoItem(actor); actor.text = text; } else { Tweener.addTween(actor, { height: 0, time: 0.25, opacity: 0, onComplete: function() { this._hideInfoItem(actor); actor.text = text; }, onCompleteScope: this }); } } }); var TrackRating = new Lang.Class({ Name: "TrackRating", Extends: BaseContainer, _init: function(player, value) { this._hidden = false; this._player = player; this._animateChange = Util.animateChange; this.parent({style_class: 'no-padding-bottom', hover: false}); this.box = new St.BoxLayout({style_class: 'no-padding track-info-album'}); this.actor.add(this.box, {expand: true, x_fill: false, x_align: St.Align.MIDDLE}); this._applyFunc = null; this._value = null; this._isNuvolaPlayer = false; this._rhythmbox3Proxy = false; if (this._player._pithosRatings) { this.rate = this._ratePithos; this._buildPithosRatings(); } else { if (this._player._ratingsExtension) { this._applyFunc = this.applyRatingsExtension; } else { this._isNuvolaPlayer = this._player.busName.indexOf("org.mpris.MediaPlayer2.NuvolaApp") != -1; if (this._isNuvolaPlayer) { this._applyFunc = this.applyNuvolaRating; } else { // Supported players (except for Nuvola Player & Pithos) let supported = { "org.mpris.MediaPlayer2.rhythmbox": this.applyRhythmbox3Rating, "org.mpris.MediaPlayer2.quodlibet": this.applyQuodLibetRating, "org.mpris.MediaPlayer2.Lollypop": this.applyLollypopRating }; if (supported[this._player.busName]) { this._rhythmbox3Proxy = new DBusIface.RhythmboxRatings(this._player.busName); this._applyFunc = supported[this._player.busName]; } } } this.rate = this._rate; this._buildStars(); } }, _buildStars: function() { this._starButton = []; for(let i=0; i < 5; i++) { // Create star icons let starIcon = new St.Icon({style_class: 'popup-menu-icon star-icon', icon_name: 'non-starred-symbolic' }); // Create the button with starred icon this._starButton[i] = new St.Button({x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, track_hover: true, child: starIcon }); this._starButton[i]._rateValue = i + 1; if (this._applyFunc) { this._starButton[i].connect('notify::hover', Lang.bind(this, function(button) { if (!this._isNuvolaPlayer || this.player._mediaServerPlayer.NuvolaCanRate) { let value = button.hover ? button._rateValue : this._value; for (let i = 0; i < 5; i++) { this._starButton[i].child.icon_name = i < value ? 'starred-symbolic' : 'non-starred-symbolic'; } } })); this._starButton[i].connect('clicked', Lang.bind(this, function(button) { let rateValue = button._rateValue == this._value ? 0 : button._rateValue; this._applyFunc(rateValue); })); } // Put the button in the box this.box.add(this._starButton[i]); } }, _buildPithosRatings: function() { this.box.add_style_class_name('pithos-rating-box'); this._ratingsIcon = new St.Icon({style_class: 'popup-menu-icon no-padding'}); this._unRateButton = new St.Button({x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, child: this._ratingsIcon }); this.box.add(this._unRateButton); this._loveButton = new St.Button(); this.box.add(this._loveButton); this._banButton = new St.Button(); this.box.add(this._banButton); this._tiredButton = new St.Button(); this.box.add(this._tiredButton); this._loveButton.label = _("Love"); this._banButton.label = _("Ban"); this._tiredButton.label = _("Tired"); this._callbackId = 0; this._unRateButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.UnRateSongRemote(this._player.state.trackObj); })); this._banButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.BanSongRemote(this._player.state.trackObj); })); this._tiredButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.TiredSongRemote(this._player.state.trackObj); })); this._unRateButton.hide(); this.box.set_width(-1); }, _ratePithos: function(rating) { if (this._value == rating) { return; } if (this._callbackId!== 0) { this._loveButton.disconnect(this._callbackId); } // Tired or banned song won't show up in the trackbox, // and if a song is banned or set tired it will be skipped automatically. // Pithos doesn't even send metadata updates for the current song if it's banned or set tired. // The only ratings we need to worry about are unrated and loved. if (rating == '') { this._ratingsIcon.icon_name = null; this._unRateButton.hide(); this._loveButton.label = _("Love"); this._callbackId = this._loveButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.LoveSongRemote(this._player.state.trackObj); })); } else if (rating == 'love') { this._ratingsIcon.icon_name = 'emblem-favorite-symbolic'; this._unRateButton.show(); this._loveButton.label = _("UnLove"); this._callbackId = this._loveButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.UnRateSongRemote(this._player.state.trackObj); })); } this._value = rating; this.box.set_width(-1); }, _rate: function(value) { // For Pithos versions without ratings support. if (value.constructor === String) { value = value == 'love' ? 5 : 0; } else { value = Math.min(Math.max(0, value), 5); } if (this._value == value) { return; } this._value = value; for (let i = 0; i < 5; i++) { let icon_name = i < this._value ? 'starred-symbolic' : 'non-starred-symbolic'; if (this.animating) { this._starButton[i].child.icon_name = icon_name; } else { let starChild = this._starButton[i].child; Mainloop.timeout_add(50 * i, Lang.bind(this, function() { this._animateChange(starChild, 'icon_name', icon_name); return false; })); } } }, applyQuodLibetRating: function(value) { // Quod Libet works on 0.0 to 1.0 scores. // Quod Libet also does the right thing and emits a prop change signal // on ratings changes so we don't have to fake it and set it ourself. GLib.spawn_command_line_async("quodlibet --set-rating=%f".format(value / 5.0)); }, applyLollypopRating: function(value) { // Lollypop works on 0 to 5 scores. // Lollypop also does the right thing and emits a prop change signal // on ratings changes so we don't have to fake it and set it ourself. GLib.spawn_command_line_async("lollypop --set-rating=%s".format(value)); }, applyRhythmbox3Rating: function(value) { if (this._player.state.trackUrl) { this._rhythmbox3Proxy.SetEntryPropertiesRemote(this._player.state.trackUrl, {rating: GLib.Variant.new_double(value)}); // Rhythmbox doesn't emit a prop change signal when we rate the song but it will more // than likely stick so we just fake it... this.rate(value); } }, applyNuvolaRating: function(value) { if (this.player._mediaServerPlayer.NuvolaCanRate) { this.player._mediaServerPlayer.NuvolaSetRatingRemote(value / 5.0); } }, applyRatingsExtension: function(value) { if (this._player.state.trackObj) { this._player._ratingsExtension.SetRatingRemote(this._player.state.trackObj, value / 5.0); } } }); var ListSubMenu = new Lang.Class({ Name: 'ListSubMenu', Extends: PopupMenu.PopupSubMenuMenuItem, _init: function(label) { this.parent(label, false); this.activeObject = null; this._hidden = false; this.menu = new SubMenu(this.actor, this._triangle, false); this.menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); }, get hidden() { return this._hidden; }, set hidden(value) { this._hidden = value; }, hide: function() { this.menu.close(); this.actor.hide(); this.actor.opacity = 0; this.actor.set_height(0); this.hidden = true; }, show: function() { this.actor.show(); this.actor.opacity = 255; this.actor.set_height(-1); this.hidden = false; }, showAnimate: function() { if (!this.actor.get_stage() || !this._hidden) return; this.actor.set_height(-1); let [minHeight, naturalHeight] = this.actor.get_preferred_height(-1); this.actor.set_height(0); this.actor.show(); Tweener.addTween(this.actor, { opacity: 255, height: naturalHeight, time: 0.25, onComplete: function() { this.show(); }, onCompleteScope: this }); }, hideAnimate: function() { if (!this.actor.get_stage() || this._hidden) return; Tweener.addTween(this.actor, { opacity: 0, height: 0, time: 0.25, onComplete: function() { this.hide(); }, onCompleteScope: this }); }, setObjectActive: function(objPath) { this.activeObject = objPath; this.menu._getMenuItems().forEach(function(listItem) { if (listItem.obj == objPath) { listItem.setOrnament(PopupMenu.Ornament.DOT); } else { listItem.setOrnament(PopupMenu.Ornament.NONE); } }); }, getItem: function(obj) { let menuItems = this.menu._getMenuItems().filter(function(item) { return item.obj === obj; }); if (menuItems && menuItems[0]) { return menuItems[0]; } else { return null; } }, hasUniqueObjPaths: function(objects, isTracklistMetadata) { //Check for unique values in the playlist and tracklist object paths. let unique = objects.reduce(function(values, object) { if (isTracklistMetadata) { object = object["mpris:trackid"] ? object["mpris:trackid"].unpack() : "/org/mpris/MediaPlayer2/TrackList/NoTrack"; } else { object = object[0]; } values[object] = true; return values; }, {}); return Object.keys(unique).length === objects.length; }, _subMenuOpenStateChanged: function(menu, open) { if (open) { this.actor.add_style_pseudo_class('open'); this.actor.add_accessible_state(Atk.StateType.EXPANDED); this.actor.add_style_pseudo_class('checked'); } else { this.actor.remove_style_pseudo_class('open'); this.actor.remove_accessible_state (Atk.StateType.EXPANDED); this.actor.remove_style_pseudo_class('checked'); } } }); var TrackList = new Lang.Class({ Name: "Tracklist", Extends: ListSubMenu, _init: function(label, player) { this.parent(label); this.player = player; this.parseMetadata = Util.parseMetadata; }, showRatings: function(value) { this.menu._getMenuItems().forEach(function(tracklistItem) { tracklistItem.showRatings(value); }); }, updateMetadata: function(UpdatedMetadata) { let metadata = {}; this.parseMetadata(UpdatedMetadata, metadata); let trackListItem = this.getItem(metadata.trackObj); if (trackListItem) { trackListItem.updateMetadata(metadata); } }, loadTracklist: function(trackListMetaData, showRatings) { this.menu.removeAll(); //As per spec all object paths MUST be unique. //If we don't have unique object paths reject the whole array. let hasUniqueObjPaths = this.hasUniqueObjPaths(trackListMetaData, true); if (hasUniqueObjPaths) { trackListMetaData.forEach(Lang.bind(this, function(trackMetadata) { let metadata = {}; this.parseMetadata(trackMetadata, metadata); //Don't add tracks with "/org/mpris/MediaPlayer2/TrackList/NoTrack" as the object path. //As per spec the "/org/mpris/MediaPlayer2/TrackList/NoTrack" object path means it's not a valid track. if (metadata.trackObj && metadata.trackObj !== '/org/mpris/MediaPlayer2/TrackList/NoTrack') { metadata.showRatings = showRatings; let trackUI = new TracklistItem(metadata, this.player); trackUI.connect('activate', Lang.bind(this, function() { this.player.playTrack(trackUI.obj); })); this.menu.addMenuItem(trackUI); } })); if (this.activeObject) { this.setObjectActive(this.activeObject); } } } }); var Playlists = new Lang.Class({ Name: "Playlists", Extends: ListSubMenu, _init: function(label, player) { this.parent(label); this.player = player; }, loadPlaylists: function(playlists) { this.menu.removeAll(); //As per spec all object paths MUST be unique. //If we don't have unique object paths reject the whole array. let hasUniqueObjPaths = this.hasUniqueObjPaths(playlists); if (hasUniqueObjPaths) { playlists.forEach(Lang.bind(this, function(playlist) { let [obj, name] = playlist; //Don't add playlists with just "/" as the object path. //Playlist object paths that just contain "/" are a way to //indicate invalid playlists as per spec. if (obj !== '/') { let playlistUI = new PlaylistItem(name, obj); playlistUI.connect('activate', Lang.bind(this, function() { this.player.playPlaylist(playlistUI.obj); })); this.menu.addMenuItem(playlistUI); } })); if (this.activeObject) { this.setObjectActive(this.activeObject); } } }, updatePlaylist: function(UpdatedPlaylist) { let [obj, name] = UpdatedPlaylist; let playlistItem = this.getItem(obj); if (playlistItem) { playlistItem.updatePlaylistName(name); } } }); var PlaylistItem = new Lang.Class({ Name: "PlaylistItem", Extends: PopupMenu.PopupBaseMenuItem, _init: function (text, obj) { this.parent(); this.obj = obj; this.label = new St.Label({text: text}); this.actor.add(this.label); }, updatePlaylistName: function(name) { if (this.label.text != name) { this.label.text = name; } } }); var TracklistItem = new Lang.Class({ Name: "TracklistItem", Extends: PopupMenu.PopupBaseMenuItem, _init: function (metadata, player) { this.parent(); this.actor.child_set_property(this._ornamentLabel, "y-fill", false); this.actor.child_set_property(this._ornamentLabel, "y-align", St.Align.MIDDLE); this._player = player; this._loveCallbackId = 0; this._banCallbackId = 0; this._tiredCallbackId = 0; this.obj = metadata.trackObj; this._setCoverIconAsync = Util.setCoverIconAsync; this._animateChange = Util.animateChange; this._rating = null; this._coverIcon = new St.Icon({style_class: 'small-cover-icon'}); let _icon_box = new St.BoxLayout({height: 48, width: 48}); _icon_box.add(this._coverIcon, {y_fill: false, y_align: St.Align.MIDDLE}); this._artistLabel = new St.Label({style_class: 'track-info-artist'}); this._titleLabel = new St.Label({style_class: 'track-info-title'}); this._albumLabel = new St.Label({style_class: 'track-info-album'}); this._ratingBox = new St.BoxLayout({style_class: 'no-padding track-info-album'}); this._ratingBox.hide(); this._box = new St.BoxLayout({vertical: true}); this._box.add(this._artistLabel, {expand: true, y_fill: false, y_align: St.Align.MIDDLE}); this._box.add(this._titleLabel, {expand: true, y_fill: false, y_align: St.Align.MIDDLE}); this._box.add(this._albumLabel, {expand: true, y_fill: false, y_align: St.Align.MIDDLE}); this._box.add(this._ratingBox, {expand: true, y_fill: false, y_align: St.Align.MIDDLE}); this.actor.add(_icon_box, {y_fill: false, y_align: St.Align.MIDDLE}); this.actor.add(this._box, {y_fill: false, y_align: St.Align.MIDDLE}); this._validRatings = metadata.trackRating != 'no rating'; if (this._player._pithosRatings) { this._rate = this._setPithosRating; if (this._validRatings) { this._buildPithosRatings(metadata.trackRating); this.showRatings(metadata.showRatings); } else { this._buildPithosRatings(''); this.showRatings(false); } } else { this._rate = this._setStarRating; if (this._validRatings) { this._buildStars(metadata.trackRating); this.showRatings(metadata.showRatings); } else { this._buildStars(0); this.showRatings(false); } } this.updateMetadata(metadata); }, updateMetadata: function(metadata) { this._setCoverIconAsync(this._coverIcon, metadata.trackCoverUrl); this._setArtist(metadata.trackArtist); this._setTitle(metadata.trackTitle); this._setAlbum(metadata.trackAlbum); this._validRatings = metadata.trackRating != 'no rating'; if (this._validRatings) { this._rate(metadata.trackRating); } else { this.showRatings(false); } }, _setArtist: function(artist) { if (this._artistLabel.text != artist) { this._animateChange(this._artistLabel, 'text', artist); } }, _setTitle: function(title) { if (this._titleLabel.text != title) { this._animateChange(this._titleLabel, 'text', title); } }, _setAlbum: function(album) { if (this._albumLabel.text != album) { this._animateChange(this._albumLabel, 'text', album); } }, _buildStars: function(value) { // For Pithos versions without ratings support. if (value.constructor === String) { value = value == 'love' ? 5 : 0; } else { value = Math.min(Math.max(0, value), 5); } this._starIcon = []; for(let i=0; i < 5; i++) { let icon_name = i < value ? 'starred-symbolic' : 'non-starred-symbolic'; this._starIcon[i] = new St.Icon({style_class: 'popup-menu-icon star-icon'}); this._ratingBox.add(this._starIcon[i]); let starIcon = this._starIcon[i]; Mainloop.timeout_add(50 * i, Lang.bind(this, function() { this._animateChange(starIcon, 'icon_name', icon_name); return false; })); } this._rating = value; }, _buildPithosRatings: function(rating) { this._ratingBox.add_style_class_name('pithos-rating-box'); this._ratingsIcon = new St.Icon({style_class: 'popup-menu-icon no-padding'}); this._unRateButton = new St.Button({x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE, child: this._ratingsIcon }); this._ratingBox.add(this._unRateButton, {y_align: St.Align.MIDDLE}); this._loveButton = new St.Button(); this._ratingBox.add(this._loveButton, {y_align: St.Align.MIDDLE}); this._banButton = new St.Button(); this._ratingBox.add(this._banButton, {y_align: St.Align.MIDDLE}); this._tiredButton = new St.Button(); this._ratingBox.add(this._tiredButton, {y_align: St.Align.MIDDLE}); this._unrateCallbackId = this._unRateButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.UnRateSongRemote(this.obj); })); this._unRateButton.hide(); this._setPithosRating(rating); }, _setPithosRating: function(rating) { if (this._rating == rating) { return; } if (this._loveCallbackId !== 0) { this._loveButton.disconnect(this._loveCallbackId); } if (this._banCallbackId !== 0) { this._banButton.disconnect(this._banCallbackId); } if (this._tiredCallbackId !== 0) { this._tiredButton.disconnect(this._tiredCallbackId); } if (rating == '') { this._ratingsIcon.icon_name = null; this._unRateButton.hide(); this._loveButton.label = _("Love"); this._banButton.label = _("Ban"); this._tiredButton.label = _("Tired"); this._loveCallbackId = this._loveButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.LoveSongRemote(this.obj); })); this._banCallbackId = this._banButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.BanSongRemote(this.obj); })); this._tiredCallbackId = this._tiredButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.TiredSongRemote(this.obj); })); } else if (rating == 'love') { this._ratingsIcon.icon_name = 'emblem-favorite-symbolic'; this._unRateButton.show(); this._loveButton.label = _("UnLove"); this._banButton.label = _("Ban"); this._tiredButton.label = _("Tired"); this._loveCallbackId = this._loveButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.UnRateSongRemote(this.obj); })); this._banCallbackId = this._banButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.BanSongRemote(this.obj); })); this._tiredCallbackId = this._tiredButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.TiredSongRemote(this.obj); })); } else if (rating == 'ban') { this._ratingsIcon.icon_name = 'dialog-error-symbolic'; this._unRateButton.show(); this._loveButton.label = _("Love"); this._banButton.label = _("UnBan"); this._tiredButton.label = _("Tired"); this._loveCallbackId = this._loveButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.LoveSongRemote(this.obj); })); this._banCallbackId = this._banButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.UnRateSongRemote(this.obj); })); this._tiredCallbackId = this._tiredButton.connect('clicked', Lang.bind(this, function() { this._player._pithosRatings.TiredSongRemote(this.obj); })); } else if (rating == 'tired') { if (this._unrateCallbackId !== 0) { this._unRateButton.disconnect(this._unrateCallbackId); } // Once a song has been set tired it's rating can't be changed. // No need to connect button signals. this._ratingsIcon.icon_name = 'go-jump-symbolic'; this._unRateButton.show(); this._loveButton.label = _("Tired… (Can't be Changed)"); this._loveButton.reactive = false; this._unRateButton.reactive = false; this._banButton.hide(); this._tiredButton.hide(); this._unrateCallbackId = 0; this._loveCallbackId = 0; this._banCallbackId = 0; this._tiredCallbackId = 0; } this._box.set_width(-1); this._rating = rating; }, _setStarRating: function(value) { // For Pithos versions without ratings support. if (value.constructor === String) { value = value == 'love' ? 5 : 0; } else { value = Math.min(Math.max(0, value), 5); } if (this._rating != value) { this._rating = value; for (let i = 0; i < 5; i++) { let icon_name = i < value ? 'starred-symbolic' : 'non-starred-symbolic'; let starIcon = this._starIcon[i]; Mainloop.timeout_add(50 * i, Lang.bind(this, function() { this._animateChange(starIcon, 'icon_name', icon_name); return false; })); } } }, showRatings: function(value) { if (value && this._validRatings) { this._albumLabel.hide(); this._ratingBox.show(); } else { this._ratingBox.hide(); this._albumLabel.show(); } } });