cantata-2.2.0/000077500000000000000000000000001316350454000131275ustar00rootroot00000000000000cantata-2.2.0/.gitignore000066400000000000000000000000451316350454000151160ustar00rootroot00000000000000/.project build/ CMakeLists.txt.user cantata-2.2.0/3rdparty/000077500000000000000000000000001316350454000146775ustar00rootroot00000000000000cantata-2.2.0/3rdparty/qtiocompressor/000077500000000000000000000000001316350454000177705ustar00rootroot00000000000000cantata-2.2.0/3rdparty/qtiocompressor/CMakeLists.txt000066400000000000000000000005521316350454000225320ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.6) set(IOCOMPRESSOR-SOURCES qtiocompressor.cpp ) set(IOCOMPRESSOR-MOC-HEADERS qtiocompressor.h ) QT5_WRAP_CPP(IOCOMPRESSOR-SOURCES-MOC ${IOCOMPRESSOR-MOC-HEADERS}) include_directories(${QTINCLUDES} ${ZLIB_INCLUDE_DIRS}) ADD_LIBRARY(qtiocompressor STATIC ${IOCOMPRESSOR-SOURCES} ${IOCOMPRESSOR-SOURCES-MOC} ) cantata-2.2.0/3rdparty/qtiocompressor/LGPL_EXCEPTION.txt000066400000000000000000000021661316350454000227120ustar00rootroot00000000000000Nokia Qt LGPL Exception version 1.1 As an additional permission to the GNU Lesser General Public License version 2.1, the object code form of a "work that uses the Library" may incorporate material from a header file that is part of the Library. You may distribute such object code under terms of your choice, provided that: (i) the header files of the Library have not been modified; and (ii) the incorporated material is limited to numerical parameters, data structure layouts, accessors, macros, inline functions and templates; and (iii) you comply with the terms of Section 6 of the GNU Lesser General Public License version 2.1. Moreover, you may apply this exception to a modified version of the Library, provided that such modification does not involve copying material from the Library into the modified Library?s header files unless such material is limited to (i) numerical parameters; (ii) data structure layouts; (iii) accessors; and (iv) small macros, templates and inline functions of five lines or less in length. Furthermore, you are not required to apply this additional permission to a modified version of the Library. cantata-2.2.0/3rdparty/qtiocompressor/LICENSE.GPL3000066400000000000000000001045131316350454000215050ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . cantata-2.2.0/3rdparty/qtiocompressor/QtIOCompressor000066400000000000000000000000341316350454000226010ustar00rootroot00000000000000#include "qtiocompressor.h" cantata-2.2.0/3rdparty/qtiocompressor/qtiocompressor.cpp000066400000000000000000000506421316350454000235740ustar00rootroot00000000000000/**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtiocompressor.h" #include "zlib.h" #include typedef Bytef ZlibByte; typedef uInt ZlibSize; class QtIOCompressorPrivate { QtIOCompressor *q_ptr; Q_DECLARE_PUBLIC(QtIOCompressor) public: enum State { // Read state NotReadFirstByte, InStream, EndOfStream, // Write state NoBytesWritten, BytesWritten, // Common Closed, Error }; QtIOCompressorPrivate(QtIOCompressor *q_ptr, QIODevice *device, int compressionLevel, int bufferSize); ~QtIOCompressorPrivate(); void flushZlib(int flushMode); bool writeBytes(ZlibByte *buffer, ZlibSize outputSize); void setZlibError(const QString &erroMessage, int zlibErrorCode); QIODevice *device; bool manageDevice; z_stream zlibStream; const int compressionLevel; const ZlibSize bufferSize; ZlibByte *buffer; State state; QtIOCompressor::StreamFormat streamFormat; }; /*! \internal */ QtIOCompressorPrivate::QtIOCompressorPrivate(QtIOCompressor *q_ptr, QIODevice *device, int compressionLevel, int bufferSize) :q_ptr(q_ptr) ,device(device) ,compressionLevel(compressionLevel) ,bufferSize(bufferSize) ,buffer(new ZlibByte[bufferSize]) ,state(Closed) ,streamFormat(QtIOCompressor::ZlibFormat) { // Use default zlib memory management. zlibStream.zalloc = Z_NULL; zlibStream.zfree = Z_NULL; zlibStream.opaque = Z_NULL; } /*! \internal */ QtIOCompressorPrivate::~QtIOCompressorPrivate() { delete[] buffer; } /*! \internal Flushes the zlib stream. */ void QtIOCompressorPrivate::flushZlib(int flushMode) { // No input. zlibStream.next_in = 0; zlibStream.avail_in = 0; int status; do { zlibStream.next_out = buffer; zlibStream.avail_out = bufferSize; status = deflate(&zlibStream, flushMode); if (status != Z_OK && status != Z_STREAM_END) { state = QtIOCompressorPrivate::Error; setZlibError(QT_TRANSLATE_NOOP("QtIOCompressor", "Internal zlib error when compressing: "), status); return; } ZlibSize outputSize = bufferSize - zlibStream.avail_out; // Try to write data from the buffer to to the underlying device, return on failure. if (!writeBytes(buffer, outputSize)) return; // If the mode is Z_FNISH we must loop until we get Z_STREAM_END, // else we loop as long as zlib is able to fill the output buffer. } while ((flushMode == Z_FINISH && status != Z_STREAM_END) || (flushMode != Z_FINISH && zlibStream.avail_out == 0)); if (flushMode == Z_FINISH) Q_ASSERT(status == Z_STREAM_END); else Q_ASSERT(status == Z_OK); } /*! \internal Writes outputSize bytes from buffer to the inderlying device. */ bool QtIOCompressorPrivate::writeBytes(ZlibByte *buffer, ZlibSize outputSize) { Q_Q(QtIOCompressor); ZlibSize totalBytesWritten = 0; // Loop until all bytes are written to the underlying device. do { const qint64 bytesWritten = device->write(reinterpret_cast(buffer), outputSize); if (bytesWritten == -1) { q->setErrorString(QT_TRANSLATE_NOOP("QtIOCompressor", "Error writing to underlying device: ") + device->errorString()); return false; } totalBytesWritten += bytesWritten; } while (totalBytesWritten != outputSize); // put up a flag so that the device will be flushed on close. state = BytesWritten; return true; } /*! \internal Sets the error string to errorMessage + zlib error string for zlibErrorCode */ void QtIOCompressorPrivate::setZlibError(const QString &errorMessage, int zlibErrorCode) { Q_Q(QtIOCompressor); // Watch out, zlibErrorString may be null. const char * const zlibErrorString = zError(zlibErrorCode); QString errorString; if (zlibErrorString) errorString = errorMessage + zlibErrorString; else errorString = errorMessage + " Unknown error, code " + QString::number(zlibErrorCode); q->setErrorString(errorString); } /*! \class QtIOCompressor \brief The QtIOCompressor class is a QIODevice that compresses data streams. A QtIOCompressor object is constructed with a pointer to an underlying QIODevice. Data written to the QtIOCompressor object will be compressed before it is written to the underlying QIODevice. Similary, if you read from the QtIOCompressor object, the data will be read from the underlying device and then decompressed. QtIOCompressor is a sequential device, which means that it does not support seeks or random access. Internally, QtIOCompressor uses the zlib library to compress and uncompress data. Usage examples: Writing compressed data to a file: \code QFile file("foo"); QtIOCompressor compressor(&file); compressor.open(QIODevice::WriteOnly); compressor.write(QByteArray() << "The quick brown fox"); compressor.close(); \endcode Reading compressed data from a file: \code QFile file("foo"); QtIOCompressor compressor(&file); compressor.open(QIODevice::ReadOnly); const QByteArray text = compressor.readAll(); compressor.close(); \endcode QtIOCompressor can also read and write compressed data in different compressed formats, ref. StreamFormat. Use setStreamFormat() before open() to select format. */ /*! \enum QtIOCompressor::StreamFormat This enum specifies which stream format to use. \value ZlibFormat: This is the default and has the smallest overhead. \value GzipFormat: This format is compatible with the gzip file format, but has more overhead than ZlibFormat. Note: requires zlib version 1.2.x or higher at runtime. \value RawZipFormat: This is compatible with the most common compression method of the data blocks contained in ZIP archives. Note: ZIP file headers are not read or generated, so setting this format, by itself, does not let QtIOCompressor read or write ZIP files. Ref. the ziplist example program. \sa setStreamFormat() */ /*! Constructs a QtIOCompressor using the given \a device as the underlying device. The allowed value range for \a compressionLevel is 0 to 9, where 0 means no compression and 9 means maximum compression. The default value is 6. \a bufferSize specifies the size of the internal buffer used when reading from and writing to the underlying device. The default value is 65KB. Using a larger value allows for faster compression and deompression at the expense of memory usage. */ QtIOCompressor::QtIOCompressor(QIODevice *device, int compressionLevel, int bufferSize) :d_ptr(new QtIOCompressorPrivate(this, device, compressionLevel, bufferSize)) {} /*! Destroys the QtIOCompressor, closing it if neccesary. */ QtIOCompressor::~QtIOCompressor() { Q_D(QtIOCompressor); close(); delete d; } /*! Sets the format on the compressed stream to \a format. \sa QtIOCompressor::StreamFormat */ void QtIOCompressor::setStreamFormat(StreamFormat format) { Q_D(QtIOCompressor); // Print a waning if the compile-time version of zlib does not support gzip. if (format == GzipFormat && checkGzipSupport(ZLIB_VERSION) == false) qWarning("QtIOCompressor::setStreamFormat: zlib 1.2.x or higher is " "required to use the gzip format. Current version is: %s", ZLIB_VERSION); d->streamFormat = format; } /*! Returns the format set on the compressed stream. \sa QtIOCompressor::StreamFormat */ QtIOCompressor::StreamFormat QtIOCompressor::streamFormat() const { Q_D(const QtIOCompressor); return d->streamFormat; } /*! Returns true if the zlib library in use supports the gzip format, false otherwise. */ bool QtIOCompressor::isGzipSupported() { return checkGzipSupport(zlibVersion()); } /*! \reimp */ bool QtIOCompressor::isSequential() const { return true; } /*! Opens the QtIOCompressor in \a mode. Only ReadOnly and WriteOnly is supported. This functon will return false if you try to open in other modes. If the underlying device is not opened, this function will open it in a suitable mode. If this happens the device will also be closed when close() is called. If the underlying device is already opened, its openmode must be compatable with \a mode. Returns true on success, false on error. \sa close() */ bool QtIOCompressor::open(OpenMode mode) { Q_D(QtIOCompressor); if (isOpen()) { qWarning("QtIOCompressor::open: device already open"); return false; } // Check for correct mode: ReadOnly xor WriteOnly const bool read = (bool)(mode & ReadOnly); const bool write = (bool)(mode & WriteOnly); const bool both = (read && write); const bool neither = !(read || write); if (both || neither) { qWarning("QtIOCompressor::open: QtIOCompressor can only be opened in the ReadOnly or WriteOnly modes"); return false; } // If the underlying device is open, check that is it opened in a compatible mode. if (d->device->isOpen()) { d->manageDevice = false; const OpenMode deviceMode = d->device->openMode(); if (read && !(deviceMode & ReadOnly)) { qWarning("QtIOCompressor::open: underlying device must be open in one of the ReadOnly or WriteOnly modes"); return false; } else if (write && !(deviceMode & WriteOnly)) { qWarning("QtIOCompressor::open: underlying device must be open in one of the ReadOnly or WriteOnly modes"); return false; } // If the underlying device is closed, open it. } else { d->manageDevice = true; if (d->device->open(mode) == false) { setErrorString(QT_TRANSLATE_NOOP("QtIOCompressor", "Error opening underlying device: ") + d->device->errorString()); return false; } } // Initialize zlib for deflating or inflating. // The second argument to inflate/deflateInit2 is the windowBits parameter, // which also controls what kind of compression stream headers to use. // The default value for this is 15. Passing a value greater than 15 // enables gzip headers and then subtracts 16 form the windowBits value. // (So passing 31 gives gzip headers and 15 windowBits). Passing a negative // value selects no headers hand then negates the windowBits argument. int windowBits; switch (d->streamFormat) { case QtIOCompressor::GzipFormat: windowBits = 31; break; case QtIOCompressor::RawZipFormat: windowBits = -15; break; default: windowBits = 15; } int status; if (read) { d->state = QtIOCompressorPrivate::NotReadFirstByte; d->zlibStream.avail_in = 0; d->zlibStream.next_in = 0; if (d->streamFormat == QtIOCompressor::ZlibFormat) { status = inflateInit(&d->zlibStream); } else { if (checkGzipSupport(zlibVersion()) == false) { setErrorString(QT_TRANSLATE_NOOP("QtIOCompressor::open", "The gzip format not supported in this version of zlib.")); return false; } status = inflateInit2(&d->zlibStream, windowBits); } } else { d->state = QtIOCompressorPrivate::NoBytesWritten; if (d->streamFormat == QtIOCompressor::ZlibFormat) status = deflateInit(&d->zlibStream, d->compressionLevel); else status = deflateInit2(&d->zlibStream, d->compressionLevel, Z_DEFLATED, windowBits, 8, Z_DEFAULT_STRATEGY); } // Handle error. if (status != Z_OK) { d->setZlibError(QT_TRANSLATE_NOOP("QtIOCompressor::open", "Internal zlib error: "), status); return false; } return QIODevice::open(mode); } /*! Closes the QtIOCompressor, and also the underlying device if it was opened by QtIOCompressor. \sa open() */ void QtIOCompressor::close() { Q_D(QtIOCompressor); if (isOpen() == false) return; // Flush and close the zlib stream. if (openMode() & ReadOnly) { d->state = QtIOCompressorPrivate::NotReadFirstByte; inflateEnd(&d->zlibStream); } else { if (d->state == QtIOCompressorPrivate::BytesWritten) { // Only flush if we have written anything. d->state = QtIOCompressorPrivate::NoBytesWritten; d->flushZlib(Z_FINISH); } deflateEnd(&d->zlibStream); } // Close the underlying device if we are managing it. if (d->manageDevice) d->device->close(); QIODevice::close(); } /*! Flushes the internal buffer. Each time you call flush, all data written to the QtIOCompressor is compressed and written to the underlying device. Calling this function can reduce the compression ratio. The underlying device is not flushed. Calling this function when QtIOCompressor is in ReadOnly mode has no effect. */ void QtIOCompressor::flush() { Q_D(QtIOCompressor); if (isOpen() == false || openMode() & ReadOnly) return; d->flushZlib(Z_SYNC_FLUSH); } /*! Returns 1 if there might be data available for reading, or 0 if there is no data available. There is unfortunately no way of knowing how much data there is available when dealing with compressed streams. Also, since the remaining compressed data might be a part of the meta-data that ends the compressed stream (and therefore will yield no uncompressed data), you cannot assume that a read after getting a 1 from this function will return data. */ qint64 QtIOCompressor::bytesAvailable() const { Q_D(const QtIOCompressor); if ((openMode() & ReadOnly) == false) return 0; int numBytes = 0; switch (d->state) { case QtIOCompressorPrivate::NotReadFirstByte: numBytes = d->device->bytesAvailable(); break; case QtIOCompressorPrivate::InStream: numBytes = 1; break; case QtIOCompressorPrivate::EndOfStream: case QtIOCompressorPrivate::Error: default: numBytes = 0; break; }; numBytes += QIODevice::bytesAvailable(); if (numBytes > 0) return 1; else return 0; } /*! \internal Reads and decompresses data from the underlying device. */ qint64 QtIOCompressor::readData(char *data, qint64 maxSize) { Q_D(QtIOCompressor); if (d->state == QtIOCompressorPrivate::EndOfStream) return 0; if (d->state == QtIOCompressorPrivate::Error) return -1; // We are ging to try to fill the data buffer d->zlibStream.next_out = reinterpret_cast(data); d->zlibStream.avail_out = maxSize; int status; do { // Read data if if the input buffer is empty. There could be data in the buffer // from a previous readData call. if (d->zlibStream.avail_in == 0) { qint64 bytesAvalible = d->device->read(reinterpret_cast(d->buffer), d->bufferSize); d->zlibStream.next_in = d->buffer; d->zlibStream.avail_in = bytesAvalible; if (bytesAvalible == -1) { d->state = QtIOCompressorPrivate::Error; setErrorString(QT_TRANSLATE_NOOP("QtIOCompressor", "Error reading data from underlying device: ") + d->device->errorString()); return -1; } if (d->state != QtIOCompressorPrivate::InStream) { // If we are not in a stream and get 0 bytes, we are probably trying to read from an empty device. if(bytesAvalible == 0) return 0; else if (bytesAvalible > 0) d->state = QtIOCompressorPrivate::InStream; } } // Decompress. status = inflate(&d->zlibStream, Z_SYNC_FLUSH); switch (status) { case Z_NEED_DICT: case Z_DATA_ERROR: case Z_MEM_ERROR: d->state = QtIOCompressorPrivate::Error; d->setZlibError(QT_TRANSLATE_NOOP("QtIOCompressor", "Internal zlib error when decompressing: "), status); return -1; case Z_BUF_ERROR: // No more input and zlib can not privide more output - Not an error, we can try to read again when we have more input. return 0; break; } // Loop util data buffer is full or we reach the end of the input stream. } while (d->zlibStream.avail_out != 0 && status != Z_STREAM_END); if (status == Z_STREAM_END) { d->state = QtIOCompressorPrivate::EndOfStream; // Unget any data left in the read buffer. for (int i = d->zlibStream.avail_in; i >= 0; --i) d->device->ungetChar(*reinterpret_cast(d->zlibStream.next_in + i)); } const ZlibSize outputSize = maxSize - d->zlibStream.avail_out; return outputSize; } /*! \internal Compresses and writes data to the underlying device. */ qint64 QtIOCompressor::writeData(const char *data, qint64 maxSize) { if (maxSize < 1) return 0; Q_D(QtIOCompressor); d->zlibStream.next_in = reinterpret_cast(const_cast(data)); d->zlibStream.avail_in = maxSize; if (d->state == QtIOCompressorPrivate::Error) return -1; do { d->zlibStream.next_out = d->buffer; d->zlibStream.avail_out = d->bufferSize; const int status = deflate(&d->zlibStream, Z_NO_FLUSH); if (status != Z_OK) { d->state = QtIOCompressorPrivate::Error; d->setZlibError(QT_TRANSLATE_NOOP("QtIOCompressor", "Internal zlib error when compressing: "), status); return -1; } ZlibSize outputSize = d->bufferSize - d->zlibStream.avail_out; // Try to write data from the buffer to to the underlying device, return -1 on failure. if (d->writeBytes(d->buffer, outputSize) == false) return -1; } while (d->zlibStream.avail_out == 0); // run until output is not full. Q_ASSERT(d->zlibStream.avail_in == 0); return maxSize; } /* \internal Checks if the run-time zlib version is 1.2.x or higher. */ bool QtIOCompressor::checkGzipSupport(const char * const versionString) { if (strlen(versionString) < 3) return false; if (versionString[0] == '0' || (versionString[0] == '1' && (versionString[2] == '0' || versionString[2] == '1' ))) return false; return true; } cantata-2.2.0/3rdparty/qtiocompressor/qtiocompressor.h000066400000000000000000000071511316350454000232360ustar00rootroot00000000000000/**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #ifndef QTIOCOMPRESSOR_H #define QTIOCOMPRESSOR_H #include #if defined(Q_WS_WIN) # if !defined(QT_QTIOCOMPRESSOR_EXPORT) && !defined(QT_QTIOCOMPRESSOR_IMPORT) # define QT_QTIOCOMPRESSOR_EXPORT # elif defined(QT_QTIOCOMPRESSOR_IMPORT) # if defined(QT_QTIOCOMPRESSOR_EXPORT) # undef QT_QTIOCOMPRESSOR_EXPORT # endif # define QT_QTIOCOMPRESSOR_EXPORT __declspec(dllimport) # elif defined(QT_QTIOCOMPRESSOR_EXPORT) # undef QT_QTIOCOMPRESSOR_EXPORT # define QT_QTIOCOMPRESSOR_EXPORT __declspec(dllexport) # endif #else # define QT_QTIOCOMPRESSOR_EXPORT #endif class QtIOCompressorPrivate; class QT_QTIOCOMPRESSOR_EXPORT QtIOCompressor : public QIODevice { Q_OBJECT public: enum StreamFormat { ZlibFormat, GzipFormat, RawZipFormat }; QtIOCompressor(QIODevice *device, int compressionLevel = 6, int bufferSize = 65500); ~QtIOCompressor(); void setStreamFormat(StreamFormat format); StreamFormat streamFormat() const; static bool isGzipSupported(); bool isSequential() const; bool open(OpenMode mode); void close(); void flush(); qint64 bytesAvailable() const; protected: qint64 readData(char * data, qint64 maxSize); qint64 writeData(const char * data, qint64 maxSize); private: static bool checkGzipSupport(const char * const versionString); QtIOCompressorPrivate *d_ptr; Q_DECLARE_PRIVATE(QtIOCompressor) Q_DISABLE_COPY(QtIOCompressor) }; #endif cantata-2.2.0/3rdparty/solid-lite/000077500000000000000000000000001316350454000167445ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/CMakeLists.txt000066400000000000000000000276001316350454000215110ustar00rootroot00000000000000INCLUDE(MacroOptionalFindPackage) INCLUDE(MacroLogFeature) add_subdirectory( ifaces ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QTINCLUDES}) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/backends/hal ${CMAKE_CURRENT_BINARY_DIR}/backends/udev ${CMAKE_CURRENT_BINARY_DIR}/backends/wmi ) set(solidlite_LIB_SRCS solidnamespace.cpp managerbase.cpp device.cpp devicemanager.cpp deviceinterface.cpp genericinterface.cpp block.cpp storagedrive.cpp opticaldrive.cpp storagevolume.cpp opticaldisc.cpp storageaccess.cpp portablemediaplayer.cpp predicate.cpp predicateparse.cpp predicate_lexer.c predicate_parser.c xdgbasedirs.cpp ifaces/block.cpp ifaces/opticaldrive.cpp ifaces/device.cpp ifaces/deviceinterface.cpp ifaces/devicemanager.cpp ifaces/genericinterface.cpp ifaces/opticaldisc.cpp ifaces/portablemediaplayer.cpp ifaces/storagedrive.cpp ifaces/storagevolume.cpp ifaces/storageaccess.cpp backends/shared/rootdevice.cpp ) set(solidlite_LIB_MOC_HDRS block.h deviceinterface.h devicemanager_p.h devicenotifier.h device_p.h genericinterface.h portablemediaplayer.h storageaccess.h storagedrive.h opticaldrive.h storagevolume.h opticaldisc.h ifaces/device.h ifaces/devicemanager.h backends/shared/rootdevice.h ) macro_optional_find_package( UDev ) macro_log_feature( UDEV_FOUND "UDev" "UDev support for Solid" "http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html" FALSE "" "Allows Solid to use UDev to provide information about devices on Linux" ) configure_file( config-solid.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-solid.h ) if ( UDEV_FOUND ) message(STATUS "Building Solid UDev backend." ) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/udev/udevdevice.cpp backends/udev/udevmanager.cpp backends/udev/udevdeviceinterface.cpp backends/udev/udevgenericinterface.cpp backends/udev/udevportablemediaplayer.cpp backends/udev/udevblock.cpp backends/shared/udevqtclient.cpp backends/shared/udevqtdevice.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/udev/udevblock.h backends/udev/udevdevice.h backends/udev/udevdeviceinterface.h backends/udev/udevgenericinterface.h backends/udev/udevmanager.h backends/udev/udevportablemediaplayer.h backends/shared/udevqt.h ) # check for media-player-info (runtime-only optional dependency) set(XDG_DATA_DIRS_ENV $ENV{XDG_DATA_DIRS}) # if(ENV{..}) does not work for me if(XDG_DATA_DIRS_ENV) find_path(MEDIAPLAYERINFO_PATH sony_psp.mpi PATHS ENV XDG_DATA_DIRS PATH_SUFFIXES "media-player-info" NO_DEFAULT_PATH ) else() set(XDG_DATA_DIRS "/usr/share") message(STATUS "Warning: environment variable XDG_DATA_DIRS not set, falling back to ${XDG_DATA_DIRS}") find_path(MEDIAPLAYERINFO_PATH sony_psp.mpi PATHS "${XDG_DATA_DIRS}" PATH_SUFFIXES "media-player-info" NO_DEFAULT_PATH ) endif() macro_log_feature(MEDIAPLAYERINFO_PATH "media-player-info" "Enables identification and querying of portable media players" "http://www.freedesktop.org/wiki/Software/media-player-info" FALSE "" "Runtime-only dependency of the udev solid backend. Support for m-p-i is included even if not found during build" ) endif( ) message(STATUS "Building Solid HAL backend." ) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/hal/halblock.cpp backends/hal/halcdrom.cpp backends/hal/haldeviceinterface.cpp backends/hal/halfstabhandling.cpp backends/hal/halgenericinterface.cpp backends/hal/haldevice.cpp backends/hal/halmanager.cpp backends/hal/halopticaldisc.cpp backends/hal/halportablemediaplayer.cpp backends/hal/halstorageaccess.cpp backends/hal/halstorage.cpp backends/hal/halvolume.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/hal/halblock.h backends/hal/halcdrom.h backends/hal/haldevice.h backends/hal/haldeviceinterface.h backends/hal/halgenericinterface.h backends/hal/halmanager.h backends/hal/halopticaldisc.h backends/hal/halportablemediaplayer.h backends/hal/halstorageaccess.h backends/hal/halstorage.h backends/hal/halvolume.h ) # FIXME: this should work on more Unix systems if (CMAKE_SYSTEM_NAME MATCHES Linux) if ( WITH_SOLID_UDISKS2 ) message(STATUS "Building Solid UDisks2 backend." ) add_definitions(-DWITH_SOLID_UDISKS2) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/udisks2/udisksmanager.cpp backends/udisks2/udisksdevice.cpp backends/udisks2/udisksdevicebackend.cpp backends/udisks2/udisksblock.cpp backends/udisks2/udisksstoragevolume.cpp backends/udisks2/udisksdeviceinterface.cpp backends/udisks2/udisksopticaldisc.cpp backends/udisks2/udisksopticaldrive.cpp backends/udisks2/udisksstoragedrive.cpp backends/udisks2/udisksstorageaccess.cpp backends/udisks2/udisksgenericinterface.cpp backends/udisks2/dbus/manager.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/udisks2/udisksblock.h backends/udisks2/udisksdevicebackend.h backends/udisks2/udisksdevice.h backends/udisks2/udisksdeviceinterface.h backends/udisks2/udisksgenericinterface.h backends/udisks2/udisksmanager.h backends/udisks2/udisksopticaldisc.h backends/udisks2/udisksopticaldrive.h backends/udisks2/udisksstorageaccess.h backends/udisks2/udisksstoragedrive.h backends/udisks2/udisksstoragevolume.h backends/udisks2/dbus/manager.h ) else ( ) message(STATUS "Building Solid UDisks backend." ) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/udisks/udisksmanager.cpp backends/udisks/udisksdevice.cpp backends/udisks/udisksblock.cpp backends/udisks/udisksstoragevolume.cpp backends/udisks/udisksdeviceinterface.cpp backends/udisks/udisksopticaldisc.cpp backends/udisks/udisksopticaldrive.cpp backends/udisks/udisksstoragedrive.cpp backends/udisks/udisksstorageaccess.cpp backends/udisks/udisksgenericinterface.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/udisks/udisksblock.h backends/udisks/udisksdevice.h backends/udisks/udisksdeviceinterface.h backends/udisks/udisksgenericinterface.h backends/udisks/udisksopticaldisc.h backends/udisks/udisksopticaldrive.h backends/udisks/udisksmanager.h backends/udisks/udisksstorageaccess.h backends/udisks/udisksstoragedrive.h backends/udisks/udisksstoragevolume.h ) endif ( ) endif () if(APPLE) find_package(IOKit REQUIRED) message(STATUS "-- Building Solid IOKit backend." ) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/iokit/iokitmanager.cpp backends/iokit/iokitdevice.cpp backends/iokit/cfhelper.cpp backends/iokit/iokitdeviceinterface.cpp backends/iokit/iokitgenericinterface.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/iokit/iokitdevice.h backends/iokit/iokitdeviceinterface.h backends/iokit/iokitgenericinterface.h backends/iokit/iokitmanager.h ) endif(APPLE) if(WIN32) include(CheckIncludeFileCXX) check_include_file_cxx(wbemidl.h HAVE_WBEM) FIND_LIBRARY(WBEM_LIBRARIES NAMES wbemuuid wbemuuidd) if(HAVE_WBEM AND WBEM_LIBRARIES) set(HAVE_WBEM True) message(STATUS "Found wbemuuid library: ${WBEM_LIBRARIES}") else() set(HAVE_WBEM False) endif() if(HAVE_WBEM AND NOT WINCE) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_WBEM") message(STATUS "-- Building Solid WMI backend." ) set(solidlite_LIB_SRCS ${solidlite_LIB_SRCS} backends/wmi/wmiblock.cpp backends/wmi/wmicdrom.cpp backends/wmi/wmideviceinterface.cpp backends/wmi/wmigenericinterface.cpp backends/wmi/wmidevice.cpp backends/wmi/wmimanager.cpp backends/wmi/wmiopticaldisc.cpp backends/wmi/wmiportablemediaplayer.cpp backends/wmi/wmiquery.cpp backends/wmi/wmistorageaccess.cpp backends/wmi/wmistorage.cpp backends/wmi/wmivolume.cpp ) set(solidlite_LIB_MOC_HDRS ${solidlite_LIB_MOC_HDRS} backends/wmi/wmiblock.h backends/wmi/wmicdrom.h backends/wmi/wmidevice.h backends/wmi/wmideviceinterface.h backends/wmi/wmigenericinterface.h backends/wmi/wmimanager.h backends/wmi/wmiopticaldisc.h backends/wmi/wmiportablemediaplayer.h backends/wmi/wmistorageaccess.h backends/wmi/wmistorage.h backends/wmi/wmivolume.h ) endif() endif() set(solidlite_OPTIONAL_LIBS) if(WIN32) set(solidlite_OPTIONAL_LIBS ${solidlite_OPTIONAL_LIBS} ${KDEWIN_LIBRARY}) if(HAVE_WBEM) set(solidlite_OPTIONAL_LIBS ${solidlite_OPTIONAL_LIBS} ${WBEM_LIBRARIES}) endif() endif() if(APPLE) set(solidlite_OPTIONAL_LIBS ${IOKIT_LIBRARY}) endif() QT5_WRAP_CPP(solidlite_LIB_MOC_SRCS ${solidlite_LIB_MOC_HDRS} ) add_library(solidlite ${solidlite_LIB_SRCS} ${solidlite_LIB_MOC_SRCS}) # set_target_properties(solidlite PROPERTIES AUTOMOC TRUE) if ( UDEV_FOUND ) set(solidlite_OPTIONAL_LIBS ${solidlite_OPTIONAL_LIBS} ${UDEV_LIBS}) endif ( ) #target_link_libraries(solidlite ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTGUI_LIBRARY} ${solidlite_OPTIONAL_LIBS} ) target_link_libraries(solidlite ${QTLIBS} ${solidlite_OPTIONAL_LIBS}) target_link_libraries(solidlite LINK_INTERFACE_LIBRARIES ${QT_CORE_LIBRARY} ) if (WINCE) target_link_libraries(solidlite ${WCECOMPAT_LIBRARIES}) endif() # set_target_properties(solidlite PROPERTIES # VERSION ${GENERIC_LIB_VERSION} # SOVERSION ${GENERIC_LIB_SOVERSION} # ) #set(lexer_FILE predicate_lexer) #set(parser_FILE predicate_parser) #find_package(Flex) #macro_log_feature(FLEX_FOUND # "Flex" # "Allows the Solid predicate parser to be updated" # "http://flex.sourceforge.net" # FALSE # "" # "Required by the UpdateSolidPredicateParser target (mainly useful for developers)") #find_program(BISON_EXECUTABLE bison) #macro_log_feature(BISON_EXECUTABLE # "Bison" # "Allows the Solid predicate parser to be updated" # "http://www.gnu.org/software/bison" # FALSE # "" # "Required by the UpdateSolidPredicateParser target (mainly useful for developers)") #mark_as_advanced(BISON_EXECUTABLE) # don't show it in the simple view in cmake-gui/ccmake # if (FLEX_EXECUTABLE AND BISON_EXECUTABLE) # # add_custom_target(UpdateSolidPredicateParser # COMMAND ${FLEX_EXECUTABLE} -P Solid -o${lexer_FILE}.c ${lexer_FILE}.l # COMMAND ${BISON_EXECUTABLE} -p Solid -d -b ${parser_FILE} ${parser_FILE}.y # COMMAND ${CMAKE_COMMAND} -E copy ${parser_FILE}.tab.c ${CMAKE_CURRENT_SOURCE_DIR}/${parser_FILE}.c # COMMAND ${CMAKE_COMMAND} -E copy ${parser_FILE}.tab.h ${CMAKE_CURRENT_SOURCE_DIR}/${parser_FILE}.h # COMMAND ${CMAKE_COMMAND} -E remove ${parser_FILE}.tab.c ${parser_FILE}.tab.h # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) # # else (FLEX_EXECUTABLE AND BISON_EXECUTABLE) # add_custom_target(UpdateSolidPredicateParser # COMMAND echo "flex and/or bison not found, so target UpdateSolidPredicateParser inactive") # endif (FLEX_EXECUTABLE AND BISON_EXECUTABLE) cantata-2.2.0/3rdparty/solid-lite/README000066400000000000000000000002001316350454000176140ustar00rootroot00000000000000This is a (cut down) copy of Solid from KDE4.10.1 Only disk/mediaplayer detection is remaining - as this is all Cantata needs. cantata-2.2.0/3rdparty/solid-lite/backends/000077500000000000000000000000001316350454000205165ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/hal/000077500000000000000000000000001316350454000212625ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/hal/halblock.cpp000066400000000000000000000025761316350454000235570ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halblock.h" #include "haldevice.h" using namespace Solid::Backends::Hal; Block::Block(HalDevice *device) : DeviceInterface(device) { } Block::~Block() { } int Block::deviceMajor() const { return m_device->prop("block.major").toInt(); } int Block::deviceMinor() const { return m_device->prop("block.minor").toInt(); } QString Block::device() const { return m_device->prop("block.device").toString(); } //#include "backends/hal/halblock.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halblock.h000066400000000000000000000027011316350454000232120ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_BLOCK_H #define SOLID_BACKENDS_HAL_BLOCK_H #include #include "haldeviceinterface.h" namespace Solid { namespace Backends { namespace Hal { class Block : public DeviceInterface, virtual public Solid::Ifaces::Block { Q_OBJECT Q_INTERFACES(Solid::Ifaces::Block) public: Block(HalDevice *device); virtual ~Block(); virtual int deviceMajor() const; virtual int deviceMinor() const; virtual QString device() const; }; } } } #endif // SOLID_BACKENDS_HAL_BLOCK_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halcdrom.cpp000066400000000000000000000152521316350454000235640ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halcdrom.h" #include #include #include #include #include #include "halfstabhandling.h" using namespace Solid::Backends::Hal; Cdrom::Cdrom(HalDevice *device) : Storage(device), m_ejectInProgress(false) { connect(device, SIGNAL(conditionRaised(QString,QString)), this, SLOT(slotCondition(QString,QString))); m_device->registerAction("eject", this, SLOT(slotEjectRequested()), SLOT(slotEjectDone(int,QString))); } Cdrom::~Cdrom() { } Solid::OpticalDrive::MediumTypes Cdrom::supportedMedia() const { Solid::OpticalDrive::MediumTypes supported; QMap map; map[Solid::OpticalDrive::Cdr] = "storage.cdrom.cdr"; map[Solid::OpticalDrive::Cdrw] = "storage.cdrom.cdrw"; map[Solid::OpticalDrive::Dvd] = "storage.cdrom.dvd"; map[Solid::OpticalDrive::Dvdr] = "storage.cdrom.dvdr"; map[Solid::OpticalDrive::Dvdrw] ="storage.cdrom.dvdrw"; map[Solid::OpticalDrive::Dvdram] ="storage.cdrom.dvdram"; map[Solid::OpticalDrive::Dvdplusr] ="storage.cdrom.dvdplusr"; map[Solid::OpticalDrive::Dvdplusrw] ="storage.cdrom.dvdplusrw"; map[Solid::OpticalDrive::Dvdplusdl] ="storage.cdrom.dvdplusrdl"; map[Solid::OpticalDrive::Dvdplusdlrw] ="storage.cdrom.dvdplusrwdl"; map[Solid::OpticalDrive::Bd] ="storage.cdrom.bd"; map[Solid::OpticalDrive::Bdr] ="storage.cdrom.bdr"; map[Solid::OpticalDrive::Bdre] ="storage.cdrom.bdre"; map[Solid::OpticalDrive::HdDvd] ="storage.cdrom.hddvd"; map[Solid::OpticalDrive::HdDvdr] ="storage.cdrom.hddvdr"; map[Solid::OpticalDrive::HdDvdrw] ="storage.cdrom.hddvdrw"; foreach (const Solid::OpticalDrive::MediumType type, map.keys()) { if (m_device->prop(map[type]).toBool()) { supported|= type; } } return supported; } int Cdrom::readSpeed() const { return m_device->prop("storage.cdrom.read_speed").toInt(); } int Cdrom::writeSpeed() const { return m_device->prop("storage.cdrom.write_speed").toInt(); } QList Cdrom::writeSpeeds() const { QList speeds; QStringList speed_strlist = m_device->prop("storage.cdrom.write_speeds").toStringList(); foreach (const QString &speed_str, speed_strlist) { speeds << speed_str.toInt(); } return speeds; } void Cdrom::slotCondition(const QString &name, const QString &/*reason */) { if (name == "EjectPressed") { emit ejectPressed(m_device->udi()); } } bool Cdrom::eject() { if (m_ejectInProgress) { return false; } m_ejectInProgress = true; m_device->broadcastActionRequested("eject"); if (FstabHandling::isInFstab(m_device->prop("block.device").toString())) { return callSystemEject(); } else { return callHalDriveEject(); } } void Cdrom::slotEjectRequested() { m_ejectInProgress = true; emit ejectRequested(m_device->udi()); } bool Cdrom::callHalDriveEject() { QString udi = m_device->udi(); QString interface = "org.freedesktop.Hal.Device.Storage"; // HACK: Eject doesn't work on cdrom drives when there's a mounted disc, // let's try to workaround this by calling a child volume... if (m_device->prop("storage.removable.media_available").toBool()) { QDBusInterface manager("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", QDBusConnection::systemBus()); QDBusReply reply = manager.call("FindDeviceStringMatch", "info.parent", udi); if (reply.isValid()) { const QStringList udis = reply; if (!udis.isEmpty()) { udi = udis[0]; interface = "org.freedesktop.Hal.Device.Volume"; } } } QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, interface, "Eject"); msg << QStringList(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool Solid::Backends::Hal::Cdrom::callSystemEject() { const QString device = m_device->prop("block.device").toString(); m_process = FstabHandling::callSystemCommand("eject", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=0; } void Cdrom::slotDBusReply(const QDBusMessage &/*reply*/) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } void Cdrom::slotDBusError(const QDBusError &error) { m_ejectInProgress = false; // TODO: Better error reporting here m_device->broadcastActionDone("eject", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } void Solid::Backends::Hal::Cdrom::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (m_ejectInProgress) { m_ejectInProgress = false; if (exitCode==0) { m_device->broadcastActionDone("eject"); } else { m_device->broadcastActionDone("eject", Solid::UnauthorizedOperation, m_process->readAllStandardError()); } } delete m_process; } void Cdrom::slotEjectDone(int error, const QString &errorString) { m_ejectInProgress = false; emit ejectDone(static_cast(error), errorString, m_device->udi()); } //#include "backends/hal/halcdrom.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halcdrom.h000066400000000000000000000044701316350454000232310ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_CDROM_H #define SOLID_BACKENDS_HAL_CDROM_H #include #include "halstorage.h" #include #include #include #include namespace Solid { namespace Backends { namespace Hal { class Cdrom : public Storage, virtual public Solid::Ifaces::OpticalDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDrive) public: Cdrom(HalDevice *device); virtual ~Cdrom(); virtual Solid::OpticalDrive::MediumTypes supportedMedia() const; virtual int readSpeed() const; virtual int writeSpeed() const; virtual QList writeSpeeds() const; virtual bool eject(); Q_SIGNALS: void ejectPressed(const QString &udi); void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void ejectRequested(const QString &udi); private Q_SLOTS: void slotCondition(const QString &name, const QString &reason); void slotDBusReply(const QDBusMessage &reply); void slotDBusError(const QDBusError &error); void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void slotEjectRequested(); void slotEjectDone(int error, const QString &errorString); private: bool callHalDriveEject(); bool callSystemEject(); bool m_ejectInProgress; QProcess *m_process; }; } } } #endif // SOLID_BACKENDS_HAL_CDROM_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/haldevice.cpp000066400000000000000000000704351316350454000237230ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "haldevice.h" #include #include #include #include #include #include #include #include #include "haldeviceinterface.h" #include "halgenericinterface.h" //#include "halprocessor.h" #include "halblock.h" #include "halstorageaccess.h" #include "halstorage.h" #include "halcdrom.h" #include "halvolume.h" #include "halopticaldisc.h" //#include "halcamera.h" #include "halportablemediaplayer.h" //#include "halnetworkinterface.h" //#include "halacadapter.h" //#include "halbattery.h" //#include "halbutton.h" //#include "halaudiointerface.h" //#include "haldvbinterface.h" //#include "halvideo.h" //#include "halserialinterface.h" //#include "halsmartcardreader.h" using namespace Solid::Backends::Hal; // Adapted from KLocale as Solid needs to be Qt-only static QString formatByteSize(double size) { // Per IEC 60027-2 // Binary prefixes //Tebi-byte TiB 2^40 1,099,511,627,776 bytes //Gibi-byte GiB 2^30 1,073,741,824 bytes //Mebi-byte MiB 2^20 1,048,576 bytes //Kibi-byte KiB 2^10 1,024 bytes QString s; // Gibi-byte if ( size >= 1073741824.0 ) { size /= 1073741824.0; if ( size > 1024 ) // Tebi-byte s = QObject::tr("%1 TiB").arg(QLocale().toString(size / 1024.0, 'f', 1)); else s = QObject::tr("%1 GiB").arg(QLocale().toString(size, 'f', 1)); } // Mebi-byte else if ( size >= 1048576.0 ) { size /= 1048576.0; s = QObject::tr("%1 MiB").arg(QLocale().toString(size, 'f', 1)); } // Kibi-byte else if ( size >= 1024.0 ) { size /= 1024.0; s = QObject::tr("%1 KiB").arg(QLocale().toString(size, 'f', 1)); } // Just byte else if ( size > 0 ) { s = QObject::tr("%1 B").arg(QLocale().toString(size, 'f', 1)); } // Nothing else { s = QObject::tr("0 B"); } return s; } class Solid::Backends::Hal::HalDevicePrivate { public: HalDevicePrivate(const QString &udi) : device("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device", QDBusConnection::systemBus()), cacheSynced(false), parent(0) { } void checkCache(const QString &key = QString()); QDBusInterface device; QMap cache; QMap capListCache; QSet invalidKeys; bool cacheSynced; HalDevice *parent; }; Q_DECLARE_METATYPE(ChangeDescription) Q_DECLARE_METATYPE(QList) const QDBusArgument &operator<<(QDBusArgument &arg, const ChangeDescription &change) { arg.beginStructure(); arg << change.key << change.added << change.removed; arg.endStructure(); return arg; } const QDBusArgument &operator>>(const QDBusArgument &arg, ChangeDescription &change) { arg.beginStructure(); arg >> change.key >> change.added >> change.removed; arg.endStructure(); return arg; } HalDevice::HalDevice(const QString &udi) : Device(), d(new HalDevicePrivate(udi)) { qDBusRegisterMetaType(); qDBusRegisterMetaType< QList >(); d->device.connection().connect("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device", "PropertyModified", this, SLOT(slotPropertyModified(int,QList))); d->device.connection().connect("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device", "Condition", this, SLOT(slotCondition(QString,QString))); } HalDevice::~HalDevice() { delete d->parent; delete d; } QString HalDevice::udi() const { return prop("info.udi").toString(); } QString HalDevice::parentUdi() const { return prop("info.parent").toString(); } QString HalDevice::vendor() const { const QString category = prop("info.category").toString(); if (category == QLatin1String("battery")) { return prop("battery.vendor").toString(); } else { return prop("info.vendor").toString(); } } QString HalDevice::product() const { return prop("info.product").toString(); } QString HalDevice::icon() const { QString category = prop("info.category").toString(); if(parentUdi().isEmpty()) { QString formfactor = prop("system.formfactor").toString(); if (formfactor=="laptop") { return "computer-laptop"; } else { return "computer"; } } else if (category=="storage" || category=="storage.cdrom") { if (prop("storage.drive_type").toString()=="floppy") { return "media-floppy"; } else if (prop("storage.drive_type").toString()=="cdrom") { return "drive-optical"; } else if (prop("storage.drive_type").toString()=="sd_mmc") { return "media-flash-sd-mmc"; } else if (prop("storage.hotpluggable").toBool()) { if (prop("storage.bus").toString()=="usb") { if (prop("storage.no_partitions_hint").toBool() || prop("storage.removable.media_size").toLongLong()<4000000000LL) { return "drive-removable-media-usb-pendrive"; } else { return "drive-removable-media-usb"; } } return "drive-removable-media"; } return "drive-harddisk"; } else if (category=="volume" || category=="volume.disc") { QStringList capabilities = prop("info.capabilities").toStringList(); if (capabilities.contains("volume.disc")) { bool has_video = prop("volume.disc.is_vcd").toBool() || prop("volume.disc.is_svcd").toBool() || prop("volume.disc.is_videodvd").toBool(); bool has_audio = prop("volume.disc.has_audio").toBool(); bool recordable = prop("volume.disc.is_blank").toBool() || prop("volume.disc.is_appendable").toBool() || prop("volume.disc.is_rewritable").toBool(); if (has_video) { return "media-optical-video"; } else if (has_audio) { return "media-optical-audio"; } else if (recordable) { return "media-optical-recordable"; } else { return "media-optical"; } } else { if (!d->parent) { d->parent = new HalDevice(parentUdi()); } QString iconName = d->parent->icon(); if (!iconName.isEmpty()) { return iconName; } return "drive-harddisk"; } } /*else if (category=="camera") { return "camera-photo"; } else if (category=="input") { QStringList capabilities = prop("info.capabilities").toStringList(); if (capabilities.contains("input.mouse")) { return "input-mouse"; } else if (capabilities.contains("input.keyboard")) { return "input-keyboard"; } else if (capabilities.contains("input.joystick")) { return "input-gaming"; } else if (capabilities.contains("input.tablet")) { return "input-tablet"; } } */ else if (category=="portable_audio_player") { QStringList protocols = prop("portable_audio_player.access_method.protocols").toStringList(); if (protocols.contains("ipod")) { return "multimedia-player-apple-ipod"; } else { return "multimedia-player"; } } /* else if (category=="battery") { return "battery"; } else if (category=="processor") { return "cpu"; // FIXME: Doesn't follow icon spec } else if (category=="video4linux") { return "camera-web"; } else if (category == "alsa" || category == "oss") { // Sorry about this const_cast, but it's the best way to not copy the code from // AudioInterface. const Hal::AudioInterface audioIface(const_cast(this)); switch (audioIface.soundcardType()) { case Solid::AudioInterface::InternalSoundcard: return QLatin1String("audio-card"); case Solid::AudioInterface::UsbSoundcard: return QLatin1String("audio-card-usb"); case Solid::AudioInterface::FirewireSoundcard: return QLatin1String("audio-card-firewire"); case Solid::AudioInterface::Headset: if (udi().contains("usb", Qt::CaseInsensitive) || audioIface.name().contains("usb", Qt::CaseInsensitive)) { return QLatin1String("audio-headset-usb"); } else { return QLatin1String("audio-headset"); } case Solid::AudioInterface::Modem: return QLatin1String("modem"); } } else if (category == "serial") { // TODO - a serial device can be a modem, or just // a COM port - need a new icon? return QLatin1String("modem"); } else if (category == "smart_card_reader") { return QLatin1String("smart-card-reader"); } */ return QString(); } QStringList HalDevice::emblems() const { QStringList res; if (queryDeviceInterface(Solid::DeviceInterface::StorageAccess)) { bool isEncrypted = prop("volume.fsusage").toString()=="crypto"; const Hal::StorageAccess accessIface(const_cast(this)); if (accessIface.isAccessible()) { if (isEncrypted) { res << "emblem-encrypted-unlocked"; } else { res << "emblem-mounted"; } } else { if (isEncrypted) { res << "emblem-encrypted-locked"; } else { res << "emblem-unmounted"; } } } return res; } QString HalDevice::description() const { QString category = prop("info.category").toString(); if (category=="storage" || category=="storage.cdrom") { return storageDescription(); } else if (category=="volume" || category=="volume.disc") { return volumeDescription(); } /*else if (category=="net.80211") { return QObject::tr("WLAN Interface"); } else if (category=="net.80203") { return QObject::tr("Networking Interface"); } */ else { return product(); } } QVariant HalDevice::prop(const QString &key) const { d->checkCache(key); return d->cache.value(key); } void HalDevicePrivate::checkCache(const QString &key) { if (cacheSynced) { if (key.isEmpty()) { if (invalidKeys.isEmpty()) { return; } } else if (!invalidKeys.contains(key)) { return; } } QDBusReply reply = device.call("GetAllProperties"); if (reply.isValid()) { cache = reply; } else { qWarning() << Q_FUNC_INFO << " error: " << reply.error().name() << ", " << reply.error().message() << endl; cache = QVariantMap(); } invalidKeys.clear(); cacheSynced = true; //qDebug( )<< q << udi() << "failure"; } QMap HalDevice::allProperties() const { d->checkCache(); return d->cache; } bool HalDevice::propertyExists(const QString &key) const { d->checkCache(key); return d->cache.value(key).isValid(); } bool HalDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const { // Special cases not matching with HAL capabilities if (type==Solid::DeviceInterface::GenericInterface) { return true; } else if (type==Solid::DeviceInterface::StorageAccess) { return prop("org.freedesktop.Hal.Device.Volume.method_names").toStringList().contains("Mount") || prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto"); } /*else if (type==Solid::DeviceInterface::Video) { if (!prop("video4linux.device").toString().contains("video" ) ) return false; } */else if (d->capListCache.contains(type)) { return d->capListCache.value(type); } QStringList cap_list = DeviceInterface::toStringList(type); foreach (const QString &cap, cap_list) { QDBusReply reply = d->device.call("QueryCapability", cap); if (!reply.isValid()) { qWarning() << Q_FUNC_INFO << " error: " << reply.error().name() << endl; return false; } if (reply) { d->capListCache.insert(type, true); return true; } } d->capListCache.insert(type, false); return false; } QObject *HalDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type) { if (!queryDeviceInterface(type)) { return 0; } DeviceInterface *iface = 0; switch (type) { case Solid::DeviceInterface::GenericInterface: iface = new GenericInterface(this); break; //case Solid::DeviceInterface::Processor: // iface = new Processor(this); // break; case Solid::DeviceInterface::Block: iface = new Block(this); break; case Solid::DeviceInterface::StorageAccess: iface = new StorageAccess(this); break; case Solid::DeviceInterface::StorageDrive: iface = new Storage(this); break; case Solid::DeviceInterface::OpticalDrive: iface = new Cdrom(this); break; case Solid::DeviceInterface::StorageVolume: iface = new Volume(this); break; case Solid::DeviceInterface::OpticalDisc: iface = new OpticalDisc(this); break; //case Solid::DeviceInterface::Camera: // iface = new Camera(this); // break; case Solid::DeviceInterface::PortableMediaPlayer: iface = new PortableMediaPlayer(this); break; /*case Solid::DeviceInterface::NetworkInterface: iface = new NetworkInterface(this); break; case Solid::DeviceInterface::AcAdapter: iface = new AcAdapter(this); break; case Solid::DeviceInterface::Battery: iface = new Battery(this); break; case Solid::DeviceInterface::Button: iface = new Button(this); break; case Solid::DeviceInterface::AudioInterface: iface = new AudioInterface(this); break; case Solid::DeviceInterface::DvbInterface: iface = new DvbInterface(this); break; case Solid::DeviceInterface::Video: iface = new Video(this); break; case Solid::DeviceInterface::SerialInterface: iface = new SerialInterface(this); break; case Solid::DeviceInterface::SmartCardReader: iface = new SmartCardReader(this); break; case Solid::DeviceInterface::InternetGateway: break; case Solid::DeviceInterface::NetworkShare: break; */ case Solid::DeviceInterface::Unknown: case Solid::DeviceInterface::Last: break; } return iface; } void HalDevice::slotPropertyModified(int /*count */, const QList &changes) { QMap result; foreach (const ChangeDescription &change, changes) { QString key = change.key; bool added = change.added; bool removed = change.removed; Solid::GenericInterface::PropertyChange type = Solid::GenericInterface::PropertyModified; if (added) { type = Solid::GenericInterface::PropertyAdded; } else if (removed) { type = Solid::GenericInterface::PropertyRemoved; } result[key] = type; d->cache.remove(key); if (d->cache.isEmpty()) { d->cacheSynced = false; d->invalidKeys.clear(); } else { d->invalidKeys.insert(key); } } //qDebug() << this << "unsyncing the cache"; emit propertyChanged(result); } void HalDevice::slotCondition(const QString &condition, const QString &reason) { emit conditionRaised(condition, reason); } QString HalDevice::storageDescription() const { QString description; const Storage storageDrive(const_cast(this)); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); bool drive_is_hotpluggable = storageDrive.isHotpluggable(); if (drive_type == Solid::StorageDrive::CdromDrive) { const Cdrom opticalDrive(const_cast(this)); Solid::OpticalDrive::MediumTypes mediumTypes = opticalDrive.supportedMedia(); QString first; QString second; first = QObject::tr("CD-ROM", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdr) first = QObject::tr("CD-R", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdrw) first = QObject::tr("CD-RW", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvd) second = QObject::tr("/DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusr) second = QObject::tr("/DVD+R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusrw) second = QObject::tr("/DVD+RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdr) second = QObject::tr("/DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdrw) second = QObject::tr("/DVD-RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdram) second = QObject::tr("/DVD-RAM", "Second item of %1%2 Drive sentence"); if ((mediumTypes & Solid::OpticalDrive::Dvdr) && (mediumTypes & Solid::OpticalDrive::Dvdplusr)) { if(mediumTypes & Solid::OpticalDrive::Dvdplusdl) second = QObject::tr("/DVD±R DL", "Second item of %1%2 Drive sentence"); else second = QObject::tr("/DVD±R", "Second item of %1%2 Drive sentence"); } if ((mediumTypes & Solid::OpticalDrive::Dvdrw) && (mediumTypes & Solid::OpticalDrive::Dvdplusrw)) { if((mediumTypes & Solid::OpticalDrive::Dvdplusdl) || (mediumTypes & Solid::OpticalDrive::Dvdplusdlrw)) second = QObject::tr("/DVD±RW DL", "Second item of %1%2 Drive sentence"); else second = QObject::tr("/DVD±RW", "Second item of %1%2 Drive sentence"); } if (mediumTypes & Solid::OpticalDrive::Bd) second = QObject::tr("/BD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdr) second = QObject::tr("/BD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdre) second = QObject::tr("/BD-RE", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvd) second = QObject::tr("/HD DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdr) second = QObject::tr("/HD DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdrw) second = QObject::tr("/HD DVD-RW", "Second item of %1%2 Drive sentence"); if (drive_is_hotpluggable) { description = QObject::tr("External %1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); } else { description = QObject::tr("%1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); } return description; } /*if (drive_type == Solid::StorageDrive::Floppy) { if (drive_is_hotpluggable) description = QObject::tr("External Floppy Drive"); else description = QObject::tr("Floppy Drive"); return description; }*/ bool drive_is_removable = storageDrive.isRemovable(); if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { QString size_str = formatByteSize(prop("storage.size").toInt()); if (!size_str.isEmpty()) { if (drive_is_hotpluggable) { description = QObject::tr("%1 External Hard Drive", "%1 is the size").arg(size_str); } else { description = QObject::tr("%1 Hard Drive", "%1 is the size").arg(size_str); } } else { if (drive_is_hotpluggable) description = QObject::tr("External Hard Drive"); else description = QObject::tr("Hard Drive"); } return description; } QString vendormodel_str; QString model = prop("storage.model").toString(); QString vendor = prop("storage.vendor").toString(); if (vendor.isEmpty()) { if (!model.isEmpty()) vendormodel_str = model; } else { if (model.isEmpty()) vendormodel_str = vendor; else vendormodel_str = QObject::tr("%1 %2", "%1 is the vendor, %2 is the model of the device").arg(vendor).arg(model); } if (vendormodel_str.isEmpty()) description = QObject::tr("Drive"); else description = vendormodel_str; return description; } QString HalDevice::volumeDescription() const { QString description; QString volume_label = prop("volume.label").toString(); if (!volume_label.isEmpty()) { return volume_label; } if (!d->parent) { d->parent = new HalDevice(parentUdi()); } const Storage storageDrive(const_cast(d->parent)); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); /* Handle media in optical drives */ if (drive_type == Solid::StorageDrive::CdromDrive) { const OpticalDisc disc(const_cast(this)); switch (disc.discType()) { case Solid::OpticalDisc::UnknownDiscType: case Solid::OpticalDisc::CdRom: description = QObject::tr("CD-ROM"); break; case Solid::OpticalDisc::CdRecordable: if (disc.isBlank()) description = QObject::tr("Blank CD-R"); else description = QObject::tr("CD-R"); break; case Solid::OpticalDisc::CdRewritable: if (disc.isBlank()) description = QObject::tr("Blank CD-RW"); else description = QObject::tr("CD-RW"); break; case Solid::OpticalDisc::DvdRom: description = QObject::tr("DVD-ROM"); break; case Solid::OpticalDisc::DvdRam: if (disc.isBlank()) description = QObject::tr("Blank DVD-RAM"); else description = QObject::tr("DVD-RAM"); break; case Solid::OpticalDisc::DvdRecordable: if (disc.isBlank()) description = QObject::tr("Blank DVD-R"); else description = QObject::tr("DVD-R"); break; case Solid::OpticalDisc::DvdPlusRecordableDuallayer: if (disc.isBlank()) description = QObject::tr("Blank DVD+R Dual-Layer"); else description = QObject::tr("DVD+R Dual-Layer"); break; case Solid::OpticalDisc::DvdRewritable: if (disc.isBlank()) description = QObject::tr("Blank DVD-RW"); else description = QObject::tr("DVD-RW"); break; case Solid::OpticalDisc::DvdPlusRecordable: if (disc.isBlank()) description = QObject::tr("Blank DVD+R"); else description = QObject::tr("DVD+R"); break; case Solid::OpticalDisc::DvdPlusRewritable: if (disc.isBlank()) description = QObject::tr("Blank DVD+RW"); else description = QObject::tr("DVD+RW"); break; case Solid::OpticalDisc::DvdPlusRewritableDuallayer: if (disc.isBlank()) description = QObject::tr("Blank DVD+RW Dual-Layer"); else description = QObject::tr("DVD+RW Dual-Layer"); break; case Solid::OpticalDisc::BluRayRom: description = QObject::tr("BD-ROM"); break; case Solid::OpticalDisc::BluRayRecordable: if (disc.isBlank()) description = QObject::tr("Blank BD-R"); else description = QObject::tr("BD-R"); break; case Solid::OpticalDisc::BluRayRewritable: if (disc.isBlank()) description = QObject::tr("Blank BD-RE"); else description = QObject::tr("BD-RE"); break; case Solid::OpticalDisc::HdDvdRom: description = QObject::tr("HD DVD-ROM"); break; case Solid::OpticalDisc::HdDvdRecordable: if (disc.isBlank()) description = QObject::tr("Blank HD DVD-R"); else description = QObject::tr("HD DVD-R"); break; case Solid::OpticalDisc::HdDvdRewritable: if (disc.isBlank()) description = QObject::tr("Blank HD DVD-RW"); else description = QObject::tr("HD DVD-RW"); break; } /* Special case for pure audio disc */ if (disc.availableContent() == Solid::OpticalDisc::Audio) { description = QObject::tr("Audio CD"); } return description; } bool drive_is_removable = storageDrive.isRemovable(); bool drive_is_hotpluggable = storageDrive.isHotpluggable(); bool drive_is_encrypted_container = prop("volume.fsusage").toString()=="crypto"; QString size_str = formatByteSize(prop("volume.size").toULongLong()); if (drive_is_encrypted_container) { if (!size_str.isEmpty()) { description = QObject::tr("%1 Encrypted Container", "%1 is the size").arg(size_str); } else { description = QObject::tr("Encrypted Container"); } } else if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { if (!size_str.isEmpty()) { if (drive_is_hotpluggable) { description = QObject::tr("%1 External Hard Drive", "%1 is the size").arg(size_str); } else { description = QObject::tr("%1 Hard Drive", "%1 is the size").arg(size_str); } } else { if (drive_is_hotpluggable) description = QObject::tr("External Hard Drive"); else description = QObject::tr("Hard Drive"); } } else { if (drive_is_removable) { description = QObject::tr("%1 Removable Media", "%1 is the size").arg(size_str); } else { description = QObject::tr("%1 Media", "%1 is the size").arg(size_str); } } return description; } //#include "backends/hal/haldevice.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/haldevice.h000066400000000000000000000046421316350454000233650ustar00rootroot00000000000000/* Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_HALDEVICE_H #define SOLID_BACKENDS_HAL_HALDEVICE_H #include class QDBusVariant; namespace Solid { namespace Backends { namespace Hal { class HalManager; class HalDevicePrivate; struct ChangeDescription { QString key; bool added; bool removed; }; class HalDevice : public Solid::Ifaces::Device { Q_OBJECT public: HalDevice(const QString &udi); virtual ~HalDevice(); virtual QString udi() const; virtual QString parentUdi() const; virtual QString vendor() const; virtual QString product() const; virtual QString icon() const; virtual QStringList emblems() const; virtual QString description() const; virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const; virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type); public: QVariant prop(const QString &key) const; QMap allProperties() const; bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); private Q_SLOTS: void slotPropertyModified(int count, const QList &changes); void slotCondition(const QString &condition, const QString &reason); private: QString storageDescription() const; QString volumeDescription() const; HalDevicePrivate *d; }; } } } #endif // SOLID_BACKENDS_HAL_HALDEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/haldeviceinterface.cpp000066400000000000000000000022451316350454000255760ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "haldeviceinterface.h" using namespace Solid::Backends::Hal; DeviceInterface::DeviceInterface(HalDevice *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } //#include "backends/hal/haldeviceinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/haldeviceinterface.h000066400000000000000000000136521316350454000252470ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_DEVICEINTERFACE_H #define SOLID_BACKENDS_HAL_DEVICEINTERFACE_H #include #include "haldevice.h" #include #include namespace Solid { namespace Backends { namespace Hal { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(HalDevice *device); virtual ~DeviceInterface(); protected: HalDevice *m_device; public: inline static QStringList toStringList(Solid::DeviceInterface::Type type) { QStringList list; switch(type) { case Solid::DeviceInterface::GenericInterface: // Doesn't exist with HAL break; //case Solid::DeviceInterface::Processor: // list << "processor"; // break; case Solid::DeviceInterface::Block: list << "block"; break; case Solid::DeviceInterface::StorageAccess: // Doesn't exist with HAL, but let's assume volume always cover this type list << "volume"; break; case Solid::DeviceInterface::StorageDrive: list << "storage"; break; case Solid::DeviceInterface::OpticalDrive: list << "storage.cdrom"; break; case Solid::DeviceInterface::StorageVolume: list << "volume"; break; case Solid::DeviceInterface::OpticalDisc: list << "volume.disc"; break; //case Solid::DeviceInterface::Camera: // list << "camera"; // break; case Solid::DeviceInterface::PortableMediaPlayer: list << "portable_audio_player"; break; /*case Solid::DeviceInterface::NetworkInterface: list << "net"; break; case Solid::DeviceInterface::AcAdapter: list << "ac_adapter"; break; case Solid::DeviceInterface::Battery: list << "battery"; break; case Solid::DeviceInterface::Button: list << "button"; break; case Solid::DeviceInterface::AudioInterface: list << "alsa" << "oss"; break; case Solid::DeviceInterface::DvbInterface: list << "dvb"; break; case Solid::DeviceInterface::Video: list << "video4linux"; break; case Solid::DeviceInterface::SerialInterface: list << "serial"; break; case Solid::DeviceInterface::SmartCardReader: list << "smart_card_reader"; case Solid::DeviceInterface::InternetGateway: list << "internet_gateway"; case Solid::DeviceInterface::NetworkShare: list << "networkshare"; break; */ case Solid::DeviceInterface::Unknown: break; case Solid::DeviceInterface::Last: break; } return list; } inline static Solid::DeviceInterface::Type fromString(const QString &capability) { /*if (capability == "processor") return Solid::DeviceInterface::Processor; else */if (capability == "block") return Solid::DeviceInterface::Block; else if (capability == "storage") return Solid::DeviceInterface::StorageDrive; else if (capability == "storage.cdrom") return Solid::DeviceInterface::OpticalDrive; else if (capability == "volume") return Solid::DeviceInterface::StorageVolume; else if (capability == "volume.disc") return Solid::DeviceInterface::OpticalDisc; /*else if (capability == "camera") return Solid::DeviceInterface::Camera; */ else if (capability == "portable_audio_player") return Solid::DeviceInterface::PortableMediaPlayer; /*else if (capability == "net") return Solid::DeviceInterface::NetworkInterface; else if (capability == "ac_adapter") return Solid::DeviceInterface::AcAdapter; else if (capability == "battery") return Solid::DeviceInterface::Battery; else if (capability == "button") return Solid::DeviceInterface::Button; else if (capability == "alsa" || capability == "oss") return Solid::DeviceInterface::AudioInterface; else if (capability == "dvb") return Solid::DeviceInterface::DvbInterface; else if (capability == "video4linux") return Solid::DeviceInterface::Video; else if (capability == "serial") return Solid::DeviceInterface::SerialInterface; else if (capability == "smart_card_reader") return Solid::DeviceInterface::SmartCardReader; else if (capability == "networkshare") return Solid::DeviceInterface::NetworkShare;*/ else return Solid::DeviceInterface::Unknown; } }; } } } #endif // SOLID_BACKENDS_HAL_DEVICEINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halfstabhandling.cpp000066400000000000000000000124501316350454000252610ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halfstabhandling.h" #include #include #include #include #include #include #include #ifdef HAVE_MNTENT_H #include #elif defined(HAVE_SYS_MNTENT_H) #include #endif #ifdef Q_OS_SOLARIS #define FSTAB "/etc/vfstab" #else #define FSTAB "/etc/fstab" #endif typedef QMultiHash QStringMultiHash; SOLID_GLOBAL_STATIC(QStringMultiHash, globalMountPointsCache) QString _k_resolveSymLink(const QString &filename) { QString resolved = filename; QString tmp = QFile::symLinkTarget(filename); while (!tmp.isEmpty()) { resolved = tmp; tmp = QFile::symLinkTarget(resolved); } return resolved; } bool _k_isNetworkFileSystem(const QString &fstype, const QString &devName) { if (fstype == "nfs" || fstype == "nfs4" || fstype == "smbfs" || fstype == "cifs" || devName.startsWith(QLatin1String("//"))) { return true; } return false; } void _k_updateMountPointsCache() { static bool firstCall = true; static QTime elapsedTime; if (firstCall) { firstCall = false; elapsedTime.start(); } else if (elapsedTime.elapsed()>10000) { elapsedTime.restart(); } else { return; } globalMountPointsCache->clear(); #ifdef HAVE_SETMNTENT struct mntent *fstab; if ((fstab = setmntent(FSTAB, "r")) == 0) { return; } struct mntent *fe; while ((fe = getmntent(fstab)) != 0) { if (!_k_isNetworkFileSystem(fe->mnt_type, fe->mnt_fsname)) { const QString device = _k_resolveSymLink(QFile::decodeName(fe->mnt_fsname)); const QString mountpoint = _k_resolveSymLink(QFile::decodeName(fe->mnt_dir)); globalMountPointsCache->insert(device, mountpoint); } } endmntent(fstab); #else QFile fstab(FSTAB); if (!fstab.open(QIODevice::ReadOnly)) { return; } QTextStream stream(&fstab); QString line; while (!stream.atEnd()) { line = stream.readLine().simplified(); if (line.isEmpty() || line.startsWith('#')) { continue; } // not empty or commented out by '#' const QStringList items = line.split(' '); #ifdef Q_OS_SOLARIS if (items.count() < 5) { continue; } #else if (items.count() < 4) { continue; } #endif //prevent accessing a blocking directory if (!_k_isNetworkFileSystem(items.at(2), items.at(0))) { const QString device = _k_resolveSymLink(items.at(0)); const QString mountpoint = _k_resolveSymLink(items.at(1)); globalMountPointsCache->insert(device, mountpoint); } } fstab.close(); #endif } bool Solid::Backends::Hal::FstabHandling::isInFstab(const QString &device) { _k_updateMountPointsCache(); const QString deviceToFind = _k_resolveSymLink(device); return globalMountPointsCache->contains(deviceToFind); } QStringList Solid::Backends::Hal::FstabHandling::possibleMountPoints(const QString &device) { _k_updateMountPointsCache(); const QString deviceToFind = _k_resolveSymLink(device); return globalMountPointsCache->values(deviceToFind); } QProcess *Solid::Backends::Hal::FstabHandling::callSystemCommand(const QString &commandName, const QStringList &args, QObject *obj, const char *slot) { QStringList env = QProcess::systemEnvironment(); env.replaceInStrings(QRegExp("^PATH=(.*)", Qt::CaseInsensitive), "PATH=/sbin:/bin:/usr/sbin/:/usr/bin"); QProcess *process = new QProcess(obj); QObject::connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), obj, slot); process->setEnvironment(env); process->start(commandName, args); if (process->waitForStarted()) { return process; } else { delete process; return 0; } } QProcess *Solid::Backends::Hal::FstabHandling::callSystemCommand(const QString &commandName, const QString &device, QObject *obj, const char *slot) { return callSystemCommand(commandName, QStringList() << device, obj, slot); } cantata-2.2.0/3rdparty/solid-lite/backends/hal/halfstabhandling.h000066400000000000000000000032361316350454000247300ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_FSTABHANDLING_H #define SOLID_BACKENDS_HAL_FSTABHANDLING_H #include class QProcess; class QObject; namespace Solid { namespace Backends { namespace Hal { class FstabHandling { public: static bool isInFstab(const QString &device); static QStringList possibleMountPoints(const QString &device); static QProcess *callSystemCommand(const QString &commandName, const QStringList &args, QObject *obj, const char *slot); static QProcess *callSystemCommand(const QString &commandName, const QString &device, QObject *obj, const char *slot); }; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/hal/halgenericinterface.cpp000066400000000000000000000033611316350454000257530ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halgenericinterface.h" #include "haldevice.h" using namespace Solid::Backends::Hal; GenericInterface::GenericInterface(HalDevice *device) : DeviceInterface(device) { connect(device, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); connect(device, SIGNAL(conditionRaised(QString,QString)), this, SIGNAL(conditionRaised(QString,QString))); } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->prop(key); } QMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } //#include "backends/hal/halgenericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halgenericinterface.h000066400000000000000000000034631316350454000254230ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_GENERICINTERFACE_H #define SOLID_BACKENDS_HAL_GENERICINTERFACE_H #include #include #include "haldeviceinterface.h" namespace Solid { namespace Backends { namespace Hal { class HalDevice; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(HalDevice *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif // SOLID_BACKENDS_HAL_GENERICINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halmanager.cpp000066400000000000000000000134271316350454000240740ustar00rootroot00000000000000/* Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halmanager.h" #include "haldevice.h" #include "haldeviceinterface.h" #include #include #include #include using namespace Solid::Backends::Hal; class Solid::Backends::Hal::HalManagerPrivate { public: HalManagerPrivate() : manager("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", QDBusConnection::systemBus()), cacheSynced(false) { } QDBusInterface manager; QList devicesCache; bool cacheSynced; QSet supportedInterfaces; }; HalManager::HalManager(QObject *parent) : DeviceManager(parent), d(new HalManagerPrivate()) { d->manager.connection().connect("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", "DeviceAdded", this, SLOT(slotDeviceAdded(QString))); d->manager.connection().connect("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", "DeviceRemoved", this, SLOT(slotDeviceRemoved(QString))); d->supportedInterfaces << Solid::DeviceInterface::GenericInterface //<< Solid::DeviceInterface::Processor << Solid::DeviceInterface::Block << Solid::DeviceInterface::StorageAccess << Solid::DeviceInterface::StorageDrive << Solid::DeviceInterface::OpticalDrive << Solid::DeviceInterface::StorageVolume << Solid::DeviceInterface::OpticalDisc //<< Solid::DeviceInterface::Camera << Solid::DeviceInterface::PortableMediaPlayer /*<< Solid::DeviceInterface::NetworkInterface << Solid::DeviceInterface::AcAdapter << Solid::DeviceInterface::Battery << Solid::DeviceInterface::Button << Solid::DeviceInterface::AudioInterface << Solid::DeviceInterface::DvbInterface << Solid::DeviceInterface::Video << Solid::DeviceInterface::SerialInterface << Solid::DeviceInterface::SmartCardReader*/; } HalManager::~HalManager() { delete d; } QString HalManager::udiPrefix() const { return "/org/freedesktop/Hal"; } QSet HalManager::supportedInterfaces() const { return d->supportedInterfaces; } QStringList HalManager::allDevices() { if (d->cacheSynced) { return d->devicesCache; } QDBusReply reply = d->manager.call("GetAllDevices"); if (!reply.isValid()) { qWarning() << Q_FUNC_INFO << " error: " << reply.error().name() << endl; return QStringList(); } d->devicesCache = reply; d->cacheSynced = true; return reply; } bool HalManager::deviceExists(const QString &udi) { if (d->devicesCache.contains(udi)) { return true; } else if (d->cacheSynced) { return false; } QDBusReply reply = d->manager.call("DeviceExists", udi); if (!reply.isValid()) { qWarning() << Q_FUNC_INFO << " error: " << reply.error().name() << endl; return false; } if (reply) { d->devicesCache.append(udi); } return reply; } QStringList HalManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type) { if ((parentUdi.isEmpty()) && (type == Solid::DeviceInterface::Unknown)) { return allDevices(); } QStringList result; foreach (const QString &udi, allDevices()) { HalDevice device(udi); if ((!parentUdi.isEmpty()) && (parentUdi != device.parentUdi())) { continue; } if ((type != Solid::DeviceInterface::Unknown) && (!device.queryDeviceInterface(type))) { continue; } result << udi; } return result; } QObject *HalManager::createDevice(const QString &udi) { if (deviceExists(udi)) { return new HalDevice(udi); } else { return 0; } } void HalManager::slotDeviceAdded(const QString &udi) { d->devicesCache.append(udi); emit deviceAdded(udi); } void HalManager::slotDeviceRemoved(const QString &udi) { d->devicesCache.removeAll(udi); emit deviceRemoved(udi); } //#include "backends/hal/halmanager.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halmanager.h000066400000000000000000000036561316350454000235440ustar00rootroot00000000000000/* Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_HALMANAGER_H #define SOLID_BACKENDS_HAL_HALMANAGER_H #include #include #include #include namespace Solid { namespace Backends { namespace Hal { class HalManagerPrivate; class HalManager : public Solid::Ifaces::DeviceManager { Q_OBJECT public: HalManager(QObject *parent); virtual ~HalManager(); virtual QString udiPrefix() const ; virtual QSet supportedInterfaces() const; bool deviceExists(const QString &udi); virtual QStringList allDevices(); virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type); virtual QObject *createDevice(const QString &udi); private Q_SLOTS: void slotDeviceAdded(const QString &udi); void slotDeviceRemoved(const QString &udi); private: HalManagerPrivate *d; }; } } } #endif // SOLID_BACKENDS_HAL_HALMANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halopticaldisc.cpp000066400000000000000000000100771316350454000247560ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halopticaldisc.h" using namespace Solid::Backends::Hal; OpticalDisc::OpticalDisc(HalDevice *device) : Volume(device) { } OpticalDisc::~OpticalDisc() { } Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const { Solid::OpticalDisc::ContentTypes content; QMap map; map[Solid::OpticalDisc::Audio] = "volume.disc.has_audio"; map[Solid::OpticalDisc::Data] = "volume.disc.has_data"; map[Solid::OpticalDisc::VideoCd] = "volume.disc.is_vcd"; map[Solid::OpticalDisc::SuperVideoCd] = "volume.disc.is_svcd"; map[Solid::OpticalDisc::VideoDvd] ="volume.disc.is_videodvd"; map[Solid::OpticalDisc::VideoBluRay] ="volume.disc.is_blurayvideo"; foreach (const Solid::OpticalDisc::ContentType type, map.keys()) { if (m_device->prop(map[type]).toBool()) { content|= type; } } return content; } Solid::OpticalDisc::DiscType OpticalDisc::discType() const { QString type = m_device->prop("volume.disc.type").toString(); if (type == "cd_rom") { return Solid::OpticalDisc::CdRom; } else if (type == "cd_r") { return Solid::OpticalDisc::CdRecordable; } else if (type == "cd_rw") { return Solid::OpticalDisc::CdRewritable; } else if (type == "dvd_rom") { return Solid::OpticalDisc::DvdRom; } else if (type == "dvd_ram") { return Solid::OpticalDisc::DvdRam; } else if (type == "dvd_r") { return Solid::OpticalDisc::DvdRecordable; } else if (type == "dvd_rw") { return Solid::OpticalDisc::DvdRewritable; } else if (type == "dvd_plus_r") { return Solid::OpticalDisc::DvdPlusRecordable; } else if (type == "dvd_plus_rw") { return Solid::OpticalDisc::DvdPlusRewritable; } else if (type == "dvd_plus_r_dl") { return Solid::OpticalDisc::DvdPlusRecordableDuallayer; } else if (type == "dvd_plus_rw_dl") { return Solid::OpticalDisc::DvdPlusRewritableDuallayer; } else if (type == "bd_rom") { return Solid::OpticalDisc::BluRayRom; } else if (type == "bd_r") { return Solid::OpticalDisc::BluRayRecordable; } else if (type == "bd_re") { return Solid::OpticalDisc::BluRayRewritable; } else if (type == "hddvd_rom") { return Solid::OpticalDisc::HdDvdRom; } else if (type == "hddvd_r") { return Solid::OpticalDisc::HdDvdRecordable; } else if (type == "hddvd_rw") { return Solid::OpticalDisc::HdDvdRewritable; } else { return Solid::OpticalDisc::UnknownDiscType; } } bool OpticalDisc::isAppendable() const { return m_device->prop("volume.disc.is_appendable").toBool(); } bool OpticalDisc::isBlank() const { return m_device->prop("volume.disc.is_blank").toBool(); } bool OpticalDisc::isRewritable() const { return m_device->prop("volume.disc.is_rewritable").toBool(); } qulonglong OpticalDisc::capacity() const { return m_device->prop("volume.disc.capacity").toULongLong(); } //#include "backends/hal/halopticaldisc.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halopticaldisc.h000066400000000000000000000032221316350454000244150ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_OPTICALDISC_H #define SOLID_BACKENDS_HAL_OPTICALDISC_H #include #include "halvolume.h" namespace Solid { namespace Backends { namespace Hal { class OpticalDisc : public Volume, virtual public Solid::Ifaces::OpticalDisc { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDisc) public: OpticalDisc(HalDevice *device); virtual ~OpticalDisc(); virtual Solid::OpticalDisc::ContentTypes availableContent() const; virtual Solid::OpticalDisc::DiscType discType() const; virtual bool isAppendable() const; virtual bool isBlank() const; virtual bool isRewritable() const; virtual qulonglong capacity() const; }; } } } #endif // SOLID_BACKENDS_HAL_OPTICALDISC_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halportablemediaplayer.cpp000066400000000000000000000042061316350454000265020ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halportablemediaplayer.h" using namespace Solid::Backends::Hal; PortableMediaPlayer::PortableMediaPlayer(HalDevice *device) : DeviceInterface(device) { } PortableMediaPlayer::~PortableMediaPlayer() { } QStringList PortableMediaPlayer::supportedProtocols() const { return m_device->prop("portable_audio_player.access_method.protocols").toStringList(); } QStringList PortableMediaPlayer::supportedDrivers(QString protocol) const { QStringList drivers = m_device->prop("portable_audio_player.access_method.drivers").toStringList(); if(protocol.isNull()) return drivers; QStringList returnedDrivers; QString temp; for(int i = 0; i < drivers.size(); i++) { temp = drivers.at(i); if(m_device->prop("portable_audio_player." + temp + ".protocol") == protocol) returnedDrivers << temp; } return returnedDrivers; } QVariant PortableMediaPlayer::driverHandle(const QString &driver) const { if (driver=="mtp") { return m_device->prop("usb.serial"); } // TODO: Fill in the blank for other drivers return QVariant(); } //#include "backends/hal/halportablemediaplayer.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halportablemediaplayer.h000066400000000000000000000034071316350454000261510ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_PORTABLEMEDIAPLAYER_H #define SOLID_BACKENDS_HAL_PORTABLEMEDIAPLAYER_H #include #include "haldeviceinterface.h" #include namespace Solid { namespace Backends { namespace Hal { class HalDevice; class PortableMediaPlayer : public DeviceInterface, virtual public Solid::Ifaces::PortableMediaPlayer { Q_OBJECT Q_INTERFACES(Solid::Ifaces::PortableMediaPlayer) public: PortableMediaPlayer(HalDevice *device); virtual ~PortableMediaPlayer(); virtual QStringList supportedProtocols() const; virtual QStringList supportedDrivers(QString protocol = QString()) const; virtual QVariant driverHandle(const QString &driver) const; }; } } } #endif // SOLID_BACKENDS_HAL_PORTABLEMEDIAPLAYER_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halstorage.cpp000066400000000000000000000055211316350454000241220ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halstorage.h" using namespace Solid::Backends::Hal; Storage::Storage(HalDevice *device) : Block(device) { } Storage::~Storage() { } Solid::StorageDrive::Bus Storage::bus() const { QString bus = m_device->prop("storage.bus").toString(); if (bus=="ide") { return Solid::StorageDrive::Ide; } else if (bus=="usb") { return Solid::StorageDrive::Usb; } else if (bus=="ieee1394") { return Solid::StorageDrive::Ieee1394; } else if (bus=="scsi") { return Solid::StorageDrive::Scsi; } else if (bus=="sata") { return Solid::StorageDrive::Sata; } else { return Solid::StorageDrive::Platform; } } Solid::StorageDrive::DriveType Storage::driveType() const { QString type = m_device->prop("storage.drive_type").toString(); if (type=="disk") { return Solid::StorageDrive::HardDisk; } else if (type=="cdrom") { return Solid::StorageDrive::CdromDrive; } else if (type=="floppy") { return Solid::StorageDrive::Floppy; } else if (type=="tape") { return Solid::StorageDrive::Tape; } else if (type=="compact_flash") { return Solid::StorageDrive::CompactFlash; } else if (type=="memory_stick") { return Solid::StorageDrive::MemoryStick; } else if (type=="smart_media") { return Solid::StorageDrive::SmartMedia; } else if (type=="sd_mmc") { return Solid::StorageDrive::SdMmc; } else { return Solid::StorageDrive::HardDisk; } } bool Storage::isRemovable() const { return m_device->prop("storage.removable").toBool(); } bool Storage::isHotpluggable() const { return m_device->prop("storage.hotpluggable").toBool(); } qulonglong Storage::size() const { return m_device->prop("storage.size").toULongLong(); } //#include "backends/hal/halstorage.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halstorage.h000066400000000000000000000031051316350454000235630ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_STORAGE_H #define SOLID_BACKENDS_HAL_STORAGE_H #include #include "halblock.h" namespace Solid { namespace Backends { namespace Hal { class Storage : public Block, virtual public Solid::Ifaces::StorageDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageDrive) public: Storage(HalDevice *device); virtual ~Storage(); virtual Solid::StorageDrive::Bus bus() const; virtual Solid::StorageDrive::DriveType driveType() const; virtual bool isRemovable() const; virtual bool isHotpluggable() const; virtual qulonglong size() const; }; } } } #endif // SOLID_BACKENDS_HAL_STORAGE_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halstorageaccess.cpp000066400000000000000000000436251316350454000253130ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halstorageaccess.h" #include "halfstabhandling.h" #include "../../genericinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_FREEBSD #include #endif using namespace Solid::Backends::Hal; StorageAccess::StorageAccess(HalDevice *device) : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_ejectInProgress(false), m_passphraseRequested(false) { connect(device, SIGNAL(propertyChanged(QMap)), this, SLOT(slotPropertyChanged(QMap))); // Delay connecting to DBus signals to avoid the related time penalty // in hot paths such as predicate matching QTimer::singleShot(0, this, SLOT(connectDBusSignals())); } StorageAccess::~StorageAccess() { } void StorageAccess::connectDBusSignals() { m_device->registerAction("setup", this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int,QString))); m_device->registerAction("teardown", this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int,QString))); m_device->registerAction("eject", this, SLOT(slotEjectRequested()), SLOT(slotEjectDone(int,QString))); } void StorageAccess::slotSetupDone(int error, const QString &errorString) { m_setupInProgress = false; emit setupDone(static_cast(error), errorString, m_device->udi()); } void StorageAccess::slotTeardownDone(int error, const QString &errorString) { m_teardownInProgress = false; emit teardownDone(static_cast(error), errorString, m_device->udi()); } void StorageAccess::slotEjectDone(int error, const QString &errorString) { m_ejectInProgress = false; emit ejectDone(static_cast(error), errorString, m_device->udi()); } bool StorageAccess::isAccessible() const { if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { // Might be a bit slow, but I see no cleaner way to do this with HAL... QDBusInterface manager("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", QDBusConnection::systemBus()); QDBusReply reply = manager.call("FindDeviceStringMatch", "volume.crypto_luks.clear.backing_volume", m_device->udi()); QStringList list = reply; return reply.isValid() && !list.isEmpty(); } else { return m_device->prop("volume.is_mounted").toBool(); } } QString StorageAccess::filePath() const { QString result = m_device->prop("volume.mount_point").toString(); if (result.isEmpty()) { QStringList mountpoints = FstabHandling::possibleMountPoints(m_device->prop("block.device").toString()); if (mountpoints.size()==1) { result = mountpoints.first(); } } return result; } bool StorageAccess::isIgnored() const { HalDevice lock("/org/freedesktop/Hal/devices/computer"); bool isLocked = lock.prop("info.named_locks.Global.org.freedesktop.Hal.Device.Storage.locked").toBool(); if (m_device->prop("volume.ignore").toBool() || isLocked ){ return true; } const QString mount_point = StorageAccess(m_device).filePath(); const bool mounted = m_device->prop("volume.is_mounted").toBool(); if (!mounted) { return false; } else if (mount_point.startsWith(QLatin1String("/media/")) || mount_point.startsWith(QLatin1String("/mnt/"))) { return false; } /* Now be a bit more aggressive on what we want to ignore, * the user generally need to check only what's removable or in /media * the volumes mounted to make the system (/, /boot, /var, etc.) * are useless to him. */ Solid::Device drive(m_device->prop("block.storage_device").toString()); const bool removable = drive.as()->property("storage.removable").toBool(); const bool hotpluggable = drive.as()->property("storage.hotpluggable").toBool(); return !removable && !hotpluggable; } bool StorageAccess::setup() { if (m_teardownInProgress || m_setupInProgress || isAccessible()) { return false; } m_setupInProgress = true; m_device->broadcastActionRequested("setup"); if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { return requestPassphrase(); } else if (FstabHandling::isInFstab(m_device->prop("block.device").toString())) { return callSystemMount(); } else { return callHalVolumeMount(); } } bool StorageAccess::teardown() { if (m_teardownInProgress || m_setupInProgress || !isAccessible()) { return false; } m_teardownInProgress = true; m_device->broadcastActionRequested("teardown"); if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { return callCryptoTeardown(); } else if (FstabHandling::isInFstab(m_device->prop("block.device").toString())) { return callSystemUnmount(); } else { return callHalVolumeUnmount(); } } void StorageAccess::slotPropertyChanged(const QMap &changes) { if (changes.contains("volume.is_mounted")) { emit accessibilityChanged(isAccessible(), m_device->udi()); } } void StorageAccess::slotDBusReply(const QDBusMessage &/*reply*/) { if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown"); HalDevice drive(m_device->prop("block.storage_device").toString()); if (drive.prop("storage.drive_type").toString()!="cdrom" && drive.prop("storage.requires_eject").toBool()) { QString devnode = m_device->prop("block.device").toString(); #if defined(Q_OS_OPENBSD) QString program = "cdio"; QStringList args; args << "-f" << devnode << "eject"; #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) devnode.remove("/dev/").replace("([0-9]).", "\\1"); QString program = "cdcontrol"; QStringList args; args << "-f" << devnode << "eject"; #else QString program = "eject"; QStringList args; args << devnode; #endif m_ejectInProgress = true; m_device->broadcastActionRequested("eject"); m_process = FstabHandling::callSystemCommand("eject", args, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); } } else if (m_ejectInProgress) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } } void StorageAccess::slotDBusError(const QDBusError &error) { // TODO: Better error reporting here if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } else if (m_ejectInProgress) { m_ejectInProgress = false; m_device->broadcastActionDone("eject", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } } void Solid::Backends::Hal::StorageAccess::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (m_setupInProgress) { m_setupInProgress = false; if (exitCode==0) { m_device->broadcastActionDone("setup"); } else { m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation, m_process->readAllStandardError()); } } else if (m_teardownInProgress) { m_teardownInProgress = false; if (exitCode==0) { m_device->broadcastActionDone("teardown"); } else { m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation, m_process->readAllStandardError()); } } else if (m_ejectInProgress) { if (exitCode==0) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } else { callHalVolumeEject(); } } delete m_process; } void StorageAccess::slotSetupRequested() { m_setupInProgress = true; emit setupRequested(m_device->udi()); } void StorageAccess::slotTeardownRequested() { m_teardownInProgress = true; emit teardownRequested(m_device->udi()); } void StorageAccess::slotEjectRequested() { m_ejectInProgress = true; } QString generateReturnObjectPath() { static int number = 1; return "/org/kde/solid/HalStorageAccess_"+QString::number(number++); } bool StorageAccess::requestPassphrase() { QString udi = m_device->udi(); QString returnService = QDBusConnection::sessionBus().baseService(); m_lastReturnObject = generateReturnObjectPath(); QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots); QWidget *activeWindow = QApplication::activeWindow(); uint wId = 0; if (activeWindow!=0) { wId = (uint)activeWindow->winId(); } QString appId = QCoreApplication::applicationName(); QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer"); QDBusReply reply = soliduiserver.call("showPassphraseDialog", udi, returnService, m_lastReturnObject, wId, appId); m_passphraseRequested = reply.isValid(); if (!m_passphraseRequested) { qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error(); } return m_passphraseRequested; } void StorageAccess::passphraseReply(const QString &passphrase) { if (m_passphraseRequested) { QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject); m_passphraseRequested = false; if (!passphrase.isEmpty()) { callCryptoSetup(passphrase); } else { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } } } bool StorageAccess::callHalVolumeMount() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume", "Mount"); // HAL 0.5.12 supports using alternative drivers for the same filesystem. // This is mainly used to integrate the ntfs-3g driver. // Unfortunately, the primary driver gets used unless we // specify some other driver (fstype) to the Mount method. // TODO: Allow the user to choose the driver. QString fstype = m_device->prop("volume.fstype").toString(); QStringList halOptions = m_device->prop("volume.mount.valid_options").toStringList(); QString alternativePreferred = m_device->prop("volume.fstype.alternative.preferred").toString(); if (!alternativePreferred.isEmpty()) { QStringList alternativeFstypes = m_device->prop("volume.fstype.alternative").toStringList(); if (alternativeFstypes.contains(alternativePreferred)) { fstype = alternativePreferred; halOptions = m_device->prop("volume.mount."+fstype+".valid_options").toStringList(); } } QStringList options; #ifdef Q_OS_FREEBSD QString uid="-u="; #else QString uid="uid="; #endif if (halOptions.contains(uid)) { options << uid+QString::number(::getuid()); } #ifdef Q_OS_FREEBSD char *cType; if ( fstype=="vfat" && halOptions.contains("-L=")) { if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) options << "-L="+QString(cType); } else if ( (fstype.startsWith(QLatin1String("ntfs")) || fstype=="iso9660" || fstype=="udf") && halOptions.contains("-C=") ) { if ((cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) options << "-C="+QString(nl_langinfo(CODESET)); } #else if (fstype=="vfat" || fstype=="ntfs" || fstype=="iso9660" || fstype=="udf" ) { if (halOptions.contains("utf8")) options<<"utf8"; else if (halOptions.contains("iocharset=")) options<<"iocharset=utf8"; if (halOptions.contains("shortname=")) options<<"shortname=mixed"; if (halOptions.contains("flush")) options<<"flush"; } // pass our locale to the ntfs-3g driver so it can translate local characters else if ( halOptions.contains("locale=") ) { // have to obtain LC_CTYPE as returned by the `locale` command // check in the same order as `locale` does char *cType; if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) { options << "locale="+QString(cType); } } #endif msg << "" << fstype << options; return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callHalVolumeUnmount() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume", "Unmount"); msg << QStringList(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callHalVolumeEject() { QString udi = m_device->udi(); QString interface = "org.freedesktop.Hal.Device.Volume"; QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, interface, "Eject"); msg << QStringList(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool Solid::Backends::Hal::StorageAccess::callSystemMount() { const QString device = m_device->prop("block.device").toString(); m_process = FstabHandling::callSystemCommand("mount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=0; } bool Solid::Backends::Hal::StorageAccess::callSystemUnmount() { const QString device = m_device->prop("block.device").toString(); m_process = FstabHandling::callSystemCommand("umount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=0; } void StorageAccess::callCryptoSetup(const QString &passphrase) { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume.Crypto", "Setup"); msg << passphrase; c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callCryptoTeardown() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume.Crypto", "Teardown"); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } //#include "backends/hal/halstorageaccess.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halstorageaccess.h000066400000000000000000000062721316350454000247550ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_STORAGEACCESS_H #define SOLID_BACKENDS_HAL_STORAGEACCESS_H #include #include "haldeviceinterface.h" #include #include #include #include namespace Solid { namespace Backends { namespace Hal { class StorageAccess : public DeviceInterface, virtual public Solid::Ifaces::StorageAccess { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageAccess) public: StorageAccess(HalDevice *device); virtual ~StorageAccess(); virtual bool isAccessible() const; virtual QString filePath() const; virtual bool isIgnored() const; virtual bool setup(); virtual bool teardown(); Q_SIGNALS: void accessibilityChanged(bool accessible, const QString &udi); void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void setupRequested(const QString &udi); void teardownRequested(const QString &udi); private Q_SLOTS: void connectDBusSignals(); void slotPropertyChanged(const QMap &changes); void slotDBusReply(const QDBusMessage &reply); void slotDBusError(const QDBusError &error); void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void slotSetupRequested(); void slotTeardownRequested(); void slotEjectRequested(); void slotSetupDone(int error, const QString &errorString); void slotTeardownDone(int error, const QString &errorString); void slotEjectDone(int error, const QString &errorString); public Q_SLOTS: Q_SCRIPTABLE Q_NOREPLY void passphraseReply(const QString &passphrase); private: bool callHalVolumeMount(); bool callHalVolumeUnmount(); bool callHalVolumeEject(); bool callSystemMount(); bool callSystemUnmount(); bool requestPassphrase(); void callCryptoSetup(const QString &passphrase); bool callCryptoTeardown(); private: bool m_setupInProgress; bool m_teardownInProgress; bool m_ejectInProgress; bool m_passphraseRequested; QString m_lastReturnObject; QProcess *m_process; }; } } } #endif // SOLID_BACKENDS_HAL_STORAGEACCESS_H cantata-2.2.0/3rdparty/solid-lite/backends/hal/halvolume.cpp000066400000000000000000000067451316350454000237760ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halvolume.h" #include "halstorageaccess.h" #include "../../genericinterface.h" using namespace Solid::Backends::Hal; Volume::Volume(HalDevice *device) : Block(device) { } Volume::~Volume() { } bool Volume::isIgnored() const { static HalDevice lock("/org/freedesktop/Hal/devices/computer"); bool isLocked = lock.prop("info.named_locks.Global.org.freedesktop.Hal.Device.Storage.locked").toBool(); if (m_device->prop("volume.ignore").toBool() || isLocked ){ return true; } const QString mount_point = StorageAccess(m_device).filePath(); const bool mounted = m_device->prop("volume.is_mounted").toBool(); if (!mounted) { return false; } else if (mount_point.startsWith(QLatin1String("/media/")) || mount_point.startsWith(QLatin1String("/mnt/"))) { return false; } /* Now be a bit more aggressive on what we want to ignore, * the user generally need to check only what's removable or in /media * the volumes mounted to make the system (/, /boot, /var, etc.) * are useless to him. */ Solid::Device drive(m_device->prop("block.storage_device").toString()); const bool removable = drive.as()->property("storage.removable").toBool(); const bool hotpluggable = drive.as()->property("storage.hotpluggable").toBool(); return !removable && !hotpluggable; } Solid::StorageVolume::UsageType Volume::usage() const { QString usage = m_device->prop("volume.fsusage").toString(); if (usage == "filesystem") { return Solid::StorageVolume::FileSystem; } else if (usage == "partitiontable") { return Solid::StorageVolume::PartitionTable; } else if (usage == "raid") { return Solid::StorageVolume::Raid; } else if (usage == "crypto") { return Solid::StorageVolume::Encrypted; } else if (usage == "unused") { return Solid::StorageVolume::Unused; } else { return Solid::StorageVolume::Other; } } QString Volume::fsType() const { return m_device->prop("volume.fstype").toString(); } QString Volume::label() const { return m_device->prop("volume.label").toString(); } QString Volume::uuid() const { return m_device->prop("volume.uuid").toString(); } qulonglong Volume::size() const { return m_device->prop("volume.size").toULongLong(); } QString Solid::Backends::Hal::Volume::encryptedContainerUdi() const { return m_device->prop("volume.crypto_luks.clear.backing_volume").toString(); } //#include "backends/hal/halvolume.moc" cantata-2.2.0/3rdparty/solid-lite/backends/hal/halvolume.h000066400000000000000000000031751316350454000234350ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_HAL_VOLUME_H #define SOLID_BACKENDS_HAL_VOLUME_H #include #include "halblock.h" namespace Solid { namespace Backends { namespace Hal { class Volume : public Block, virtual public Solid::Ifaces::StorageVolume { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageVolume) public: Volume(HalDevice *device); virtual ~Volume(); virtual bool isIgnored() const; virtual Solid::StorageVolume::UsageType usage() const; virtual QString fsType() const; virtual QString label() const; virtual QString uuid() const; virtual qulonglong size() const; virtual QString encryptedContainerUdi() const; }; } } } #endif // SOLID_BACKENDS_HAL_VOLUME_H cantata-2.2.0/3rdparty/solid-lite/backends/iokit/000077500000000000000000000000001316350454000216355ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/iokit/cfhelper.cpp000066400000000000000000000144071316350454000241370ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include /* helper classes to convert from CF types to Qt */ static QString q_toString(const CFStringRef &str) { CFIndex length = CFStringGetLength(str); QVarLengthArray buffer(length); CFRange range = { 0, length }; CFStringGetCharacters(str, range, buffer.data()); return QString(reinterpret_cast(buffer.data()), length); } template static inline T convertCFNumber(const CFNumberRef &num, CFNumberType type) { T n; CFNumberGetValue(num, type, &n); return n; } static QVariant q_toVariant(const CFTypeRef &obj) { const CFTypeID typeId = CFGetTypeID(obj); if (typeId == CFStringGetTypeID()) return QVariant(q_toString(static_cast(obj))); if (typeId == CFNumberGetTypeID()) { const CFNumberRef num = static_cast(obj); const CFNumberType type = CFNumberGetType(num); switch (type) { case kCFNumberSInt8Type: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberSInt16Type: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberSInt32Type: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberSInt64Type: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberCharType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberShortType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberIntType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberLongType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberLongLongType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberFloatType: return qVariantFromValue(convertCFNumber(num, type)); case kCFNumberDoubleType: return qVariantFromValue(convertCFNumber(num, type)); default: if (CFNumberIsFloatType(num)) return qVariantFromValue(convertCFNumber(num, kCFNumberDoubleType)); return qVariantFromValue(convertCFNumber(num, kCFNumberLongLongType)); } } if (typeId == CFDateGetTypeID()) { QDateTime dt; dt.setTime_t(uint(kCFAbsoluteTimeIntervalSince1970)); return dt.addSecs(int(CFDateGetAbsoluteTime(static_cast(obj)))); } if (typeId == CFDataGetTypeID()) { const CFDataRef cfdata = static_cast(obj); return QByteArray(reinterpret_cast(CFDataGetBytePtr(cfdata)), CFDataGetLength(cfdata)); } if (typeId == CFBooleanGetTypeID()) return QVariant(bool(CFBooleanGetValue(static_cast(obj)))); if (typeId == CFArrayGetTypeID()) { const CFArrayRef cfarray = static_cast(obj); QList list; CFIndex size = CFArrayGetCount(cfarray); bool metNonString = false; for (CFIndex i = 0; i < size; ++i) { QVariant value = q_toVariant(CFArrayGetValueAtIndex(cfarray, i)); if (value.type() != QVariant::String) metNonString = true; list << value; } if (metNonString) return list; else return QVariant(list).toStringList(); } if (typeId == CFDictionaryGetTypeID()) { const CFDictionaryRef cfdict = static_cast(obj); const CFTypeID arrayTypeId = CFArrayGetTypeID(); int size = int(CFDictionaryGetCount(cfdict)); QVarLengthArray keys(size); QVarLengthArray values(size); CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data()); QMultiMap map; for (int i = 0; i < size; ++i) { QString key = q_toString(static_cast(keys[i])); if (CFGetTypeID(values[i]) == arrayTypeId) { const CFArrayRef cfarray = static_cast(values[i]); CFIndex arraySize = CFArrayGetCount(cfarray); for (CFIndex j = arraySize - 1; j >= 0; --j) map.insert(key, q_toVariant(CFArrayGetValueAtIndex(cfarray, j))); } else { map.insert(key, q_toVariant(values[i])); } } return map; } return QVariant(); } QMap q_toVariantMap (const CFMutableDictionaryRef &dict) { Q_ASSERT(dict); QMap result; const int count = CFDictionaryGetCount(dict); QVarLengthArray keys(count); QVarLengthArray values(count); CFDictionaryGetKeysAndValues(dict, const_cast(keys.data()), const_cast(values.data())); for (int i = 0; i < count; ++i) { const QString key = q_toString((CFStringRef)keys[i]); const QVariant value = q_toVariant((CFTypeRef)values[i]); result[key] = value; } return result; } cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitdevice.cpp000066400000000000000000000146601316350454000246470ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "iokitdevice.h" #include "iokitgenericinterface.h" //#include "iokitprocessor.h" //#include "iokitbattery.h" //#include "iokitnetworkinterface.h" //#include "iokitserialinterface.h" #include #include #include //#include #include // from cfhelper.cpp extern QMap q_toVariantMap(const CFMutableDictionaryRef &dict); namespace Solid { namespace Backends { namespace IOKit { // returns a solid type from an entry and its properties static Solid::DeviceInterface::Type typeFromEntry(const io_registry_entry_t &entry, const QMap &properties) { /* if (IOObjectConformsTo(entry, kIOEthernetInterfaceClass)) return Solid::DeviceInterface::NetworkInterface; if (IOObjectConformsTo(entry, "AppleACPICPU")) return Solid::DeviceInterface::Processor; if (IOObjectConformsTo(entry, "IOSerialBSDClient")) return Solid::DeviceInterface::SerialInterface; if (IOObjectConformsTo(entry, "AppleSmartBattery")) return Solid::DeviceInterface::Battery; */ return Solid::DeviceInterface::Unknown; } // gets all properties from an entry into a QMap static QMap getProperties(const io_registry_entry_t &entry) { CFMutableDictionaryRef propertyDict = 0; if (IORegistryEntryCreateCFProperties(entry, &propertyDict, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS) { return QMap(); } QMap result = q_toVariantMap(propertyDict); CFRelease(propertyDict); return result; } // gets the parent's Udi from an entry static QString getParentDeviceUdi(const io_registry_entry_t &entry) { io_registry_entry_t parent = 0; kern_return_t ret = IORegistryEntryGetParentEntry(entry, kIOServicePlane, &parent); if (ret != KERN_SUCCESS) { // don't release parent here - docs say only on success return QString(); } QString result; io_string_t pathName; ret = IORegistryEntryGetPath(parent, kIOServicePlane, pathName); if (ret == KERN_SUCCESS) result = QString::fromUtf8(pathName); // now we can release the parent IOObjectRelease(parent); return result; } class IOKitDevicePrivate { public: inline IOKitDevicePrivate() : type(Solid::DeviceInterface::Unknown) {} void init(const QString &udiString, const io_registry_entry_t & entry); QString udi; QString parentUdi; QMap properties; Solid::DeviceInterface::Type type; }; void IOKitDevicePrivate::init(const QString &udiString, const io_registry_entry_t &entry) { Q_ASSERT(entry != MACH_PORT_NULL); udi = udiString; properties = getProperties(entry); io_name_t className; IOObjectGetClass(entry, className); properties["className"] = QString::fromUtf8(className); parentUdi = getParentDeviceUdi(entry); type = typeFromEntry(entry, properties); IOObjectRelease(entry); } IOKitDevice::IOKitDevice(const QString &udi, const io_registry_entry_t &entry) : d(new IOKitDevicePrivate) { d->init(udi, entry); } IOKitDevice::IOKitDevice(const QString &udi) : d(new IOKitDevicePrivate) { io_registry_entry_t entry = IORegistryEntryFromPath( kIOMasterPortDefault, udi.toLocal8Bit().constData()); if (entry == MACH_PORT_NULL) { qDebug() << Q_FUNC_INFO << "Tried to create Device from invalid UDI" << udi; return; } d->init(udi, entry); } IOKitDevice::~IOKitDevice() { delete d; } QString IOKitDevice::udi() const { return d->udi; } QString IOKitDevice::parentUdi() const { return d->parentUdi; } QString IOKitDevice::vendor() const { return QString(); // TODO } QString IOKitDevice::product() const { return QString(); // TODO } QString IOKitDevice::icon() const { return QString(); // TODO } QStringList IOKitDevice::emblems() const { return QStringList(); // TODO } QString IOKitDevice::description() const { return product(); // TODO } QVariant IOKitDevice::property(const QString &key) const { return d->properties.value(key); } QMap IOKitDevice::allProperties() const { return d->properties; } bool IOKitDevice::propertyExists(const QString &key) const { return d->properties.contains(key); } bool IOKitDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const { return (type == Solid::DeviceInterface::GenericInterface || type == d->type); } QObject *IOKitDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type) { QObject *iface = 0; switch (type) { case Solid::DeviceInterface::GenericInterface: iface = new GenericInterface(this); break; /* case Solid::DeviceInterface::Processor: if (d->type == Solid::DeviceInterface::Processor) iface = new Processor(this); break; case Solid::DeviceInterface::NetworkInterface: if (d->type == Solid::DeviceInterface::NetworkInterface) iface = new NetworkInterface(this); break; case Solid::DeviceInterface::SerialInterface: if (d->type == Solid::DeviceInterface::SerialInterface) iface = new SerialInterface(this); break; case Solid::DeviceInterface::Battery: if (d->type == Solid::DeviceInterface::Battery) iface = new Battery(this); break; */ // the rest is TODO } return iface; } } } } // namespaces cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitdevice.h000066400000000000000000000043721316350454000243130ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_IOKIT_IOKITDEVICE_H #define SOLID_BACKENDS_IOKIT_IOKITDEVICE_H #include #include namespace Solid { namespace Backends { namespace IOKit { class IOKitDevicePrivate; class IOKitManager; class IOKitDevice : public Solid::Ifaces::Device { Q_OBJECT public: IOKitDevice(const QString &udi); virtual ~IOKitDevice(); virtual QString udi() const; virtual QString parentUdi() const; virtual QString vendor() const; virtual QString product() const; virtual QString icon() const; virtual QStringList emblems() const; virtual QString description() const; virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const; virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type); Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); private: friend class IOKitManager; IOKitDevice(const QString &udi, const io_registry_entry_t &entry); IOKitDevicePrivate * const d; }; } } } #endif // SOLID_BACKENDS_IOKIT_IOKITDEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitdeviceinterface.cpp000066400000000000000000000022701316350454000265220ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "iokitdeviceinterface.h" using namespace Solid::Backends::IOKit; DeviceInterface::DeviceInterface(IOKitDevice *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } //#include "backends/iokit/iokitdeviceinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitdeviceinterface.h000066400000000000000000000030011316350454000261600ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_IOKIT_DEVICEINTERFACE_H #define SOLID_BACKENDS_IOKIT_DEVICEINTERFACE_H #include #include "iokitdevice.h" #include #include namespace Solid { namespace Backends { namespace IOKit { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(IOKitDevice *device); virtual ~DeviceInterface(); protected: IOKitDevice *m_device; }; } } } #endif // SOLID_BACKENDS_IOKIT_DEVICEINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitgenericinterface.cpp000066400000000000000000000030201316350454000266710ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "iokitgenericinterface.h" #include "iokitdevice.h" using namespace Solid::Backends::IOKit; GenericInterface::GenericInterface(IOKitDevice *device) : DeviceInterface(device) { } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->property(key); } QMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } //#include "backends/iokit/iokitgenericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitgenericinterface.h000066400000000000000000000035121316350454000263440ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_IOKIT_GENERICINTERFACE_H #define SOLID_BACKENDS_IOKIT_GENERICINTERFACE_H #include #include #include "iokitdeviceinterface.h" namespace Solid { namespace Backends { namespace IOKit { class IOKitDevice; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(IOKitDevice *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif // SOLID_BACKENDS_IOKIT_GENERICINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitmanager.cpp000066400000000000000000000177221316350454000250240ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "iokitmanager.h" #include "iokitdevice.h" #include #include #include #include #include namespace Solid { namespace Backends { namespace IOKit { class IOKitManagerPrivate { public: inline IOKitManagerPrivate() : port(0), source(0) {} IONotificationPortRef port; CFRunLoopSourceRef source; static const char *typeToName(Solid::DeviceInterface::Type type); static QStringList devicesFromRegistry(io_iterator_t it); QSet supportedInterfaces; }; // gets all registry pathes from an iterator QStringList IOKitManagerPrivate::devicesFromRegistry(io_iterator_t it) { QStringList result; io_object_t obj; io_string_t pathName; while ((obj = IOIteratorNext(it))) { kern_return_t ret = IORegistryEntryGetPath(obj, kIOServicePlane, pathName); if (ret != KERN_SUCCESS) { qWarning() << Q_FUNC_INFO << "IORegistryEntryGetPath failed"; continue; } result += QString::fromUtf8(pathName); ret = IOObjectRelease(obj); if (ret != KERN_SUCCESS) { // very unlikely to happen - keep it a qDebug just in case. // compiler will nuke this code in release builds. qDebug() << Q_FUNC_INFO << "Unable to release object reference"; } } IOObjectRelease(it); return result; } const char *IOKitManagerPrivate::typeToName(Solid::DeviceInterface::Type type) { switch (type) { case Solid::DeviceInterface::Unknown: return 0; case Solid::DeviceInterface::NetworkInterface: return kIOEthernetInterfaceClass; case Solid::DeviceInterface::Processor: return "AppleACPICPU"; case Solid::DeviceInterface::SerialInterface: return "IOSerialBSDClient"; case Solid::DeviceInterface::Battery: return "AppleSmartBattery"; //Solid::DeviceInterface::GenericInterface: //Solid::DeviceInterface::Block: //Solid::DeviceInterface::StorageAccess: //Solid::DeviceInterface::StorageDrive: Solid::DeviceInterface::OpticalDrive: //Solid::DeviceInterface::StorageVolume: Solid::DeviceInterface::OpticalDisc: //Solid::DeviceInterface::Camera: //Solid::DeviceInterface::PortableMediaPlayer: //Solid::DeviceInterface::NetworkInterface: //Solid::DeviceInterface::AcAdapter: //Solid::DeviceInterface::Button: //Solid::DeviceInterface::AudioInterface: //Solid::DeviceInterface::DvbInterface: //Solid::DeviceInterface::Video: } return 0; } IOKitManager::IOKitManager(QObject *parent) : Solid::Ifaces::DeviceManager(parent), d(new IOKitManagerPrivate) { d->port = IONotificationPortCreate(kIOMasterPortDefault); if (!d->port) { qWarning() << Q_FUNC_INFO << "Unable to create notification port"; return; } d->source = IONotificationPortGetRunLoopSource(d->port); if (!d->source) { qWarning() << Q_FUNC_INFO << "Unable to create notification source"; return; } CFRunLoopAddSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode); d->supportedInterfaces << Solid::DeviceInterface::GenericInterface << Solid::DeviceInterface::Processor << Solid::DeviceInterface::Block << Solid::DeviceInterface::StorageAccess << Solid::DeviceInterface::StorageDrive << Solid::DeviceInterface::OpticalDrive << Solid::DeviceInterface::StorageVolume << Solid::DeviceInterface::OpticalDisc << Solid::DeviceInterface::Camera << Solid::DeviceInterface::PortableMediaPlayer << Solid::DeviceInterface::NetworkInterface << Solid::DeviceInterface::AcAdapter << Solid::DeviceInterface::Battery << Solid::DeviceInterface::Button << Solid::DeviceInterface::AudioInterface << Solid::DeviceInterface::DvbInterface << Solid::DeviceInterface::Video << Solid::DeviceInterface::SerialInterface << Solid::DeviceInterface::SmartCardReader; } IOKitManager::~IOKitManager() { if (d->source) CFRunLoopRemoveSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode); if (d->port) IONotificationPortDestroy(d->port); delete d; } QString IOKitManager::udiPrefix() const { return QString(); //FIXME: We should probably use a prefix there... has to be tested on Mac } QSet IOKitManager::supportedInterfaces() const { return d->supportedInterfaces; } QStringList IOKitManager::allDevices() { // use an IORegistry Iterator to iterate over all devices in the service plane io_iterator_t it; kern_return_t ret = IORegistryCreateIterator( kIOMasterPortDefault, kIOServicePlane, kIORegistryIterateRecursively, &it); if (ret != KERN_SUCCESS) { qWarning() << Q_FUNC_INFO << "unable to create iterator"; return QStringList(); } return IOKitManagerPrivate::devicesFromRegistry(it); } QStringList IOKitManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type) { QStringList result; if (type == Solid::DeviceInterface::Unknown) { // match all device interfaces result = allDevices(); } else { const char *deviceClassName = IOKitManagerPrivate::typeToName(type); if (!deviceClassName) return QStringList(); CFMutableDictionaryRef matchingDict = IOServiceMatching(deviceClassName); if (!matchingDict) return QStringList(); io_iterator_t it = 0; // note - IOServiceGetMatchingServices dereferences the dict kern_return_t ret = IOServiceGetMatchingServices( kIOMasterPortDefault, matchingDict, &it); result = IOKitManagerPrivate::devicesFromRegistry(it); } // if the parentUdi is an empty string, return all matches if (parentUdi.isEmpty()) return result; // return only matches that start with the parent's UDI QStringList filtered; foreach (const QString &udi, result) { if (udi.startsWith(parentUdi)) filtered += udi; } return filtered; } QObject *IOKitManager::createDevice(const QString &udi) { io_registry_entry_t entry = IORegistryEntryFromPath( kIOMasterPortDefault, udi.toLocal8Bit().constData()); // we have to do IOObjectConformsTo - comparing the class names is not good enough //if (IOObjectConformsTo(entry, kIOEthernetInterfaceClass)) { //} if (entry == MACH_PORT_NULL) return 0; return new IOKitDevice(udi, entry); } }}} // namespaces cantata-2.2.0/3rdparty/solid-lite/backends/iokit/iokitmanager.h000066400000000000000000000034561316350454000244700ustar00rootroot00000000000000/* Copyright 2009 Harald Fernengel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_IOKIT_IOKITMANAGER_H #define SOLID_BACKENDS_IOKIT_IOKITMANAGER_H #include #include #include #include namespace Solid { namespace Backends { namespace IOKit { class IOKitManagerPrivate; class IOKitManager : public Solid::Ifaces::DeviceManager { Q_OBJECT public: IOKitManager(QObject *parent); virtual ~IOKitManager(); virtual QString udiPrefix() const ; virtual QSet supportedInterfaces() const; virtual QStringList allDevices(); virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type); virtual QObject *createDevice(const QString &udi); private: IOKitManagerPrivate *d; }; } } } #endif // SOLID_BACKENDS_IOKIT_IOKITMANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/shared/000077500000000000000000000000001316350454000217645ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/shared/rootdevice.cpp000066400000000000000000000043511316350454000246360ustar00rootroot00000000000000/* Copyright 2010 Mario Bensi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "rootdevice.h" #include using namespace Solid::Backends::Shared; RootDevice::RootDevice(const QString &udi, const QString &parentUdi) : Solid::Ifaces::Device(), m_udi(udi), m_parentUdi(parentUdi), m_vendor("KDE") { } RootDevice::~RootDevice() { } QString RootDevice::udi() const { return m_udi; } QString RootDevice::parentUdi() const { return m_parentUdi; } QString RootDevice::vendor() const { return m_vendor; } void RootDevice::setVendor(const QString &vendor) { m_vendor = vendor; } QString RootDevice::product() const { return m_product; } void RootDevice::setProduct(const QString &product) { m_product = product; } QString RootDevice::icon() const { return m_icon; } void RootDevice::setIcon(const QString &icon) { m_icon = icon; } QStringList RootDevice::emblems() const { return m_emblems; } void RootDevice::setEmblems(const QStringList &emblems) { m_emblems = emblems; } QString RootDevice::description() const { return m_description; } void RootDevice::setDescription(const QString &description) { m_description = description; } bool RootDevice::queryDeviceInterface(const Solid::DeviceInterface::Type&) const { return false; } QObject* RootDevice::createDeviceInterface(const Solid::DeviceInterface::Type&) { return 0; } cantata-2.2.0/3rdparty/solid-lite/backends/shared/rootdevice.h000066400000000000000000000042141316350454000243010ustar00rootroot00000000000000/* Copyright 2010 Mario Bensi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_SHARED_ROOT_DEVICE_H #define SOLID_BACKENDS_SHARED_ROOT_DEVICE_H #include #include namespace Solid { namespace Backends { namespace Shared { class RootDevice : public Solid::Ifaces::Device { Q_OBJECT public: explicit RootDevice(const QString &udi, const QString &parentUdi = QString()); virtual ~RootDevice(); virtual QString udi() const; virtual QString parentUdi() const; virtual QString vendor() const; void setVendor(const QString &vendor); virtual QString product() const; void setProduct(const QString &product); virtual QString icon() const; void setIcon(const QString &icon); virtual QStringList emblems() const; void setEmblems(const QStringList &emblems); virtual QString description() const; void setDescription(const QString &description); virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const; virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type); private: QString m_udi; QString m_parentUdi; QString m_vendor; QString m_product; QString m_icon; QStringList m_emblems; QString m_description; }; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/shared/udevqt.h000066400000000000000000000111511316350454000234440ustar00rootroot00000000000000/* Copyright 2009 Benjamin K. Stuhl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDEVQT_H #define UDEVQT_H #include #include #include #include #include #include #include namespace UdevQt { class DevicePrivate; class Device { public: Device(); Device(const Device &other); ~Device(); Device &operator= (const Device &other); bool isValid() const; QString subsystem() const; QString devType() const; QString name() const; QString sysfsPath() const; int sysfsNumber() const; QString driver() const; QString primaryDeviceFile() const; QStringList alternateDeviceSymlinks() const; QStringList deviceProperties() const; Device parent() const; // ### should this really be a QVariant? as far as udev knows, everything is a string... // see also Client::devicesByProperty QVariant deviceProperty(const QString &name) const; QString decodedDeviceProperty(const QString &name) const; QVariant sysfsProperty(const QString &name) const; Device ancestorOfType(const QString &subsys, const QString &devtype) const; private: Device(DevicePrivate *devPrivate); friend class Client; friend class ClientPrivate; DevicePrivate *d; }; typedef QList DeviceList; class ClientPrivate; class Client : public QObject { Q_OBJECT Q_PROPERTY(QStringList watchedSubsystems READ watchedSubsystems WRITE setWatchedSubsystems) public: Client(QObject *parent = 0); Client(const QStringList &subsystemList, QObject *parent = 0); ~Client(); QStringList watchedSubsystems() const; void setWatchedSubsystems(const QStringList &subsystemList); DeviceList allDevices(); DeviceList devicesByProperty(const QString &property, const QVariant &value); DeviceList devicesBySubsystem(const QString &subsystem); Device deviceByDeviceFile(const QString &deviceFile); Device deviceBySysfsPath(const QString &sysfsPath); Device deviceBySubsystemAndName(const QString &subsystem, const QString &name); signals: void deviceAdded(const UdevQt::Device &dev); void deviceRemoved(const UdevQt::Device &dev); void deviceChanged(const UdevQt::Device &dev); void deviceOnlined(const UdevQt::Device &dev); void deviceOfflined(const UdevQt::Device &dev); private: friend class ClientPrivate; Q_PRIVATE_SLOT(d, void _uq_monitorReadyRead(int fd)) ClientPrivate *d; }; class DevicePrivate { public: DevicePrivate(struct udev_device *udev_, bool ref = true); ~DevicePrivate(); DevicePrivate &operator=(const DevicePrivate& other); QString decodePropertyValue(const QByteArray &encoded) const; struct udev_device *udev; }; class ClientPrivate { public: enum ListenToWhat { ListenToList, ListenToNone }; ClientPrivate(Client *q_); ~ClientPrivate(); void init(const QStringList &subsystemList, ListenToWhat what); void setWatchedSubsystems(const QStringList &subsystemList); void _uq_monitorReadyRead(int fd); DeviceList deviceListFromEnumerate(struct udev_enumerate *en); struct udev *udev; struct udev_monitor *monitor; Client *q; QSocketNotifier *monitorNotifier; QStringList watchedSubsystems; }; inline QStringList listFromListEntry(struct udev_list_entry *list) { QStringList ret; struct udev_list_entry *entry; udev_list_entry_foreach(entry, list) { ret << QString::fromLatin1(udev_list_entry_get_name(entry)); } return ret; } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/shared/udevqt_p.h000066400000000000000000000043521316350454000237700ustar00rootroot00000000000000/* Copyright 2009 Benjamin K. Stuhl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDEVQT_P_H #define UDEVQT_P_H extern "C" { #define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE #include } class QByteArray; class QSocketNotifier; namespace UdevQt { class DevicePrivate { public: DevicePrivate(struct udev_device *udev_, bool ref = true); ~DevicePrivate(); DevicePrivate &operator=(const DevicePrivate& other); QString decodePropertyValue(const QByteArray &encoded) const; struct udev_device *udev; }; class ClientPrivate { public: enum ListenToWhat { ListenToList, ListenToNone }; ClientPrivate(Client *q_); ~ClientPrivate(); void init(const QStringList &subsystemList, ListenToWhat what); void setWatchedSubsystems(const QStringList &subsystemList); void _uq_monitorReadyRead(int fd); DeviceList deviceListFromEnumerate(struct udev_enumerate *en); struct udev *udev; struct udev_monitor *monitor; Client *q; QSocketNotifier *monitorNotifier; QStringList watchedSubsystems; }; inline QStringList listFromListEntry(struct udev_list_entry *list) { QStringList ret; struct udev_list_entry *entry; udev_list_entry_foreach(entry, list) { ret << QString::fromLatin1(udev_list_entry_get_name(entry)); } return ret; } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/shared/udevqtclient.cpp000066400000000000000000000163521316350454000252060ustar00rootroot00000000000000/* Copyright 2009 Benjamin K. Stuhl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevqt.h" // #include "udevqt_p.h" #include #include namespace UdevQt { ClientPrivate::ClientPrivate(Client *q_) : udev(0), monitor(0), q(q_), monitorNotifier(0) { } ClientPrivate::~ClientPrivate() { udev_unref(udev); delete monitorNotifier; if (monitor) udev_monitor_unref(monitor); } void ClientPrivate::init(const QStringList &subsystemList, ListenToWhat what) { udev = udev_new(); if (what != ListenToNone) { setWatchedSubsystems(subsystemList); } } void ClientPrivate::setWatchedSubsystems(const QStringList &subsystemList) { // create a listener struct udev_monitor *newM = udev_monitor_new_from_netlink(udev, "udev"); if (!newM) { qWarning("UdevQt: unable to create udev monitor connection"); return; } // apply our filters; an empty list means listen to everything foreach (const QString& subsysDevtype, subsystemList) { int ix = subsysDevtype.indexOf("/"); if (ix > 0) { QByteArray subsystem = subsysDevtype.left(ix).toLatin1(); QByteArray devType = subsysDevtype.mid(ix + 1).toLatin1(); udev_monitor_filter_add_match_subsystem_devtype(newM, subsystem.constData(), devType.constData()); } else { udev_monitor_filter_add_match_subsystem_devtype(newM, subsysDevtype.toLatin1().constData(), NULL); } } // start the new monitor receiving udev_monitor_enable_receiving(newM); QSocketNotifier *sn = new QSocketNotifier(udev_monitor_get_fd(newM), QSocketNotifier::Read); QObject::connect(sn, SIGNAL(activated(int)), q, SLOT(_uq_monitorReadyRead(int))); // kill any previous monitor delete monitorNotifier; if (monitor) udev_monitor_unref(monitor); // and save our new one monitor = newM; monitorNotifier = sn; watchedSubsystems = subsystemList; } void ClientPrivate::_uq_monitorReadyRead(int fd) { Q_UNUSED(fd); monitorNotifier->setEnabled(false); struct udev_device *dev = udev_monitor_receive_device(monitor); monitorNotifier->setEnabled(true); if (!dev) return; Device device(new DevicePrivate(dev, false)); QByteArray action(udev_device_get_action(dev)); if (action == "add") { emit q->deviceAdded(device); } else if (action == "remove") { emit q->deviceRemoved(device); } else if (action == "change") { emit q->deviceChanged(device); } else if (action == "online") { emit q->deviceOnlined(device); } else if (action == "offline") { emit q->deviceOfflined(device); } else { qWarning("UdevQt: unhandled device action \"%s\"", action.constData()); } } DeviceList ClientPrivate::deviceListFromEnumerate(struct udev_enumerate *en) { DeviceList ret; struct udev_list_entry *list, *entry; udev_enumerate_scan_devices(en); list = udev_enumerate_get_list_entry(en); udev_list_entry_foreach(entry, list) { struct udev_device *ud = udev_device_new_from_syspath(udev_enumerate_get_udev(en), udev_list_entry_get_name(entry)); if (!ud) continue; ret << Device(new DevicePrivate(ud, false)); } udev_enumerate_unref(en); return ret; } Client::Client(QObject *parent) : QObject(parent) , d(new ClientPrivate(this)) { d->init(QStringList(), ClientPrivate::ListenToNone); } Client::Client(const QStringList& subsystemList, QObject *parent) : QObject(parent) , d(new ClientPrivate(this)) { d->init(subsystemList, ClientPrivate::ListenToList); } Client::~Client() { delete d; } QStringList Client::watchedSubsystems() const { // we're watching a specific list if (!d->watchedSubsystems.isEmpty()) return d->watchedSubsystems; // we're not watching anything if (!d->monitor) return QStringList(); // we're watching everything: figure out what "everything" currently is // we don't cache it, since it may be subject to change, depending on hotplug struct udev_enumerate *en = udev_enumerate_new(d->udev); udev_enumerate_scan_subsystems(en); QStringList s = listFromListEntry(udev_enumerate_get_list_entry(en)); udev_enumerate_unref(en); return s; } void Client::setWatchedSubsystems(const QStringList &subsystemList) { d->setWatchedSubsystems(subsystemList); } DeviceList Client::devicesByProperty(const QString &property, const QVariant &value) { struct udev_enumerate *en = udev_enumerate_new(d->udev); if (value.isValid()) { udev_enumerate_add_match_property(en, property.toLatin1().constData(), value.toString().toLatin1().constData()); } else { udev_enumerate_add_match_property(en, property.toLatin1().constData(), NULL); } return d->deviceListFromEnumerate(en); } DeviceList Client::allDevices() { struct udev_enumerate *en = udev_enumerate_new(d->udev); return d->deviceListFromEnumerate(en); } DeviceList Client::devicesBySubsystem(const QString &subsystem) { struct udev_enumerate *en = udev_enumerate_new(d->udev); udev_enumerate_add_match_subsystem(en, subsystem.toLatin1().constData()); return d->deviceListFromEnumerate(en); } Device Client::deviceByDeviceFile(const QString &deviceFile) { struct stat sb; if (stat(deviceFile.toLatin1().constData(), &sb) != 0) return Device(); struct udev_device *ud = 0; if (S_ISBLK(sb.st_mode)) ud = udev_device_new_from_devnum(d->udev, 'b', sb.st_rdev); else if (S_ISCHR(sb.st_mode)) ud = udev_device_new_from_devnum(d->udev, 'c', sb.st_rdev); if (!ud) return Device(); return Device(new DevicePrivate(ud, false)); } Device Client::deviceBySysfsPath(const QString &sysfsPath) { struct udev_device *ud = udev_device_new_from_syspath(d->udev, sysfsPath.toLatin1().constData()); if (!ud) return Device(); return Device(new DevicePrivate(ud, false)); } Device Client::deviceBySubsystemAndName(const QString &subsystem, const QString &name) { struct udev_device *ud = udev_device_new_from_subsystem_sysname(d->udev, subsystem.toLatin1().constData(), name.toLatin1().constData()); if (!ud) return Device(); return Device(new DevicePrivate(ud, false)); } } //#include "udevqt.moc" cantata-2.2.0/3rdparty/solid-lite/backends/shared/udevqtdevice.cpp000066400000000000000000000134011316350454000251570ustar00rootroot00000000000000/* Copyright 2009 Benjamin K. Stuhl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevqt.h" // #include "udevqt_p.h" #include namespace UdevQt { DevicePrivate::DevicePrivate(struct udev_device *udev_, bool ref) : udev(udev_) { if (ref) udev_device_ref(udev); } DevicePrivate::~DevicePrivate() { udev_device_unref(udev); } DevicePrivate &DevicePrivate::operator=(const DevicePrivate &other) { udev_device_unref(udev); udev = udev_device_ref(other.udev); return *this; } QString DevicePrivate::decodePropertyValue(const QByteArray &encoded) const { QByteArray decoded; const int len = encoded.length(); for (int i = 0; i < len; i++) { quint8 ch = encoded.at(i); if (ch == '\\') { if (i + 1 < len && encoded.at(i + 1) == '\\') { decoded.append('\\'); i++; continue; } else if (i + 3 < len && encoded.at(i + 1) == 'x') { QByteArray hex = encoded.mid(i + 2, 2); bool ok; int code = hex.toInt(&ok, 16); if (ok) decoded.append(char(code)); i += 3; continue; } } else { decoded.append(ch); } } return QString::fromUtf8(decoded); } Device::Device() : d(0) { } Device::Device(const Device &other) { if (other.d) { d = new DevicePrivate(other.d->udev); } else { d = 0; } } Device::Device(DevicePrivate *devPrivate) : d(devPrivate) { } Device::~Device() { delete d; } Device &Device::operator=(const Device &other) { if (this == &other) return *this; if (!other.d) { delete d; d = 0; return *this; } if (!d) { d = new DevicePrivate(other.d->udev); } else { *d = *other.d; } return *this; } bool Device::isValid() const { return (d != 0); } QString Device::subsystem() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_subsystem(d->udev)); } QString Device::devType() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_devtype(d->udev)); } QString Device::name() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_sysname(d->udev)); } QString Device::sysfsPath() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_syspath(d->udev)); } int Device::sysfsNumber() const { if (!d) return -1; QString value = QString::fromLatin1(udev_device_get_sysnum(d->udev)); bool success = false; int number = value.toInt(&success); if (success) return number; return -1; } QString Device::driver() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_driver(d->udev)); } QString Device::primaryDeviceFile() const { if (!d) return QString(); return QString::fromLatin1(udev_device_get_devnode(d->udev)); } QStringList Device::alternateDeviceSymlinks() const { if (!d) return QStringList(); return listFromListEntry(udev_device_get_devlinks_list_entry(d->udev)); } QStringList Device::deviceProperties() const { if (!d) return QStringList(); return listFromListEntry(udev_device_get_properties_list_entry(d->udev)); } Device Device::parent() const { if (!d) return Device(); struct udev_device *p = udev_device_get_parent(d->udev); if (!p) return Device(); return Device(new DevicePrivate(p)); } QVariant Device::deviceProperty(const QString &name) const { if (!d) return QVariant(); QByteArray propName = name.toLatin1(); QString propValue = QString::fromLatin1(udev_device_get_property_value(d->udev, propName.constData())); if (!propValue.isEmpty()) { return QVariant::fromValue(propValue); } return QVariant(); } QString Device::decodedDeviceProperty(const QString &name) const { if (!d) return QString(); QByteArray propName = name.toLatin1(); return d->decodePropertyValue(udev_device_get_property_value(d->udev, propName.constData())); } QVariant Device::sysfsProperty(const QString &name) const { if (!d) return QVariant(); QByteArray propName = name.toLatin1(); QString propValue = QString::fromLatin1(udev_device_get_sysattr_value(d->udev, propName.constData())); if (!propValue.isEmpty()) { return QVariant::fromValue(propValue); } return QVariant(); } Device Device::ancestorOfType(const QString &subsys, const QString &devtype) const { if (!d) return Device(); struct udev_device *p = udev_device_get_parent_with_subsystem_devtype(d->udev, subsys.toLatin1().constData(), devtype.toLatin1().constData()); if (!p) return Device(); return Device(new DevicePrivate(p)); } } cantata-2.2.0/3rdparty/solid-lite/backends/udev/000077500000000000000000000000001316350454000214615ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/udev/udev.h000066400000000000000000000021351316350454000225760ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_H #define SOLID_BACKENDS_UDEV_H #include "../shared/udevqt.h" #define UDEV_UDI_PREFIX "/org/kde/solid/udev" #endif // SOLID_BACKENDS_UDEV_H cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevblock.cpp000066400000000000000000000025431316350454000241470ustar00rootroot00000000000000/* Copyright 2010 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevblock.h" using namespace Solid::Backends::UDev; Block::Block(UDevDevice *device) : DeviceInterface(device) { } Block::~Block() { } int Block::deviceMajor() const { return m_device->property("MAJOR").toInt(); } int Block::deviceMinor() const { return m_device->property("MINOR").toInt(); } QString Block::device() const { return m_device->property("DEVNAME").toString(); } //#include "backends/udev/udevblock.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevblock.h000066400000000000000000000027231316350454000236140ustar00rootroot00000000000000/* Copyright 2010 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_UDEVBLOCK_H #define SOLID_BACKENDS_UDEV_UDEVBLOCK_H #include #include "udevdeviceinterface.h" namespace Solid { namespace Backends { namespace UDev { class Block : public DeviceInterface, virtual public Solid::Ifaces::Block { Q_OBJECT Q_INTERFACES(Solid::Ifaces::Block) public: Block(UDevDevice *device); virtual ~Block(); virtual int deviceMajor() const; virtual int deviceMinor() const; virtual QString device() const; }; } } } #endif // SOLID_BACKENDS_UDEV_UDEVBLOCK_H cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevdevice.cpp000066400000000000000000000255611316350454000243210ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevdevice.h" #include "udevgenericinterface.h" //#include "udevprocessor.h" //#include "udevcamera.h" //#include "udevvideo.h" #include "udevportablemediaplayer.h" //#include "udevdvbinterface.h" #include "udevblock.h" //#include "udevaudiointerface.h" //#include "udevserialinterface.h" //#include "udevnetworkinterface.h" //#include "cpuinfo.h" #include #include #include #include using namespace Solid::Backends::UDev; UDevDevice::UDevDevice(const UdevQt::Device device) : Solid::Ifaces::Device() , m_device(device) { } UDevDevice::~UDevDevice() { } QString UDevDevice::udi() const { return devicePath(); } QString UDevDevice::parentUdi() const { return UDEV_UDI_PREFIX; } QString UDevDevice::vendor() const { QString vendor = m_device.sysfsProperty("manufacturer").toString(); if (vendor.isEmpty()) { /*if (queryDeviceInterface(Solid::DeviceInterface::Processor)) { // sysfs doesn't have anything useful here vendor = extractCpuInfoLine(deviceNumber(), "vendor_id\\s+:\\s+(\\S.+)"); } else if (queryDeviceInterface(Solid::DeviceInterface::Video)) { vendor = m_device.deviceProperty("ID_VENDOR").toString().replace('_', " "); } else if (queryDeviceInterface(Solid::DeviceInterface::NetworkInterface)) { vendor = m_device.deviceProperty("ID_VENDOR_FROM_DATABASE").toString(); } else if (queryDeviceInterface(Solid::DeviceInterface::AudioInterface)) { if (m_device.parent().isValid()) { vendor = m_device.parent().deviceProperty("ID_VENDOR_FROM_DATABASE").toString(); } } */ if (vendor.isEmpty()) { vendor = m_device.deviceProperty("ID_VENDOR").toString().replace('_', ' '); } } return vendor; } QString UDevDevice::product() const { QString product = m_device.sysfsProperty("product").toString(); if (product.isEmpty()) { /*if (queryDeviceInterface(Solid::DeviceInterface::Processor)) { // sysfs doesn't have anything useful here product = extractCpuInfoLine(deviceNumber(), "model name\\s+:\\s+(\\S.+)"); } else if(queryDeviceInterface(Solid::DeviceInterface::Video)) { product = m_device.deviceProperty("ID_V4L_PRODUCT").toString(); } else if(queryDeviceInterface(Solid::DeviceInterface::AudioInterface)) { const AudioInterface audioIface(const_cast(this)); product = audioIface.name(); } else if(queryDeviceInterface(Solid::DeviceInterface::NetworkInterface)) { QFile typeFile(deviceName() + "/type"); if (typeFile.open(QIODevice::ReadOnly | QIODevice::Text)) { int mediaType = typeFile.readAll().trimmed().toInt(); if (mediaType == ARPHRD_LOOPBACK) { product = QLatin1String("Loopback device Interface"); } else { product = m_device.deviceProperty("ID_MODEL_FROM_DATABASE").toString(); } } } else if(queryDeviceInterface(Solid::DeviceInterface::SerialInterface)) { const SerialInterface serialIface(const_cast(this)); if (serialIface.serialType() == Solid::SerialInterface::Platform) { product.append(QLatin1String("Platform serial")); } else if (serialIface.serialType() == Solid::SerialInterface::Usb) { product.append(QLatin1String("USB Serial Port")); } } */ if (product.isEmpty()) { product = m_device.deviceProperty("ID_MODEL").toString().replace('_', ' '); } } return product; } QString UDevDevice::icon() const { if (parentUdi().isEmpty()) { return QLatin1String("computer"); } /*if (queryDeviceInterface(Solid::DeviceInterface::Processor)) { return QLatin1String("cpu"); } else*/ if (queryDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) { // TODO: check out special cases like iPod return QLatin1String("multimedia-player"); } /*else if (queryDeviceInterface(Solid::DeviceInterface::Camera)) { return QLatin1String("camera-photo"); } else if (queryDeviceInterface(Solid::DeviceInterface::Video)) { return QLatin1String("camera-web"); } else if (queryDeviceInterface(Solid::DeviceInterface::AudioInterface)) { const AudioInterface audioIface(const_cast(this)); switch (audioIface.soundcardType()) { case Solid::AudioInterface::InternalSoundcard: return QLatin1String("audio-card"); case Solid::AudioInterface::UsbSoundcard: return QLatin1String("audio-card-usb"); case Solid::AudioInterface::FirewireSoundcard: return QLatin1String("audio-card-firewire"); case Solid::AudioInterface::Headset: if (udi().contains("usb", Qt::CaseInsensitive) || audioIface.name().contains("usb", Qt::CaseInsensitive)) { return QLatin1String("audio-headset-usb"); } else { return QLatin1String("audio-headset"); } case Solid::AudioInterface::Modem: return QLatin1String("modem"); } } else if (queryDeviceInterface(Solid::DeviceInterface::SerialInterface)) { // TODO - a serial device can be a modem, or just // a COM port - need a new icon? return QLatin1String("modem"); } */ return QString(); } QStringList UDevDevice::emblems() const { return QStringList(); } QString UDevDevice::description() const { if (parentUdi().isEmpty()) { return QObject::tr("Computer"); } /*if (queryDeviceInterface(Solid::DeviceInterface::Processor)) { return QObject::tr("Processor"); } else*/ if (queryDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) { // TODO: check out special cases like iPod return QObject::tr("Portable Media Player"); } /*else if (queryDeviceInterface(Solid::DeviceInterface::Camera)) { return QObject::tr("Camera"); } else if (queryDeviceInterface(Solid::DeviceInterface::Video)) { return product(); } else if (queryDeviceInterface(Solid::DeviceInterface::AudioInterface)) { return product(); } else if (queryDeviceInterface(Solid::DeviceInterface::NetworkInterface)) { const NetworkInterface networkIface(const_cast(this)); if (networkIface.isWireless()) { return QObject::tr("WLAN Interface"); } return QObject::tr("Networking Interface"); } */ return QString(); } bool UDevDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const { switch (type) { case Solid::DeviceInterface::GenericInterface: return true; /*case Solid::DeviceInterface::Processor: return property("DRIVER").toString() == "processor"; case Solid::DeviceInterface::Camera: return property("ID_GPHOTO2").toInt() == 1; */ case Solid::DeviceInterface::PortableMediaPlayer: return !property("ID_MEDIA_PLAYER").toString().isEmpty(); /* case Solid::DeviceInterface::DvbInterface: return m_device.subsystem() == QLatin1String("dvb"); */ case Solid::DeviceInterface::Block: return !property("MAJOR").toString().isEmpty(); /* case Solid::DeviceInterface::Video: return m_device.subsystem() == QLatin1String("video4linux"); case Solid::DeviceInterface::AudioInterface: return m_device.subsystem() == QLatin1String("sound"); case Solid::DeviceInterface::NetworkInterface: return m_device.subsystem() == QLatin1String("net"); case Solid::DeviceInterface::SerialInterface: return m_device.subsystem() == QLatin1String("tty"); */ default: return false; } } QObject *UDevDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type) { if (!queryDeviceInterface(type)) { return 0; } switch (type) { case Solid::DeviceInterface::GenericInterface: return new GenericInterface(this); /* case Solid::DeviceInterface::Processor: return new Processor(this); case Solid::DeviceInterface::Camera: return new Camera(this); */ case Solid::DeviceInterface::PortableMediaPlayer: return new PortableMediaPlayer(this); /* case Solid::DeviceInterface::DvbInterface: return new DvbInterface(this); */ case Solid::DeviceInterface::Block: return new Block(this); /* case Solid::DeviceInterface::Video: return new Video(this); case Solid::DeviceInterface::AudioInterface: return new AudioInterface(this); case Solid::DeviceInterface::NetworkInterface: return new NetworkInterface(this); case Solid::DeviceInterface::SerialInterface: return new SerialInterface(this); */ default: // qFatal("Shouldn't happen"); return 0; } } QString UDevDevice::device() const { return devicePath(); } QVariant UDevDevice::property(const QString &key) const { const QVariant res = m_device.deviceProperty(key); if (res.isValid()) { return res; } return m_device.sysfsProperty(key); } QMap UDevDevice::allProperties() const { QMap res; foreach (const QString &prop, m_device.deviceProperties()) { res[prop] = property(prop); } return res; } bool UDevDevice::propertyExists(const QString &key) const { return m_device.deviceProperties().contains(key); } QString UDevDevice::systemAttribute(const char *attribute) const { return m_device.sysfsProperty(attribute).toString(); } QString UDevDevice::deviceName() const { return m_device.sysfsPath(); } int UDevDevice::deviceNumber() const { return m_device.sysfsNumber(); } QString UDevDevice::devicePath() const { return QString(UDEV_UDI_PREFIX) + deviceName(); } UdevQt::Device UDevDevice::udevDevice() { return m_device; } cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevdevice.h000066400000000000000000000042451316350454000237620ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_UDEVDEVICE_H #define SOLID_BACKENDS_UDEV_UDEVDEVICE_H #include "udev.h" #include #include namespace Solid { namespace Backends { namespace UDev { class UDevDevice : public Solid::Ifaces::Device { Q_OBJECT public: UDevDevice(const UdevQt::Device device); virtual ~UDevDevice(); virtual QString udi() const; virtual QString parentUdi() const; virtual QString vendor() const; virtual QString product() const; virtual QString icon() const; virtual QStringList emblems() const; virtual QString description() const; virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const; virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type); QString device() const; QVariant property(const QString &key) const; QMap allProperties() const; bool propertyExists(const QString &key) const; QString systemAttribute(const char *attribute) const; QString deviceName() const; QString devicePath() const; int deviceNumber() const; UdevQt::Device udevDevice(); private: UdevQt::Device m_device; }; } } } #endif // SOLID_BACKENDS_UDEV_UDEVDEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevdeviceinterface.cpp000066400000000000000000000022521316350454000261720ustar00rootroot00000000000000/* Copyright 2010 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevdeviceinterface.h" using namespace Solid::Backends::UDev; DeviceInterface::DeviceInterface(UDevDevice *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } //#include "backends/udev/udevdeviceinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevdeviceinterface.h000066400000000000000000000027101316350454000256360ustar00rootroot00000000000000/* Copyright 2010 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_DEVICEINTERFACE_H #define SOLID_BACKENDS_UDEV_DEVICEINTERFACE_H #include #include "udevdevice.h" #include #include namespace Solid { namespace Backends { namespace UDev { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(UDevDevice *device); virtual ~DeviceInterface(); protected: UDevDevice *m_device; }; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevgenericinterface.cpp000066400000000000000000000034101316350454000263440ustar00rootroot00000000000000/* Copyright 2010 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevgenericinterface.h" #include "udevdevice.h" using namespace Solid::Backends::UDev; GenericInterface::GenericInterface(UDevDevice *device) : DeviceInterface(device) { #if 0 connect(device, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); connect(device, SIGNAL(conditionRaised(QString,QString)), this, SIGNAL(conditionRaised(QString,QString))); #endif } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->property(key); } QMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } //#include "backends/udev/udevgenericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevgenericinterface.h000066400000000000000000000034201316350454000260120ustar00rootroot00000000000000/* Copyright 2010 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_GENERICINTERFACE_H #define SOLID_BACKENDS_UDEV_GENERICINTERFACE_H #include #include #include "udevdeviceinterface.h" namespace Solid { namespace Backends { namespace UDev { class UDevDevice; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(UDevDevice *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevmanager.cpp000066400000000000000000000160301316350454000244630ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevmanager.h" #include "udev.h" #include "udevdevice.h" #include "../shared/rootdevice.h" #include #include #include #define DETAILED_OUTPUT 0 using namespace Solid::Backends::UDev; using namespace Solid::Backends::Shared; class UDevManager::Private { public: Private(); ~Private(); bool isOfInterest(const UdevQt::Device &device); UdevQt::Client *m_client; QSet m_supportedInterfaces; }; UDevManager::Private::Private() { QStringList subsystems; subsystems << "processor"; subsystems << "sound"; subsystems << "tty"; subsystems << "dvb"; subsystems << "video4linux"; subsystems << "net"; subsystems << "usb"; m_client = new UdevQt::Client(subsystems); } UDevManager::Private::~Private() { delete m_client; } bool UDevManager::Private::isOfInterest(const UdevQt::Device &device) { #if DETAILED_OUTPUT qDebug() << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; qDebug() << "Path:" << device.sysfsPath(); qDebug() << "Properties:" << device.deviceProperties(); Q_FOREACH (const QString &key, device.deviceProperties()) { qDebug() << "\t" << key << ":" << device.deviceProperty(key).toString(); } qDebug() << "Driver:" << device.driver(); qDebug() << "Subsystem:" << device.subsystem(); qDebug() << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; #endif if (device.driver() == QLatin1String("processor")) { // Linux ACPI reports processor slots, rather than processors. // Empty slots will not have a system device associated with them. return QFile::exists(device.sysfsPath() + "/sysdev"); } if (device.subsystem() == QLatin1String("sound") && device.deviceProperty("SOUND_FORM_FACTOR").toString() != "internal") { return true; } if (device.subsystem() == QLatin1String("tty")) { QString path = device.deviceProperty("DEVPATH").toString(); int lastSlash = path.length() - path.lastIndexOf(QLatin1String("/")) -1; #if QT_VERSION < 0x050000 QByteArray lastElement = path.right(lastSlash).toAscii(); #else QByteArray lastElement = path.right(lastSlash).toLatin1(); #endif if (lastElement.startsWith("tty") && !path.startsWith("/devices/virtual")) { return true; } } return device.subsystem() == QLatin1String("dvb") || device.subsystem() == QLatin1String("video4linux") || device.subsystem() == QLatin1String("net") || device.deviceProperty("ID_MEDIA_PLAYER").toString().isEmpty() == false || // media-player-info recognized devices device.deviceProperty("ID_GPHOTO2").toInt() == 1; // GPhoto2 cameras } UDevManager::UDevManager(QObject *parent) : Solid::Ifaces::DeviceManager(parent), d(new Private) { connect(d->m_client, SIGNAL(deviceAdded(UdevQt::Device)), this, SLOT(slotDeviceAdded(UdevQt::Device))); connect(d->m_client, SIGNAL(deviceRemoved(UdevQt::Device)), this, SLOT(slotDeviceRemoved(UdevQt::Device))); d->m_supportedInterfaces << Solid::DeviceInterface::GenericInterface /*<< Solid::DeviceInterface::Processor << Solid::DeviceInterface::AudioInterface << Solid::DeviceInterface::NetworkInterface << Solid::DeviceInterface::SerialInterface << Solid::DeviceInterface::Camera */ << Solid::DeviceInterface::PortableMediaPlayer //<< Solid::DeviceInterface::DvbInterface << Solid::DeviceInterface::Block /*<< Solid::DeviceInterface::Video*/; } UDevManager::~UDevManager() { delete d; } QString UDevManager::udiPrefix() const { return QString::fromLatin1(UDEV_UDI_PREFIX); } QSet UDevManager::supportedInterfaces() const { return d->m_supportedInterfaces; } QStringList UDevManager::allDevices() { QStringList res; const UdevQt::DeviceList deviceList = d->m_client->allDevices(); foreach (const UdevQt::Device &device, deviceList) { if (d->isOfInterest(device)) { res << udiPrefix() + device.sysfsPath(); } } return res; } QStringList UDevManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type) { QStringList allDev = allDevices(); QStringList result; if (!parentUdi.isEmpty()) { foreach (const QString &udi, allDev) { UDevDevice device(d->m_client->deviceBySysfsPath(udi.right(udi.size() - udiPrefix().size()))); if (device.queryDeviceInterface(type) && device.parentUdi() == parentUdi) { result << udi; } } return result; } else if (type != Solid::DeviceInterface::Unknown) { foreach (const QString &udi, allDev) { UDevDevice device(d->m_client->deviceBySysfsPath(udi.right(udi.size() - udiPrefix().size()))); if (device.queryDeviceInterface(type)) { result << udi; } } return result; } else { return allDev; } } QObject *UDevManager::createDevice(const QString &udi_) { if (udi_ == udiPrefix()) { RootDevice *const device = new RootDevice(UDEV_UDI_PREFIX); device->setProduct(tr("Devices")); device->setDescription(tr("Devices declared in your system")); device->setIcon("computer"); return device; } const QString udi = udi_.right(udi_.size() - udiPrefix().size()); UdevQt::Device device = d->m_client->deviceBySysfsPath(udi); if (d->isOfInterest(device) || QFile::exists(udi)) { return new UDevDevice(device); } return 0; } void UDevManager::slotDeviceAdded(const UdevQt::Device &device) { if (d->isOfInterest(device)) { emit deviceAdded(udiPrefix() + device.sysfsPath()); } } void UDevManager::slotDeviceRemoved(const UdevQt::Device &device) { if (d->isOfInterest(device)) { emit deviceRemoved(udiPrefix() + device.sysfsPath()); } } cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevmanager.h000066400000000000000000000035531316350454000241360ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_UDEVMANAGER_H #define SOLID_BACKENDS_UDEV_UDEVMANAGER_H #include #include "../shared/udevqt.h" namespace Solid { namespace Backends { namespace UDev { class UDevManager : public Solid::Ifaces::DeviceManager { Q_OBJECT public: UDevManager(QObject *parent); virtual ~UDevManager(); virtual QString udiPrefix() const; virtual QSet supportedInterfaces() const; virtual QStringList allDevices(); virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type); virtual QObject *createDevice(const QString &udi); private Q_SLOTS: void slotDeviceAdded(const UdevQt::Device &device); void slotDeviceRemoved(const UdevQt::Device &device); private: class Private; Private *const d; }; } } } #endif // SOLID_BACKENDS_UDEV_UDEVMANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevportablemediaplayer.cpp000066400000000000000000000125211316350454000270770ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López 2010 Lukas Tinkl 2011 Matej Laitl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udevportablemediaplayer.h" #include "solid-lite/xdgbasedirs_p.h" #include #include #include #include using namespace Solid::Backends::UDev; /** * Reads one value from media-player-info ini-like file. * * @param file file to open. May advance current seek position * @param group group name to read from, e.g. "Device" for [Device] group * @param key key name, e.g. "AccessProtocol" * @return value as a string or an empty string */ static QString readMpiValue(QIODevice &file, const QString &group, const QString &key) { QTextStream mpiStream(&file); QString line; QString currGroup; while (!mpiStream.atEnd()) { line = mpiStream.readLine().trimmed(); // trimmed is needed for possible indentation if (line.isEmpty() || line.startsWith(QChar(';'))) { // skip empty and comment lines } else if (line.startsWith(QChar('[')) && line.endsWith(QChar(']'))) { currGroup = line.mid(1, line.length() - 2); // strip [ and ] } else if (line.indexOf(QChar('=') != -1)) { int index = line.indexOf(QChar('=')); if (currGroup == group && line.left(index) == key) { line = line.right(line.length() - index - 1); if (line.startsWith(QChar('"')) && line.endsWith(QChar('"'))) { line = line.mid(1, line.length() - 2); // strip enclosing double quotes } return line; } } else { qWarning() << "readMpiValue: cannot parse line:" << line; } } return QString(); } PortableMediaPlayer::PortableMediaPlayer(UDevDevice *device) : DeviceInterface(device) { } PortableMediaPlayer::~PortableMediaPlayer() { } QStringList PortableMediaPlayer::supportedProtocols() const { /* There are multiple packages that set ID_MEDIA_PLAYER: * * gphoto2 sets it to numeric 1 (for _some_ cameras it supports) and it hopefully * means MTP-compatible device. * * libmtp >= 1.0.4 sets it to numeric 1 and this always denotes MTP-compatible player. * * media-player-info sets it to a string that denotes a name of the .mpi file with * additional info. */ if (m_device->property("ID_MEDIA_PLAYER").toInt() == 1) { return QStringList() << "mtp"; } QString mpiFileName = mediaPlayerInfoFilePath(); if (mpiFileName.isEmpty()) { return QStringList(); } // we unfornutately cannot use QSettings as it cannot read unquoted valued with semicolons in it QFile mpiFile(mpiFileName); if (!mpiFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Cannot open" << mpiFileName << "for reading." << "Check your media-player-info installation."; return QStringList(); } QString value = readMpiValue(mpiFile, QString("Device"), QString("AccessProtocol")); return value.split(QChar(';'), QString::SkipEmptyParts); } QStringList PortableMediaPlayer::supportedDrivers(QString protocol) const { Q_UNUSED(protocol) QStringList res; if (!supportedProtocols().isEmpty()) { res << "usb"; } if (m_device->property("USBMUX_SUPPORTED").toBool() == true) { res << "usbmux"; } return res; } QVariant PortableMediaPlayer::driverHandle(const QString &driver) const { if (driver == "mtp" || driver == "usbmux") return m_device->property("ID_SERIAL_SHORT"); return QVariant(); } QString PortableMediaPlayer::mediaPlayerInfoFilePath() const { QString relativeFilename = m_device->property("ID_MEDIA_PLAYER").toString(); if (relativeFilename.isEmpty()) { qWarning() << "We attached PortableMediaPlayer interface to device" << m_device->udi() << "but m_device->property(\"ID_MEDIA_PLAYER\") is empty???"; return QString(); } relativeFilename.prepend("media-player-info/"); relativeFilename.append(".mpi"); QString filename = Solid::XdgBaseDirs::findResourceFile("data", relativeFilename); if (filename.isEmpty()) { qWarning() << "media player info file" << relativeFilename << "not found under user and" << "system XDG data directories. Do you have media-player-info installed?"; } return filename; } //#include "backends/udev/udevportablemediaplayer.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udev/udevportablemediaplayer.h000066400000000000000000000037721316350454000265540ustar00rootroot00000000000000/* Copyright 2010 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDEV_PORTABLEMEDIAPLAYER_H #define SOLID_BACKENDS_UDEV_PORTABLEMEDIAPLAYER_H #include #include "udevdeviceinterface.h" #include namespace Solid { namespace Backends { namespace UDev { class UDevDevice; class PortableMediaPlayer : public DeviceInterface, virtual public Solid::Ifaces::PortableMediaPlayer { Q_OBJECT Q_INTERFACES(Solid::Ifaces::PortableMediaPlayer) public: PortableMediaPlayer(UDevDevice *device); virtual ~PortableMediaPlayer(); virtual QStringList supportedProtocols() const; virtual QStringList supportedDrivers(QString protocol = QString()) const; virtual QVariant driverHandle(const QString &driver) const; private: /** * Return full absolute path to media-player-info .mpi file, based on ID_MEDIA_PLAYER * udev property. Does not check for existence. Returns empty string in case no reasonable * file path could be determined. */ QString mediaPlayerInfoFilePath() const; }; } } } #endif // SOLID_BACKENDS_UDEV_PORTABLEMEDIAPLAYER_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/000077500000000000000000000000001316350454000220205ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisks.h000066400000000000000000000035531316350454000235010ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDISKS_H #define SOLID_BACKENDS_UDISKS_H /* UDisks */ #define UD_DBUS_SERVICE "org.freedesktop.UDisks" #define UD_DBUS_PATH "/org/freedesktop/UDisks" #define UD_DBUS_INTERFACE_DISKS "org.freedesktop.UDisks" #define UD_DBUS_INTERFACE_DISKS_DEVICE "org.freedesktop.UDisks.Device" #define UD_UDI_DISKS_PREFIX "/org/freedesktop/UDisks" /* errors */ #define UD_ERROR_UNAUTHORIZED "org.freedesktop.PolicyKit.Error.NotAuthorized" #define UD_ERROR_BUSY "org.freedesktop.UDisks.Error.Busy" #define UD_ERROR_FAILED "org.freedesktop.UDisks.Error.Failed" #define UD_ERROR_CANCELED "org.freedesktop.UDisks.Error.Cancelled" #define UD_ERROR_INVALID_OPTION "org.freedesktop.UDisks.Error.InvalidOption" #define UD_ERROR_MISSING_DRIVER "org.freedesktop.UDisks.Error.FilesystemDriverMissing" #endif // SOLID_BACKENDS_UDISKS_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksblock.cpp000066400000000000000000000025141316350454000250430ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksblock.h" using namespace Solid::Backends::UDisks; Block::Block(UDisksDevice *device) : DeviceInterface(device) { } Block::~Block() { } QString Block::device() const { return m_device->prop("DeviceFile").toString(); } int Block::deviceMinor() const { return m_device->prop("DeviceMinor").toInt(); } int Block::deviceMajor() const { return m_device->prop("DeviceMajor").toInt(); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksblock.h000066400000000000000000000026531316350454000245140ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSBLOCK_H #define UDISKSBLOCK_H #include #include "udisksdeviceinterface.h" namespace Solid { namespace Backends { namespace UDisks { class Block: public DeviceInterface, virtual public Solid::Ifaces::Block { Q_OBJECT Q_INTERFACES(Solid::Ifaces::Block) public: Block(UDisksDevice *device); virtual ~Block(); virtual QString device() const; virtual int deviceMinor() const; virtual int deviceMajor() const; }; } } } #endif // UDISKSBLOCK_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksdevice.cpp000066400000000000000000000652111316350454000252130ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisks.h" #include "udisksdevice.h" #include "udisksdeviceinterface.h" #include "udisksstoragevolume.h" #include "udisksstoragedrive.h" #include "udisksopticaldisc.h" #include "udisksopticaldrive.h" #include "udisksstorageaccess.h" #include "udisksgenericinterface.h" #include #include #include #include #include #include #include #include using namespace Solid::Backends::UDisks; // Adapted from KLocale as Solid needs to be Qt-only static QString formatByteSize(double size) { // Per IEC 60027-2 // Binary prefixes //Tebi-byte TiB 2^40 1,099,511,627,776 bytes //Gibi-byte GiB 2^30 1,073,741,824 bytes //Mebi-byte MiB 2^20 1,048,576 bytes //Kibi-byte KiB 2^10 1,024 bytes QString s; // Gibi-byte if ( size >= 1073741824.0 ) { size /= 1073741824.0; if ( size > 1024 ) // Tebi-byte s = QObject::tr("%1 TiB").arg(QLocale().toString(size / 1024.0, 'f', 1)); else s = QObject::tr("%1 GiB").arg(QLocale().toString(size, 'f', 1)); } // Mebi-byte else if ( size >= 1048576.0 ) { size /= 1048576.0; s = QObject::tr("%1 MiB").arg(QLocale().toString(size, 'f', 1)); } // Kibi-byte else if ( size >= 1024.0 ) { size /= 1024.0; s = QObject::tr("%1 KiB").arg(QLocale().toString(size, 'f', 1)); } // Just byte else if ( size > 0 ) { s = QObject::tr("%1 B").arg(QLocale().toString(size, 'f', 1)); } // Nothing else { s = QObject::tr("0 B"); } return s; } UDisksDevice::UDisksDevice(const QString &udi) : Solid::Ifaces::Device() , m_udi(udi) { QString realUdi = m_udi; if (realUdi.endsWith(":media")) { realUdi.chop(6); } m_device = new QDBusInterface(UD_DBUS_SERVICE, realUdi, UD_DBUS_INTERFACE_DISKS_DEVICE, QDBusConnection::systemBus()); if (m_device->isValid()) connect(m_device, SIGNAL(Changed()), this, SLOT(slotChanged())); } UDisksDevice::~UDisksDevice() { delete m_device; } QObject* UDisksDevice::createDeviceInterface(const Solid::DeviceInterface::Type& type) { if (!queryDeviceInterface(type)) { return 0; } DeviceInterface *iface = 0; switch (type) { case Solid::DeviceInterface::GenericInterface: iface = new GenericInterface(this); break; case Solid::DeviceInterface::Block: iface = new Block(this); break; case Solid::DeviceInterface::StorageAccess: iface = new UDisksStorageAccess(this); break; case Solid::DeviceInterface::StorageDrive: iface = new UDisksStorageDrive(this); break; case Solid::DeviceInterface::OpticalDrive: iface = new UDisksOpticalDrive(this); break; case Solid::DeviceInterface::StorageVolume: iface = new UDisksStorageVolume(this); break; case Solid::DeviceInterface::OpticalDisc: iface = new OpticalDisc(this); break; default: break; } return iface; } bool UDisksDevice::queryDeviceInterface(const Solid::DeviceInterface::Type& type) const { switch (type) { case Solid::DeviceInterface::GenericInterface: return true; case Solid::DeviceInterface::Block: return prop("DeviceMajor").toInt() != -1; case Solid::DeviceInterface::StorageVolume: if (prop("DeviceIsOpticalDisc").toBool()) { return m_udi.endsWith(":media"); } else { return prop("DeviceIsPartition").toBool() || prop("IdUsage").toString()=="filesystem" || prop("IdUsage").toString()=="crypto"; } case Solid::DeviceInterface::StorageAccess: if (prop("DeviceIsOpticalDisc").toBool()) { return prop("IdUsage").toString()=="filesystem" && m_udi.endsWith(":media"); } else { return prop("IdUsage").toString()=="filesystem" || prop("IdUsage").toString()=="crypto"; } case Solid::DeviceInterface::StorageDrive: return !m_udi.endsWith(":media") && prop("DeviceIsDrive").toBool(); case Solid::DeviceInterface::OpticalDrive: return !m_udi.endsWith(":media") && prop( "DeviceIsDrive" ).toBool() && !prop( "DriveMediaCompatibility" ).toStringList().filter( "optical_" ).isEmpty(); case Solid::DeviceInterface::OpticalDisc: return m_udi.endsWith(":media") && prop("DeviceIsOpticalDisc").toBool(); default: return false; } } QStringList UDisksDevice::emblems() const { QStringList res; if (queryDeviceInterface(Solid::DeviceInterface::StorageAccess)) { bool isEncrypted = false; if (queryDeviceInterface(Solid::DeviceInterface::StorageVolume)) { const UDisks::UDisksStorageVolume volIface(const_cast(this)); isEncrypted = (volIface.usage() == Solid::StorageVolume::Encrypted); } const UDisks::UDisksStorageAccess accessIface(const_cast(this)); if (accessIface.isAccessible()) { if (isEncrypted) res << "emblem-encrypted-unlocked"; else res << "emblem-mounted"; } else { if (isEncrypted) res << "emblem-encrypted-locked"; else res << "emblem-unmounted"; } } return res; } QString UDisksDevice::description() const { if (queryDeviceInterface(Solid::DeviceInterface::StorageDrive)) return storageDescription(); else if (queryDeviceInterface(Solid::DeviceInterface::StorageVolume)) return volumeDescription(); else return product(); } QString UDisksDevice::storageDescription() const { QString description; const UDisks::UDisksStorageDrive storageDrive(const_cast(this)); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); bool drive_is_hotpluggable = storageDrive.isHotpluggable(); const UDisks::UDisksStorageVolume storageVolume(const_cast(this)); if (drive_type == Solid::StorageDrive::CdromDrive) { const UDisks::UDisksOpticalDrive opticalDrive(const_cast(this)); Solid::OpticalDrive::MediumTypes mediumTypes = opticalDrive.supportedMedia(); QString first; QString second; first = QObject::tr("CD-ROM", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdr) first = QObject::tr("CD-R", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdrw) first = QObject::tr("CD-RW", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvd) second = QObject::tr("/DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusr) second = QObject::tr("/DVD+R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusrw) second = QObject::tr("/DVD+RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdr) second = QObject::tr("/DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdrw) second = QObject::tr("/DVD-RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdram) second = QObject::tr("/DVD-RAM", "Second item of %1%2 Drive sentence"); if ((mediumTypes & Solid::OpticalDrive::Dvdr) && (mediumTypes & Solid::OpticalDrive::Dvdplusr)) { if(mediumTypes & Solid::OpticalDrive::Dvdplusdl) second = QObject::tr("/DVD±R DL", "Second item of %1%2 Drive sentence"); else second = QObject::tr("/DVD±R", "Second item of %1%2 Drive sentence"); } if ((mediumTypes & Solid::OpticalDrive::Dvdrw) && (mediumTypes & Solid::OpticalDrive::Dvdplusrw)) { if((mediumTypes & Solid::OpticalDrive::Dvdplusdl) || (mediumTypes & Solid::OpticalDrive::Dvdplusdlrw)) second = QObject::tr("/DVD±RW DL", "Second item of %1%2 Drive sentence"); else second = QObject::tr("/DVD±RW", "Second item of %1%2 Drive sentence"); } if (mediumTypes & Solid::OpticalDrive::Bd) second = QObject::tr("/BD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdr) second = QObject::tr("/BD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdre) second = QObject::tr("/BD-RE", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvd) second = QObject::tr("/HD DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdr) second = QObject::tr("/HD DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdrw) second = QObject::tr("/HD DVD-RW", "Second item of %1%2 Drive sentence"); if (drive_is_hotpluggable) description = QObject::tr("External %1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); else description = QObject::tr("%1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); return description; } /*if (drive_type == Solid::StorageDrive::Floppy) { if (drive_is_hotpluggable) description = QObject::tr("External Floppy Drive"); else description = QObject::tr("Floppy Drive"); return description; }*/ bool drive_is_removable = storageDrive.isRemovable(); if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { QString size_str = formatByteSize(storageVolume.size()); if (!size_str.isEmpty()) { if (drive_is_hotpluggable) description = QObject::tr("%1 External Hard Drive", "%1 is the size").arg(size_str); else description = QObject::tr("%1 Hard Drive", "%1 is the size").arg(size_str); } else { if (drive_is_hotpluggable) description = QObject::tr("External Hard Drive"); else description = QObject::tr("Hard Drive"); } return description; } QString vendormodel_str; QString model = product(); QString vendor_str = vendor(); if (vendor_str.isEmpty()) { if (!model.isEmpty()) vendormodel_str = model; } else { if (model.isEmpty()) vendormodel_str = vendor_str; else { if (model.startsWith(vendor_str)) { // e.g. vendor is "Nokia" and model is "Nokia N950" we do not want "Nokia Nokia N950" as description vendormodel_str = model; } else { vendormodel_str = QObject::tr("%1 %2", "%1 is the vendor, %2 is the model of the device").arg(vendor_str).arg(model); } } } if (vendormodel_str.isEmpty()) description = QObject::tr("Drive"); else description = vendormodel_str; return description; } QString UDisksDevice::volumeDescription() const { QString description; const UDisks::UDisksStorageVolume storageVolume(const_cast(this)); QString volume_label = prop("IdLabel").toString(); if (volume_label.isEmpty()) volume_label = prop("PartitionLabel").toString(); if (!volume_label.isEmpty()) return volume_label; const UDisks::UDisksStorageDrive storageDrive(const_cast(this)); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); // Handle media in optical drives if (drive_type == Solid::StorageDrive::CdromDrive) { const UDisks::OpticalDisc disc(const_cast(this)); switch (disc.discType()) { case Solid::OpticalDisc::UnknownDiscType: case Solid::OpticalDisc::CdRom: description = QObject::tr("CD-ROM"); break; case Solid::OpticalDisc::CdRecordable: if (disc.isBlank()) description = QObject::tr("Blank CD-R"); else description = QObject::tr("CD-R"); break; case Solid::OpticalDisc::CdRewritable: if (disc.isBlank()) description = QObject::tr("Blank CD-RW"); else description = QObject::tr("CD-RW"); break; case Solid::OpticalDisc::DvdRom: description = QObject::tr("DVD-ROM"); break; case Solid::OpticalDisc::DvdRam: if (disc.isBlank()) description = QObject::tr("Blank DVD-RAM"); else description = QObject::tr("DVD-RAM"); break; case Solid::OpticalDisc::DvdRecordable: if (disc.isBlank()) description = QObject::tr("Blank DVD-R"); else description = QObject::tr("DVD-R"); break; case Solid::OpticalDisc::DvdPlusRecordableDuallayer: if (disc.isBlank()) description = QObject::tr("Blank DVD+R Dual-Layer"); else description = QObject::tr("DVD+R Dual-Layer"); break; case Solid::OpticalDisc::DvdRewritable: if (disc.isBlank()) description = QObject::tr("Blank DVD-RW"); else description = QObject::tr("DVD-RW"); break; case Solid::OpticalDisc::DvdPlusRecordable: if (disc.isBlank()) description = QObject::tr("Blank DVD+R"); else description = QObject::tr("DVD+R"); break; case Solid::OpticalDisc::DvdPlusRewritable: if (disc.isBlank()) description = QObject::tr("Blank DVD+RW"); else description = QObject::tr("DVD+RW"); break; case Solid::OpticalDisc::DvdPlusRewritableDuallayer: if (disc.isBlank()) description = QObject::tr("Blank DVD+RW Dual-Layer"); else description = QObject::tr("DVD+RW Dual-Layer"); break; case Solid::OpticalDisc::BluRayRom: description = QObject::tr("BD-ROM"); break; case Solid::OpticalDisc::BluRayRecordable: if (disc.isBlank()) description = QObject::tr("Blank BD-R"); else description = QObject::tr("BD-R"); break; case Solid::OpticalDisc::BluRayRewritable: if (disc.isBlank()) description = QObject::tr("Blank BD-RE"); else description = QObject::tr("BD-RE"); break; case Solid::OpticalDisc::HdDvdRom: description = QObject::tr("HD DVD-ROM"); break; case Solid::OpticalDisc::HdDvdRecordable: if (disc.isBlank()) description = QObject::tr("Blank HD DVD-R"); else description = QObject::tr("HD DVD-R"); break; case Solid::OpticalDisc::HdDvdRewritable: if (disc.isBlank()) description = QObject::tr("Blank HD DVD-RW"); else description = QObject::tr("HD DVD-RW"); break; } // Special case for pure audio disc if (disc.availableContent() == Solid::OpticalDisc::Audio) description = QObject::tr("Audio CD"); return description; } bool drive_is_removable = storageDrive.isRemovable(); bool drive_is_hotpluggable = storageDrive.isHotpluggable(); bool drive_is_encrypted_container = (storageVolume.usage() == Solid::StorageVolume::Encrypted); QString size_str = formatByteSize(storageVolume.size()); if (drive_is_encrypted_container) { if (!size_str.isEmpty()) description = QObject::tr("%1 Encrypted Container", "%1 is the size").arg(size_str); else description = QObject::tr("Encrypted Container"); } else if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { if (!size_str.isEmpty()) { if (drive_is_hotpluggable) description = QObject::tr("%1 External Hard Drive", "%1 is the size").arg(size_str); else description = QObject::tr("%1 Hard Drive", "%1 is the size").arg(size_str); } else { if (drive_is_hotpluggable) description = QObject::tr("External Hard Drive"); else description = QObject::tr("Hard Drive"); } } else { if (drive_is_removable) description = QObject::tr("%1 Removable Media", "%1 is the size").arg(size_str); else description = QObject::tr("%1 Media", "%1 is the size").arg(size_str); } return description; } QString UDisksDevice::icon() const { QString iconName = prop( "DevicePresentationIconName" ).toString(); if ( !iconName.isEmpty() ) { return iconName; } else { bool isPartition = prop( "DeviceIsPartition" ).toBool(); if ( isPartition ) // this is a slave device, we need to return its parent's icon { UDisksDevice* parent = 0; if ( !parentUdi().isEmpty() ) parent = new UDisksDevice( parentUdi() ); if ( parent ) { iconName = parent->icon(); delete parent; } if ( !iconName.isEmpty() ) return iconName; } // handle mounted ISOs bool isLoop = prop( "DeviceIsLinuxLoop" ).toBool(); QString fstype = prop("IdType").toString(); if( isLoop && ( fstype == "iso9660" || fstype == "udf" ) ) { return "media-optical"; } // handle media const QString media = prop( "DriveMedia" ).toString(); bool isOptical = prop( "DeviceIsOpticalDisc" ).toBool(); if ( !media.isEmpty() ) { if ( isOptical ) // optical stuff { bool isWritable = prop( "OpticalDiscIsBlank" ).toBool() || prop("OpticalDiscIsAppendable").toBool(); const UDisks::OpticalDisc disc(const_cast(this)); Solid::OpticalDisc::ContentTypes availContent = disc.availableContent(); if (availContent & Solid::OpticalDisc::VideoDvd) // Video DVD return "media-optical-dvd-video"; else if ((availContent & Solid::OpticalDisc::VideoCd) || (availContent & Solid::OpticalDisc::SuperVideoCd)) // Video CD return "media-optical-video"; else if ((availContent & Solid::OpticalDisc::Data) && (availContent & Solid::OpticalDisc::Audio)) // Mixed CD return "media-optical-mixed-cd"; else if (availContent & Solid::OpticalDisc::Audio) // Audio CD return "media-optical-audio"; else if (availContent & Solid::OpticalDisc::Data) // Data CD return "media-optical-data"; else if ( isWritable ) return "media-optical-recordable"; else { if ( media.startsWith( "optical_dvd" ) || media.startsWith( "optical_hddvd" ) ) // DVD return "media-optical-dvd"; else if ( media.startsWith( "optical_bd" ) ) // BluRay return "media-optical-blu-ray"; } // fallback for every other optical disc return "media-optical"; } if ( media == "flash_ms" ) // Flash & Co. return "media-flash-memory-stick"; else if ( media == "flash_sd" || media == "flash_sdhc" || media == "flash_mmc" ) return "media-flash-sd-mmc"; else if ( media == "flash_sm" ) return "media-flash-smart-media"; else if ( media.startsWith( "flash" ) ) return "media-flash"; else if ( media == "floppy" ) // the good ol' floppy return "media-floppy"; } // handle drives bool isRemovable = prop( "DeviceIsRemovable" ).toBool(); const QString conn = prop( "DriveConnectionInterface" ).toString(); if ( queryDeviceInterface(Solid::DeviceInterface::OpticalDrive) ) return "drive-optical"; else if ( isRemovable && !isOptical ) { if ( conn == "usb" ) return "drive-removable-media-usb"; else return "drive-removable-media"; } } return "drive-harddisk"; // general fallback } QString UDisksDevice::product() const { QString product = prop("DriveModel").toString(); const bool isDrive = prop( "DeviceIsDrive" ).toBool() && !m_udi.endsWith(":media"); if (!isDrive) { QString label = prop("IdLabel").toString(); if (!label.isEmpty()) { product = label; } } return product; } QString UDisksDevice::vendor() const { return prop("DriveVendor").toString(); } QString UDisksDevice::udi() const { return m_udi; } QString UDisksDevice::parentUdi() const { if (m_udi.endsWith(QLatin1String(":media"))) { QString result = m_udi; return result.remove(":media"); } else if ( prop( "DeviceIsLuksCleartext" ).toBool() ) return prop( "LuksCleartextSlave" ).value().path(); else { QString parent = prop("PartitionSlave").value().path(); if (parent.isEmpty() || parent=="/") { parent = UD_UDI_DISKS_PREFIX; } return parent; } } void UDisksDevice::checkCache(const QString &key) const { if (m_cache.isEmpty()) // recreate the cache allProperties(); if (m_cache.contains(key)) return; QVariant reply = m_device->property(key.toUtf8()); if (reply.isValid()) { m_cache[key] = reply; } else { m_cache[key] = QVariant(); } } QVariant UDisksDevice::prop(const QString &key) const { checkCache(key); return m_cache.value(key); } bool UDisksDevice::propertyExists(const QString &key) const { checkCache(key); return m_cache.contains(key); } QMap UDisksDevice::allProperties() const { QDBusMessage call = QDBusMessage::createMethodCall(m_device->service(), m_device->path(), "org.freedesktop.DBus.Properties", "GetAll"); call << m_device->interface(); QDBusPendingReply< QVariantMap > reply = QDBusConnection::systemBus().asyncCall(call); reply.waitForFinished(); if (reply.isValid()) m_cache = reply.value(); else m_cache.clear(); return m_cache; } void UDisksDevice::slotChanged() { // given we cannot know which property/ies changed, clear the cache m_cache.clear(); emit changed(); } bool UDisksDevice::isDeviceBlacklisted() const { return prop("DevicePresentationHide").toBool() || prop("DeviceMountPaths").toStringList().contains("/boot") || prop("IdLabel").toString() == "System Reserved" || ( prop("IdUsage").toString().isEmpty() && !(prop("OpticalDiscIsBlank").toBool() || (prop("OpticalDiscNumAudioTracks").toInt() > 0) )); } QString UDisksDevice::errorToString(const QString & error) const { if (error == UD_ERROR_UNAUTHORIZED) return QObject::tr("You are not authorized to perform this operation."); else if (error == UD_ERROR_BUSY) return QObject::tr("The device is currently busy."); else if (error == UD_ERROR_FAILED) return QObject::tr("The requested operation has failed."); else if (error == UD_ERROR_CANCELED) return QObject::tr("The requested operation has been canceled."); else if (error == UD_ERROR_INVALID_OPTION) return QObject::tr("An invalid or malformed option has been given."); else if (error == UD_ERROR_MISSING_DRIVER) return QObject::tr("The kernel driver for this filesystem type is not available."); else return QObject::tr("An unspecified error has occurred."); } Solid::ErrorType UDisksDevice::errorToSolidError(const QString & error) const { if (error == UD_ERROR_BUSY) return Solid::DeviceBusy; else if (error == UD_ERROR_FAILED) return Solid::OperationFailed; else if (error == UD_ERROR_CANCELED) return Solid::UserCanceled; else if (error == UD_ERROR_INVALID_OPTION) return Solid::InvalidOption; else if (error == UD_ERROR_MISSING_DRIVER) return Solid::MissingDriver; else return Solid::UnauthorizedOperation; } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksdevice.h000066400000000000000000000046521316350454000246620ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSDEVICE_H #define UDISKSDEVICE_H #include #include #include #include #include namespace Solid { namespace Backends { namespace UDisks { class UDisksDevice : public Solid::Ifaces::Device { Q_OBJECT public: UDisksDevice(const QString &udi); virtual ~UDisksDevice(); virtual QObject* createDeviceInterface(const Solid::DeviceInterface::Type& type); virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type& type) const; virtual QString description() const; virtual QStringList emblems() const; virtual QString icon() const; virtual QString product() const; virtual QString vendor() const; virtual QString udi() const; virtual QString parentUdi() const; QVariant prop(const QString &key) const; bool propertyExists(const QString &key) const; QMap allProperties() const; bool isDeviceBlacklisted() const; QString errorToString(const QString & error) const; Solid::ErrorType errorToSolidError(const QString & error) const; Q_SIGNALS: void changed(); private Q_SLOTS: void slotChanged(); private: QString storageDescription() const; QString volumeDescription() const; mutable QDBusInterface *m_device; QString m_udi; mutable QVariantMap m_cache; void checkCache(const QString &key) const; }; } } } #endif // UDISKSDEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksdeviceinterface.cpp000066400000000000000000000022031316350454000270640ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksdeviceinterface.h" using namespace Solid::Backends::UDisks; DeviceInterface::DeviceInterface(UDisksDevice *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksdeviceinterface.h000066400000000000000000000137471316350454000265500ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSDEVICEINTERFACE_H #define UDISKSDEVICEINTERFACE_H #include #include "udisksdevice.h" #include #include namespace Solid { namespace Backends { namespace UDisks { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(UDisksDevice *device); virtual ~DeviceInterface(); protected: UDisksDevice *m_device; public: inline static QStringList toStringList(Solid::DeviceInterface::Type type) { QStringList list; switch(type) { case Solid::DeviceInterface::GenericInterface: list << "generic"; break; //case Solid::DeviceInterface::Processor: // Doesn't exist with UDisks // break; case Solid::DeviceInterface::Block: list << "block"; break; case Solid::DeviceInterface::StorageAccess: list << "volume"; break; case Solid::DeviceInterface::StorageDrive: list << "storage"; break; case Solid::DeviceInterface::OpticalDrive: list << "storage.cdrom"; break; case Solid::DeviceInterface::StorageVolume: list << "volume"; break; case Solid::DeviceInterface::OpticalDisc: list << "volume.disc"; break; //case Solid::DeviceInterface::Camera: // Doesn't exist with UDisks // break; case Solid::DeviceInterface::PortableMediaPlayer: // Doesn't exist with UDisks break; /* case Solid::DeviceInterface::NetworkInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::AcAdapter: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Battery: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Button: // Doesn't exist with UDisks break; case Solid::DeviceInterface::AudioInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::DvbInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Video: // Doesn't exist with UDisks break; case Solid::DeviceInterface::SerialInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::InternetGateway: break; case Solid::DeviceInterface::SmartCardReader: // Doesn't exist with UDisks case Solid::DeviceInterface::NetworkShare: // Doesn't exist with UDisks break; */ case Solid::DeviceInterface::Unknown: break; case Solid::DeviceInterface::Last: break; } return list; } inline static Solid::DeviceInterface::Type fromString(const QString &capability) { if (capability == "generic") return Solid::DeviceInterface::GenericInterface; /*else if (capability == "processor") return Solid::DeviceInterface::Processor; */ else if (capability == "block") return Solid::DeviceInterface::Block; else if (capability == "storage") return Solid::DeviceInterface::StorageDrive; else if (capability == "storage.cdrom") return Solid::DeviceInterface::OpticalDrive; else if (capability == "volume") return Solid::DeviceInterface::StorageVolume; else if (capability == "volume.disc") return Solid::DeviceInterface::OpticalDisc; /*else if (capability == "camera") return Solid::DeviceInterface::Camera;*/ else if (capability == "portable_audio_player") return Solid::DeviceInterface::PortableMediaPlayer; /*else if (capability == "net") return Solid::DeviceInterface::NetworkInterface; else if (capability == "ac_adapter") return Solid::DeviceInterface::AcAdapter; else if (capability == "battery") return Solid::DeviceInterface::Battery; else if (capability == "button") return Solid::DeviceInterface::Button; else if (capability == "alsa" || capability == "oss") return Solid::DeviceInterface::AudioInterface; else if (capability == "dvb") return Solid::DeviceInterface::DvbInterface; else if (capability == "video4linux") return Solid::DeviceInterface::Video; else if (capability == "serial") return Solid::DeviceInterface::SerialInterface; else if (capability == "smart_card_reader") return Solid::DeviceInterface::SmartCardReader; else if (capability == "networkshare") return Solid::DeviceInterface::NetworkShare;*/ else return Solid::DeviceInterface::Unknown; } }; } } } #endif // UDISKSDEVICEINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksgenericinterface.cpp000066400000000000000000000034141316350454000272460ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksgenericinterface.h" #include "udisksdevice.h" using namespace Solid::Backends::UDisks; GenericInterface::GenericInterface(UDisksDevice *device) : DeviceInterface(device) { /* TODO connect(device, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); connect(device, SIGNAL(conditionRaised(QString,QString)), this, SIGNAL(conditionRaised(QString,QString))); */ } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->prop(key); } QMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } //#include "backends/udisks/udisksgenericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksgenericinterface.h000066400000000000000000000035101316350454000267100ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDISKS_GENERICINTERFACE_H #define SOLID_BACKENDS_UDISKS_GENERICINTERFACE_H #include #include #include "udisksdeviceinterface.h" namespace Solid { namespace Backends { namespace UDisks { class UDisksDevice; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(UDisksDevice *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif // SOLID_BACKENDS_UDISKS_GENERICINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksmanager.cpp000066400000000000000000000201641316350454000253640ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksmanager.h" #include "udisks.h" #include #include #include #include #include "../shared/rootdevice.h" using namespace Solid::Backends::UDisks; using namespace Solid::Backends::Shared; UDisksManager::UDisksManager(QObject *parent) : Solid::Ifaces::DeviceManager(parent), m_manager(UD_DBUS_SERVICE, UD_DBUS_PATH, UD_DBUS_INTERFACE_DISKS, QDBusConnection::systemBus()) { m_supportedInterfaces << Solid::DeviceInterface::GenericInterface << Solid::DeviceInterface::Block << Solid::DeviceInterface::StorageAccess << Solid::DeviceInterface::StorageDrive << Solid::DeviceInterface::OpticalDrive << Solid::DeviceInterface::OpticalDisc << Solid::DeviceInterface::StorageVolume; qDBusRegisterMetaType >(); qDBusRegisterMetaType(); bool serviceFound = m_manager.isValid(); if (!serviceFound) { // find out whether it will be activated automatically QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListActivatableNames"); QDBusReply reply = QDBusConnection::systemBus().call(message); if (reply.isValid() && reply.value().contains(UD_DBUS_SERVICE)) { QDBusConnection::systemBus().interface()->startService(UD_DBUS_SERVICE); serviceFound = true; } } if (serviceFound) { connect(&m_manager, SIGNAL(DeviceAdded(QDBusObjectPath)), this, SLOT(slotDeviceAdded(QDBusObjectPath))); connect(&m_manager, SIGNAL(DeviceRemoved(QDBusObjectPath)), this, SLOT(slotDeviceRemoved(QDBusObjectPath))); connect(&m_manager, SIGNAL(DeviceChanged(QDBusObjectPath)), this, SLOT(slotDeviceChanged(QDBusObjectPath))); } } UDisksManager::~UDisksManager() { } QObject* UDisksManager::createDevice(const QString& udi) { if (udi==udiPrefix()) { RootDevice *root = new RootDevice(udi); root->setProduct(tr("Storage")); root->setDescription(tr("Storage devices")); root->setIcon("server-database"); // Obviously wasn't meant for that, but maps nicely in oxygen icon set :-p return root; } else if (deviceCache().contains(udi)) { return new UDisksDevice(udi); } else { return 0; } } QStringList UDisksManager::devicesFromQuery(const QString& parentUdi, Solid::DeviceInterface::Type type) { QStringList result; if (!parentUdi.isEmpty()) { foreach (const QString &udi, deviceCache()) { if (udi==udiPrefix()) continue; UDisksDevice device(udi); if (device.queryDeviceInterface(type) && device.parentUdi() == parentUdi) result << udi; } return result; } else if (type != Solid::DeviceInterface::Unknown) { foreach (const QString &udi, deviceCache()) { if (udi==udiPrefix()) continue; UDisksDevice device(udi); if (device.queryDeviceInterface(type)) result << udi; } return result; } return deviceCache(); } QStringList UDisksManager::allDevices() { m_knownDrivesWithMedia.clear(); m_deviceCache.clear(); m_deviceCache << udiPrefix(); foreach(const QString &udi, allDevicesInternal()) { m_deviceCache.append(udi); UDisksDevice device(udi); if (device.queryDeviceInterface(Solid::DeviceInterface::OpticalDrive)) // forge a special (separate) device for optical discs { if (device.prop("DeviceIsOpticalDisc").toBool()) { if (!m_knownDrivesWithMedia.contains(udi)) m_knownDrivesWithMedia.append(udi); m_deviceCache.append(udi + ":media"); } } } return m_deviceCache; } QStringList UDisksManager::allDevicesInternal() { QDBusReply > reply = m_manager.call("EnumerateDevices"); if (!reply.isValid()) { qWarning() << Q_FUNC_INFO << " error: " << reply.error().name(); return QStringList(); } QStringList retList; foreach(const QDBusObjectPath &path, reply.value()) { retList << path.path(); } return retList; } QSet< Solid::DeviceInterface::Type > UDisksManager::supportedInterfaces() const { return m_supportedInterfaces; } QString UDisksManager::udiPrefix() const { return UD_UDI_DISKS_PREFIX; } void UDisksManager::slotDeviceAdded(const QDBusObjectPath &opath) { const QString udi = opath.path(); if (!m_deviceCache.contains(udi)) { m_deviceCache.append(udi); } UDisksDevice device(udi); if (device.queryDeviceInterface(Solid::DeviceInterface::StorageDrive) && !device.prop("DeviceIsMediaAvailable").toBool() && !m_dirtyDevices.contains(udi)) m_dirtyDevices.append(udi); emit deviceAdded(udi); slotDeviceChanged(opath); // case: hotswap event (optical drive with media inside) } void UDisksManager::slotDeviceRemoved(const QDBusObjectPath &opath) { const QString udi = opath.path(); // case: hotswap event (optical drive with media inside) if (m_knownDrivesWithMedia.contains(udi)) { m_knownDrivesWithMedia.removeAll(udi); m_deviceCache.removeAll(udi + ":media"); emit deviceRemoved(udi + ":media"); } if (m_dirtyDevices.contains(udi)) m_dirtyDevices.removeAll(udi); emit deviceRemoved(udi); m_deviceCache.removeAll(opath.path()); } void UDisksManager::slotDeviceChanged(const QDBusObjectPath &opath) { const QString udi = opath.path(); UDisksDevice device(udi); if (device.queryDeviceInterface(Solid::DeviceInterface::OpticalDrive)) { if (!m_knownDrivesWithMedia.contains(udi) && device.prop("DeviceIsOpticalDisc").toBool()) { m_knownDrivesWithMedia.append(udi); if (!m_deviceCache.isEmpty()) { m_deviceCache.append(udi + ":media"); } emit deviceAdded(udi + ":media"); } if (m_knownDrivesWithMedia.contains(udi) && !device.prop("DeviceIsOpticalDisc").toBool()) { m_knownDrivesWithMedia.removeAll(udi); m_deviceCache.removeAll(udi + ":media"); emit deviceRemoved(udi + ":media"); } } if (device.queryDeviceInterface(Solid::DeviceInterface::StorageDrive) && device.prop("DeviceIsMediaAvailable").toBool() && m_dirtyDevices.contains(udi)) { //qDebug() << "dirty device added:" << udi; emit deviceAdded(udi); m_dirtyDevices.removeAll(udi); } } const QStringList &UDisksManager::deviceCache() { if (m_deviceCache.isEmpty()) allDevices(); return m_deviceCache; } //#include "backends/udisks/udisksmanager.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksmanager.h000066400000000000000000000043451316350454000250340ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSMANAGER_H #define UDISKSMANAGER_H #include "udisksdevice.h" #include "solid-lite/ifaces/devicemanager.h" #include #include namespace Solid { namespace Backends { namespace UDisks { class UDisksManager : public Solid::Ifaces::DeviceManager { Q_OBJECT public: UDisksManager(QObject *parent); virtual QObject* createDevice(const QString& udi); virtual QStringList devicesFromQuery(const QString& parentUdi, Solid::DeviceInterface::Type type); virtual QStringList allDevices(); virtual QSet< Solid::DeviceInterface::Type > supportedInterfaces() const; virtual QString udiPrefix() const; virtual ~UDisksManager(); private Q_SLOTS: void slotDeviceAdded(const QDBusObjectPath &opath); void slotDeviceRemoved(const QDBusObjectPath &opath); void slotDeviceChanged(const QDBusObjectPath &opath); private: const QStringList &deviceCache(); QStringList allDevicesInternal(); QStringList m_knownDrivesWithMedia; // list of known optical drives which contain a media QSet m_supportedInterfaces; QDBusInterface m_manager; QStringList m_deviceCache; QStringList m_dirtyDevices; // special 2-stage storage like Nokia N900 }; } } } #endif // UDISKSMANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksopticaldisc.cpp000066400000000000000000000227071316350454000262550ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010, 2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include "udisksopticaldisc.h" #include "soliddefs_p.h" typedef QMap ContentTypesCache; SOLID_GLOBAL_STATIC(ContentTypesCache, cache) SOLID_GLOBAL_STATIC(QMutex, cacheLock) // inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c static Solid::OpticalDisc::ContentType advancedDiscDetect(const QString & device_file) { /* the discs block size */ unsigned short bs; /* the path table size */ unsigned short ts; /* the path table location (in blocks) */ unsigned int tl; /* length of the directory name in current path table entry */ unsigned char len_di = 0; /* the number of the parent directory's path table entry */ unsigned int parent = 0; /* filename for the current path table entry */ char dirname[256]; /* our position into the path table */ int pos = 0; /* the path table record we're on */ int curr_record = 1; Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent; int fd = open (QFile::encodeName(device_file), O_RDONLY); /* read the block size */ lseek (fd, 0x8080, SEEK_CUR); if (read (fd, &bs, 2) != 2) { qDebug("Advanced probing on %s failed while reading block size", qPrintable(device_file)); goto out; } /* read in size of path table */ lseek (fd, 2, SEEK_CUR); if (read (fd, &ts, 2) != 2) { qDebug("Advanced probing on %s failed while reading path table size", qPrintable(device_file)); goto out; } /* read in which block path table is in */ lseek (fd, 6, SEEK_CUR); if (read (fd, &tl, 4) != 4) { qDebug("Advanced probing on %s failed while reading path table block", qPrintable(device_file)); goto out; } /* seek to the path table */ lseek (fd, bs * tl, SEEK_SET); /* loop through the path table entries */ while (pos < ts) { /* get the length of the filename of the current entry */ if (read (fd, &len_di, 1) != 1) { qDebug("Advanced probing on %s failed, cannot read more entries", qPrintable(device_file)); break; } /* get the record number of this entry's parent i'm pretty sure that the 1st entry is always the top directory */ lseek (fd, 5, SEEK_CUR); if (read (fd, &parent, 2) != 2) { qDebug("Advanced probing on %s failed, couldn't read parent entry", qPrintable(device_file)); break; } /* read the name */ if (read (fd, dirname, len_di) != len_di) { qDebug("Advanced probing on %s failed, couldn't read the entry name", qPrintable(device_file)); break; } dirname[len_di] = 0; /* if we found a folder that has the root as a parent, and the directory name matches one of the special directories then set the properties accordingly */ if (parent == 1) { if (!strcasecmp (dirname, "VIDEO_TS")) { qDebug("Disc in %s is a Video DVD", qPrintable(device_file)); result = Solid::OpticalDisc::VideoDvd; break; } else if (!strcasecmp (dirname, "BDMV")) { qDebug("Disc in %s is a Blu-ray video disc", qPrintable(device_file)); result = Solid::OpticalDisc::VideoBluRay; break; } else if (!strcasecmp (dirname, "VCD")) { qDebug("Disc in %s is a Video CD", qPrintable(device_file)); result = Solid::OpticalDisc::VideoCd; break; } else if (!strcasecmp (dirname, "SVCD")) { qDebug("Disc in %s is a Super Video CD", qPrintable(device_file)); result = Solid::OpticalDisc::SuperVideoCd; break; } } /* all path table entries are padded to be even, so if this is an odd-length table, seek a byte to fix it */ if (len_di%2 == 1) { lseek (fd, 1, SEEK_CUR); pos++; } /* update our position */ pos += 8 + len_di; curr_record++; } close(fd); return result; out: /* go back to the start of the file */ lseek (fd, 0, SEEK_SET); close(fd); return result; } using namespace Solid::Backends::UDisks; OpticalDisc::OpticalDisc(UDisksDevice *device) : UDisksStorageVolume(device), m_needsReprobe(true), m_cachedContent(Solid::OpticalDisc::NoContent) { connect(device, SIGNAL(changed()), this, SLOT(slotChanged())); } OpticalDisc::~OpticalDisc() { } qulonglong OpticalDisc::capacity() const { return m_device->prop("DeviceSize").toULongLong(); } bool OpticalDisc::isRewritable() const { // the hard way, udisks has no notion of a disc "rewritability" const QString mediaType = m_device->prop("DriveMedia").toString(); return mediaType == "optical_cd_rw" || mediaType == "optical_dvd_rw" || mediaType == "optical_dvd_ram" || mediaType == "optical_dvd_plus_rw" || mediaType == "optical_dvd_plus_rw_dl" || mediaType == "optical_bd_re" || mediaType == "optical_hddvd_rw"; // TODO check completeness } bool OpticalDisc::isBlank() const { return m_device->prop("OpticalDiscIsBlank").toBool(); } bool OpticalDisc::isAppendable() const { return m_device->prop("OpticalDiscIsAppendable").toBool(); } Solid::OpticalDisc::DiscType OpticalDisc::discType() const { const QString discType = m_device->prop("DriveMedia").toString(); QMap map; map[Solid::OpticalDisc::CdRom] = "optical_cd"; map[Solid::OpticalDisc::CdRecordable] = "optical_cd_r"; map[Solid::OpticalDisc::CdRewritable] = "optical_cd_rw"; map[Solid::OpticalDisc::DvdRom] = "optical_dvd"; map[Solid::OpticalDisc::DvdRecordable] = "optical_dvd_r"; map[Solid::OpticalDisc::DvdRewritable] ="optical_dvd_rw"; map[Solid::OpticalDisc::DvdRam] ="optical_dvd_ram"; map[Solid::OpticalDisc::DvdPlusRecordable] ="optical_dvd_plus_r"; map[Solid::OpticalDisc::DvdPlusRewritable] ="optical_dvd_plus_rw"; map[Solid::OpticalDisc::DvdPlusRecordableDuallayer] ="optical_dvd_plus_r_dl"; map[Solid::OpticalDisc::DvdPlusRewritableDuallayer] ="optical_dvd_plus_rw_dl"; map[Solid::OpticalDisc::BluRayRom] ="optical_bd"; map[Solid::OpticalDisc::BluRayRecordable] ="optical_bd_r"; map[Solid::OpticalDisc::BluRayRewritable] ="optical_bd_re"; map[Solid::OpticalDisc::HdDvdRom] ="optical_hddvd"; map[Solid::OpticalDisc::HdDvdRecordable] ="optical_hddvd_r"; map[Solid::OpticalDisc::HdDvdRewritable] ="optical_hddvd_rw"; // TODO add these to Solid //map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo"; //map[Solid::OpticalDisc::MountRainer] ="optical_mrw"; //map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w"; return map.key( discType, Solid::OpticalDisc::UnknownDiscType ); } Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const { if (isBlank()) { m_needsReprobe = false; return Solid::OpticalDisc::NoContent; } if (m_needsReprobe) { QMutexLocker lock(cacheLock); QString deviceFile = m_device->prop("DeviceFile").toString(); if (cache->contains(deviceFile)) { m_cachedContent = cache->value(deviceFile); m_needsReprobe = false; return m_cachedContent; } m_cachedContent = Solid::OpticalDisc::NoContent; bool hasData = m_device->prop("OpticalDiscNumTracks").toInt() > 0 && m_device->prop("OpticalDiscNumTracks").toInt() > m_device->prop("OpticalDiscNumAudioTracks").toInt(); bool hasAudio = m_device->prop("OpticalDiscNumAudioTracks").toInt() > 0; if ( hasData ) { m_cachedContent |= Solid::OpticalDisc::Data; m_cachedContent |= advancedDiscDetect(deviceFile); } if ( hasAudio ) m_cachedContent |= Solid::OpticalDisc::Audio; m_needsReprobe = false; cache->insert(deviceFile, m_cachedContent); } return m_cachedContent; } void OpticalDisc::slotChanged() { QMutexLocker lock(cacheLock); m_needsReprobe = true; m_cachedContent = Solid::OpticalDisc::NoContent; cache->remove(m_device->prop("DeviceFile").toString()); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksopticaldisc.h000066400000000000000000000035101316350454000257110ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010, 2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef OPTICALDISC_H #define OPTICALDISC_H #include #include "udisksstoragevolume.h" namespace Solid { namespace Backends { namespace UDisks { class OpticalDisc : public UDisksStorageVolume, virtual public Solid::Ifaces::OpticalDisc { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDisc) public: OpticalDisc(UDisksDevice *device); virtual ~OpticalDisc(); virtual qulonglong capacity() const; virtual bool isRewritable() const; virtual bool isBlank() const; virtual bool isAppendable() const; virtual Solid::OpticalDisc::DiscType discType() const; virtual Solid::OpticalDisc::ContentTypes availableContent() const; private slots: void slotChanged(); private: mutable bool m_needsReprobe; mutable Solid::OpticalDisc::ContentTypes m_cachedContent; }; } } } #endif // OPTICALDISC_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksopticaldrive.cpp000066400000000000000000000140221316350454000264330ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include "udisksopticaldrive.h" #include "udisks.h" #include "udisksdevice.h" using namespace Solid::Backends::UDisks; UDisksOpticalDrive::UDisksOpticalDrive(UDisksDevice *device) : UDisksStorageDrive(device), m_ejectInProgress(false), m_readSpeed(0), m_writeSpeed(0), m_speedsInit(false) { m_device->registerAction("eject", this, SLOT(slotEjectRequested()), SLOT(slotEjectDone(int,QString))); connect(m_device, SIGNAL(changed()), this, SLOT(slotChanged())); } UDisksOpticalDrive::~UDisksOpticalDrive() { } bool UDisksOpticalDrive::eject() { if (m_ejectInProgress) return false; m_ejectInProgress = true; m_device->broadcastActionRequested("eject"); QDBusConnection c = QDBusConnection::systemBus(); QString path = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveEject"); msg << (QStringList() << "unmount"); // unmount parameter return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } void UDisksOpticalDrive::slotDBusReply(const QDBusMessage &/*reply*/) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } void UDisksOpticalDrive::slotDBusError(const QDBusError &error) { m_ejectInProgress = false; m_device->broadcastActionDone("eject", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " +error.message()); } void UDisksOpticalDrive::slotEjectRequested() { m_ejectInProgress = true; emit ejectRequested(m_device->udi()); } void UDisksOpticalDrive::slotEjectDone(int error, const QString &errorString) { m_ejectInProgress = false; emit ejectDone(static_cast(error), errorString, m_device->udi()); } void UDisksOpticalDrive::initReadWriteSpeeds() const { #if 0 int read_speed, write_speed; char *write_speeds = 0; QByteArray device_file = QFile::encodeName(m_device->property("DeviceFile").toString()); //qDebug("Doing open (\"%s\", O_RDONLY | O_NONBLOCK)", device_file.constData()); int fd = open(device_file, O_RDONLY | O_NONBLOCK); if (fd < 0) { qWarning("Cannot open %s: %s", device_file.constData(), strerror (errno)); return; } if (get_read_write_speed(fd, &read_speed, &write_speed, &write_speeds) >= 0) { m_readSpeed = read_speed; m_writeSpeed = write_speed; QStringList list = QString::fromLatin1(write_speeds).split(',', QString::SkipEmptyParts); foreach (const QString & speed, list) m_writeSpeeds.append(speed.toInt()); free(write_speeds); m_speedsInit = true; } close(fd); #endif } QList UDisksOpticalDrive::writeSpeeds() const { if (!m_speedsInit) initReadWriteSpeeds(); //qDebug() << "solid write speeds:" << m_writeSpeeds; return m_writeSpeeds; } int UDisksOpticalDrive::writeSpeed() const { if (!m_speedsInit) initReadWriteSpeeds(); return m_writeSpeed; } int UDisksOpticalDrive::readSpeed() const { if (!m_speedsInit) initReadWriteSpeeds(); return m_readSpeed; } Solid::OpticalDrive::MediumTypes UDisksOpticalDrive::supportedMedia() const { const QStringList mediaTypes = m_device->prop("DriveMediaCompatibility").toStringList(); Solid::OpticalDrive::MediumTypes supported; QMap map; map[Solid::OpticalDrive::Cdr] = "optical_cd_r"; map[Solid::OpticalDrive::Cdrw] = "optical_cd_rw"; map[Solid::OpticalDrive::Dvd] = "optical_dvd"; map[Solid::OpticalDrive::Dvdr] = "optical_dvd_r"; map[Solid::OpticalDrive::Dvdrw] ="optical_dvd_rw"; map[Solid::OpticalDrive::Dvdram] ="optical_dvd_ram"; map[Solid::OpticalDrive::Dvdplusr] ="optical_dvd_plus_r"; map[Solid::OpticalDrive::Dvdplusrw] ="optical_dvd_plus_rw"; map[Solid::OpticalDrive::Dvdplusdl] ="optical_dvd_plus_r_dl"; map[Solid::OpticalDrive::Dvdplusdlrw] ="optical_dvd_plus_rw_dl"; map[Solid::OpticalDrive::Bd] ="optical_bd"; map[Solid::OpticalDrive::Bdr] ="optical_bd_r"; map[Solid::OpticalDrive::Bdre] ="optical_bd_re"; map[Solid::OpticalDrive::HdDvd] ="optical_hddvd"; map[Solid::OpticalDrive::HdDvdr] ="optical_hddvd_r"; map[Solid::OpticalDrive::HdDvdrw] ="optical_hddvd_rw"; // TODO add these to Solid //map[Solid::OpticalDrive::Mo] ="optical_mo"; //map[Solid::OpticalDrive::Mr] ="optical_mrw"; //map[Solid::OpticalDrive::Mrw] ="optical_mrw_w"; foreach ( const Solid::OpticalDrive::MediumType & type, map.keys() ) { if ( mediaTypes.contains( map[type] ) ) { supported |= type; } } return supported; } void UDisksOpticalDrive::slotChanged() { m_speedsInit = false; // reset the read/write speeds, changes eg. with an inserted media } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksopticaldrive.h000066400000000000000000000044471316350454000261120ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSOPTICALDRIVE_H #define UDISKSOPTICALDRIVE_H #include #include "udisksstoragedrive.h" namespace Solid { namespace Backends { namespace UDisks { class UDisksOpticalDrive: public UDisksStorageDrive, virtual public Solid::Ifaces::OpticalDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDrive) public: UDisksOpticalDrive(UDisksDevice *device); virtual ~UDisksOpticalDrive(); Q_SIGNALS: void ejectPressed(const QString &udi); void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void ejectRequested(const QString &udi); public: virtual bool eject(); virtual QList writeSpeeds() const; virtual int writeSpeed() const; virtual int readSpeed() const; virtual Solid::OpticalDrive::MediumTypes supportedMedia() const; private Q_SLOTS: void slotDBusReply(const QDBusMessage &reply); void slotDBusError(const QDBusError &error); void slotEjectRequested(); void slotEjectDone(int error, const QString &errorString); void slotChanged(); private: void initReadWriteSpeeds() const; bool m_ejectInProgress; // read/write speeds mutable int m_readSpeed; mutable int m_writeSpeed; mutable QList m_writeSpeeds; mutable bool m_speedsInit; }; } } } #endif // UDISKSOPTICALDRIVE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstorageaccess.cpp000066400000000000000000000315261316350454000266040ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano Copyright 2009, 2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstorageaccess.h" #include "udisks.h" #include #include #include #include using namespace Solid::Backends::UDisks; UDisksStorageAccess::UDisksStorageAccess(UDisksDevice *device) : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false) { connect(device, SIGNAL(changed()), this, SLOT(slotChanged())); updateCache(); // Delay connecting to DBus signals to avoid the related time penalty // in hot paths such as predicate matching QTimer::singleShot(0, this, SLOT(connectDBusSignals())); } UDisksStorageAccess::~UDisksStorageAccess() { } void UDisksStorageAccess::connectDBusSignals() { m_device->registerAction("setup", this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int,QString))); m_device->registerAction("teardown", this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int,QString))); } bool UDisksStorageAccess::isLuksDevice() const { return m_device->prop("DeviceIsLuks").toBool(); } bool UDisksStorageAccess::isAccessible() const { if (isLuksDevice()) { // check if the cleartext slave is mounted UDisksDevice holderDevice(m_device->prop("LuksHolder").value().path()); return holderDevice.prop("DeviceIsMounted").toBool(); } return m_device->prop("DeviceIsMounted").toBool(); } QString UDisksStorageAccess::filePath() const { if (!isAccessible()) return QString(); QStringList mntPoints; if (isLuksDevice()) { // encrypted (and unlocked) device QString path = m_device->prop("LuksHolder").value().path(); if (path.isEmpty() || path == "/") return QString(); UDisksDevice holderDevice(path); mntPoints = holderDevice.prop("DeviceMountPaths").toStringList(); if (!mntPoints.isEmpty()) return mntPoints.first(); // FIXME Solid doesn't support multiple mount points else return QString(); } mntPoints = m_device->prop("DeviceMountPaths").toStringList(); if (!mntPoints.isEmpty()) return mntPoints.first(); // FIXME Solid doesn't support multiple mount points else return QString(); } bool UDisksStorageAccess::isIgnored() const { return m_device->isDeviceBlacklisted(); } bool UDisksStorageAccess::setup() { if ( m_teardownInProgress || m_setupInProgress ) return false; m_setupInProgress = true; m_device->broadcastActionRequested("setup"); if (m_device->prop("IdUsage").toString() == "crypto") return requestPassphrase(); else return mount(); } bool UDisksStorageAccess::teardown() { if ( m_teardownInProgress || m_setupInProgress ) return false; m_teardownInProgress = true; m_device->broadcastActionRequested("teardown"); return unmount(); } void UDisksStorageAccess::slotChanged() { const bool old_isAccessible = m_isAccessible; updateCache(); if (old_isAccessible != m_isAccessible) { emit accessibilityChanged(m_isAccessible, m_device->udi()); } } void UDisksStorageAccess::updateCache() { m_isAccessible = isAccessible(); } void UDisksStorageAccess::slotDBusReply( const QDBusMessage & reply ) { Q_UNUSED(reply); if (m_setupInProgress) { if (isLuksDevice() && !isAccessible()) // unlocked device, now mount it mount(); else // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156) { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } } else if (m_teardownInProgress) { QString clearTextPath = m_device->prop("LuksHolder").value().path(); if (isLuksDevice() && clearTextPath != "/") // unlocked device, lock it { callCryptoTeardown(); } else if (m_device->prop("DeviceIsLuksCleartext").toBool()) { callCryptoTeardown(true); // Lock crypted parent } else { if (m_device->prop("DriveIsMediaEjectable").toBool() && m_device->prop("DeviceIsMediaAvailable").toBool() && !m_device->prop("DeviceIsOpticalDisc").toBool()) // optical drives have their Eject method { QString devnode = m_device->prop("DeviceFile").toString(); #if defined(Q_OS_OPENBSD) QString program = "cdio"; QStringList args; args << "-f" << devnode << "eject"; #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) devnode.remove("/dev/").replace("([0-9]).", "\\1"); QString program = "cdcontrol"; QStringList args; args << "-f" << devnode << "eject"; #else QString program = "eject"; QStringList args; args << devnode; #endif QProcess::startDetached( program, args ); } // try to eject the (parent) drive, e.g. SD card from a reader QString drivePath = m_device->prop("PartitionSlave").value().path(); if (!drivePath.isEmpty() || drivePath != "/") { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveEject"); msg << QStringList(); // options, unused now c.call(msg, QDBus::NoBlock); // power down removable USB hard drives, rhbz#852196 UDisksDevice drive(drivePath); if (drive.prop("DriveCanDetach").toBool()) { QDBusMessage msg2 = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveDetach"); msg2 << QStringList(); // options, unused now c.call(msg2, QDBus::NoBlock); } } m_teardownInProgress = false; m_device->broadcastActionDone("teardown"); } } } void UDisksStorageAccess::slotDBusError( const QDBusError & error ) { if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " +error.message()); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " + error.message()); } } void UDisksStorageAccess::slotSetupRequested() { m_setupInProgress = true; emit setupRequested(m_device->udi()); } void UDisksStorageAccess::slotSetupDone(int error, const QString &errorString) { m_setupInProgress = false; emit setupDone(static_cast(error), errorString, m_device->udi()); slotChanged(); } void UDisksStorageAccess::slotTeardownRequested() { m_teardownInProgress = true; emit teardownRequested(m_device->udi()); } void UDisksStorageAccess::slotTeardownDone(int error, const QString &errorString) { m_teardownInProgress = false; emit teardownDone(static_cast(error), errorString, m_device->udi()); slotChanged(); } bool UDisksStorageAccess::mount() { QString path = m_device->udi(); if (path.endsWith(":media")) { path.chop(6); } QString fstype; QStringList options; if (isLuksDevice()) { // mount options for the cleartext volume path = m_device->prop("LuksHolder").value().path(); UDisksDevice holderDevice(path); fstype = holderDevice.prop("IdType").toString(); } QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemMount"); if (m_device->prop("IdUsage").toString() == "filesystem") fstype = m_device->prop("IdType").toString(); if (fstype == "vfat") { options << "flush"; } msg << fstype; msg << options; return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool UDisksStorageAccess::unmount() { QString path = m_device->udi(); if (path.endsWith(":media")) { path.chop(6); } if (isLuksDevice()) { // unmount options for the cleartext volume path = m_device->prop("LuksHolder").value().path(); } QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemUnmount"); msg << QStringList(); // options, unused now return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)), s_unmountTimeout); } QString UDisksStorageAccess::generateReturnObjectPath() { static int number = 1; return "/org/kde/solid/UDisksStorageAccess_"+QString::number(number++); } bool UDisksStorageAccess::requestPassphrase() { QString udi = m_device->udi(); QString returnService = QDBusConnection::sessionBus().baseService(); m_lastReturnObject = generateReturnObjectPath(); QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots); QWidget *activeWindow = QApplication::activeWindow(); uint wId = 0; if (activeWindow!=0) wId = (uint)activeWindow->winId(); QString appId = QCoreApplication::applicationName(); QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer"); QDBusReply reply = soliduiserver.call("showPassphraseDialog", udi, returnService, m_lastReturnObject, wId, appId); m_passphraseRequested = reply.isValid(); if (!m_passphraseRequested) qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error(); return m_passphraseRequested; } void UDisksStorageAccess::passphraseReply( const QString & passphrase ) { if (m_passphraseRequested) { QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject); m_passphraseRequested = false; if (!passphrase.isEmpty()) callCryptoSetup(passphrase); else { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } } } void UDisksStorageAccess::callCryptoSetup( const QString & passphrase ) { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, m_device->udi(), UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksUnlock"); msg << passphrase; msg << QStringList(); // options, unused now c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool UDisksStorageAccess::callCryptoTeardown(bool actOnParent) { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, actOnParent?(m_device->prop("LuksCleartextSlave").value().path()):m_device->udi(), UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksLock"); msg << QStringList(); // options, unused now return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } //#include "backends/udisks/udisksstorageaccess.moc" cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstorageaccess.h000066400000000000000000000057631316350454000262550ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano Copyright 2009 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSSTORAGEACCESS_H #define UDISKSSTORAGEACCESS_H #include #include "udisksdeviceinterface.h" #include #include namespace Solid { namespace Backends { namespace UDisks { class UDisksStorageAccess : public DeviceInterface, virtual public Solid::Ifaces::StorageAccess { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageAccess) public: UDisksStorageAccess(UDisksDevice *device); virtual ~UDisksStorageAccess(); virtual bool isAccessible() const; virtual QString filePath() const; virtual bool isIgnored() const; virtual bool setup(); virtual bool teardown(); Q_SIGNALS: void accessibilityChanged(bool accessible, const QString &udi); void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void setupRequested(const QString &udi); void teardownRequested(const QString &udi); public Q_SLOTS: Q_SCRIPTABLE Q_NOREPLY void passphraseReply( const QString & passphrase ); private Q_SLOTS: void slotChanged(); void slotDBusReply( const QDBusMessage & reply ); void slotDBusError( const QDBusError & error ); void connectDBusSignals(); void slotSetupRequested(); void slotSetupDone(int error, const QString &errorString); void slotTeardownRequested(); void slotTeardownDone(int error, const QString &errorString); private: /// @return true if this device is luks and unlocked bool isLuksDevice() const; void updateCache(); bool mount(); bool unmount(); bool requestPassphrase(); void callCryptoSetup( const QString & passphrase ); bool callCryptoTeardown( bool actOnParent=false ); QString generateReturnObjectPath(); private: bool m_isAccessible; bool m_setupInProgress; bool m_teardownInProgress; bool m_passphraseRequested; QString m_lastReturnObject; static const int s_unmountTimeout = 0x7fffffff; }; } } } #endif // UDISKSSTORAGEACCESS_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstoragedrive.cpp000066400000000000000000000102331316350454000264440ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstoragedrive.h" #include using namespace Solid::Backends::UDisks; UDisksStorageDrive::UDisksStorageDrive(UDisksDevice* device) : Block(device) { } UDisksStorageDrive::~UDisksStorageDrive() { } qulonglong UDisksStorageDrive::size() const { return m_device->prop("DeviceSize").toULongLong(); } bool UDisksStorageDrive::isHotpluggable() const { return m_device->prop("DriveCanDetach").toBool(); } bool UDisksStorageDrive::isRemovable() const { return m_device->prop("DeviceIsRemovable").toBool() || !m_device->prop( "DeviceIsSystemInternal" ).toBool(); } Solid::StorageDrive::DriveType UDisksStorageDrive::driveType() const { const QStringList mediaTypes = m_device->prop("DriveMediaCompatibility").toStringList(); bool isHardDisk = m_device->prop( "DeviceIsSystemInternal" ).toBool(); if ( isHardDisk ) { return Solid::StorageDrive::HardDisk; } else if ( !mediaTypes.filter( "optical" ).isEmpty() ) // optical disks { return Solid::StorageDrive::CdromDrive; } else if ( mediaTypes.contains( "floppy" ) ) { return Solid::StorageDrive::Floppy; } #if 0 // TODO add to Solid else if ( mediaTypes.contains( "floppy_jaz" ) ) { return Solid::StorageDrive::Jaz; } else if ( mediaTypes.contains( "floppy_zip" ) ) { return Solid::StorageDrive::Zip; } #endif /* else if (type=="tape") // FIXME: DK doesn't know about tapes { return Solid::StorageDrive::Tape; } */ #if 0 // TODO add to Solid else if ( mediaTypes.contains( "flash" ) ) { return Solid::StorageDrive::Flash; } #endif else if ( mediaTypes.contains( "flash_cf" ) ) { return Solid::StorageDrive::CompactFlash; } else if ( mediaTypes.contains( "flash_ms" ) ) { return Solid::StorageDrive::MemoryStick; } else if ( mediaTypes.contains( "flash_sm" ) ) { return Solid::StorageDrive::SmartMedia; } else if ( mediaTypes.contains( "flash_sd" ) || mediaTypes.contains( "flash_sdhc" ) || mediaTypes.contains( "flash_mmc" ) ) { return Solid::StorageDrive::SdMmc; } // FIXME: DK doesn't know about xD cards either else { return Solid::StorageDrive::HardDisk; } } Solid::StorageDrive::Bus UDisksStorageDrive::bus() const { const QString bus = m_device->prop( "DriveConnectionInterface" ).toString(); if ( bus == "ata" || bus == "ata_parallel" ) // parallel (classical) ATA { return Solid::StorageDrive::Ide; } else if ( bus == "usb" ) { return Solid::StorageDrive::Usb; } else if ( bus == "firewire" ) { return Solid::StorageDrive::Ieee1394; } else if ( bus == "scsi" ) { return Solid::StorageDrive::Scsi; } else if ( bus == "ata_serial" || bus == "ata_serial_esata" ) // serial ATA { return Solid::StorageDrive::Sata; } #if 0 // TODO add these to Solid else if ( bus == "sdio" ) { return Solid::StorageDrive::SDIO; } else if ( bus == "virtual" ) { return Solid::StorageDrive::Virtual; } #endif else return Solid::StorageDrive::Platform; } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstoragedrive.h000066400000000000000000000032051316350454000261120ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSSTORAGEDRIVE_H #define UDISKSSTORAGEDRIVE_H #include #include "udisksblock.h" namespace Solid { namespace Backends { namespace UDisks { class UDisksStorageDrive: public Block, virtual public Solid::Ifaces::StorageDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageDrive) public: UDisksStorageDrive(UDisksDevice *device); virtual ~UDisksStorageDrive(); virtual qulonglong size() const; virtual bool isHotpluggable() const; virtual bool isRemovable() const; virtual Solid::StorageDrive::DriveType driveType() const; virtual Solid::StorageDrive::Bus bus() const; }; } } } #endif // UDISKSSTORAGEDRIVE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstoragevolume.cpp000066400000000000000000000052111316350454000266420ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstoragevolume.h" using namespace Solid::Backends::UDisks; UDisksStorageVolume::UDisksStorageVolume(UDisksDevice *device) : Block(device) { } UDisksStorageVolume::~UDisksStorageVolume() { } QString UDisksStorageVolume::encryptedContainerUdi() const { if ( m_device->prop( "DeviceIsLuksCleartext" ).toBool() ) return m_device->prop( "LuksCleartextSlave" ).value().path(); return QString(); } qulonglong UDisksStorageVolume::size() const { return m_device->prop("PartitionSize").toULongLong(); } QString UDisksStorageVolume::uuid() const { return m_device->prop("IdUuid").toString(); } QString UDisksStorageVolume::label() const { QString label = m_device->prop("IdLabel").toString(); if (label.isEmpty()) label = m_device->prop("PartitionLabel").toString(); return label; } QString UDisksStorageVolume::fsType() const { return m_device->prop("IdType").toString(); } Solid::StorageVolume::UsageType UDisksStorageVolume::usage() const { QString usage = m_device->prop("IdUsage").toString(); if (usage == "filesystem") { return Solid::StorageVolume::FileSystem; } else if (usage == "partitiontable") { return Solid::StorageVolume::PartitionTable; } else if (usage == "raid") { return Solid::StorageVolume::Raid; } else if (usage == "crypto") { return Solid::StorageVolume::Encrypted; } else if (usage == "unused") { return Solid::StorageVolume::Unused; } else { return Solid::StorageVolume::Other; } } bool UDisksStorageVolume::isIgnored() const { return m_device->isDeviceBlacklisted(); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks/udisksstoragevolume.h000066400000000000000000000032301316350454000263060ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSSTORAGEVOLUME_H #define UDISKSSTORAGEVOLUME_H #include #include "udisksblock.h" namespace Solid { namespace Backends { namespace UDisks { class UDisksStorageVolume: public Block, virtual public Solid::Ifaces::StorageVolume { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageVolume) public: UDisksStorageVolume(UDisksDevice *device); virtual ~UDisksStorageVolume(); virtual QString encryptedContainerUdi() const; virtual qulonglong size() const; virtual QString uuid() const; virtual QString label() const; virtual QString fsType() const; virtual Solid::StorageVolume::UsageType usage() const; virtual bool isIgnored() const; }; } } } #endif // UDISKSSTORAGEVOLUME_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/000077500000000000000000000000001316350454000221025ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/dbus/000077500000000000000000000000001316350454000230375ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/dbus/manager.cpp000066400000000000000000000014551316350454000251620ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -p manager manager.xml * * qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #include "manager.h" /* * Implementation of interface class OrgFreedesktopDBusObjectManagerInterface */ OrgFreedesktopDBusObjectManagerInterface::OrgFreedesktopDBusObjectManagerInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } OrgFreedesktopDBusObjectManagerInterface::~OrgFreedesktopDBusObjectManagerInterface() { } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/dbus/manager.h000066400000000000000000000031561316350454000246270ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -p manager manager.xml * * qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #ifndef MANAGER_H_1329493525 #define MANAGER_H_1329493525 #include #include #include #include #include #include #include #include #include "../udisks2.h" /* * Proxy class for interface org.freedesktop.DBus.ObjectManager */ class OrgFreedesktopDBusObjectManagerInterface: public QDBusAbstractInterface { Q_OBJECT public: static inline const char *staticInterfaceName() { return "org.freedesktop.DBus.ObjectManager"; } public: OrgFreedesktopDBusObjectManagerInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); ~OrgFreedesktopDBusObjectManagerInterface(); public Q_SLOTS: // METHODS inline QDBusPendingReply GetManagedObjects() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("GetManagedObjects"), argumentList); } Q_SIGNALS: // SIGNALS void InterfacesAdded(const QDBusObjectPath &object_path, const QVariantMapMap &interfaces_and_properties); void InterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces); }; namespace org { namespace freedesktop { namespace DBus { typedef ::OrgFreedesktopDBusObjectManagerInterface ObjectManager; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/dbus/manager.xml000066400000000000000000000015731316350454000252010ustar00rootroot00000000000000 cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisks2.h000066400000000000000000000100411316350454000236330ustar00rootroot00000000000000/* Copyright 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDISKS2_H #define SOLID_BACKENDS_UDISKS2_H #include #include #include #include #include typedef QList QByteArrayList; Q_DECLARE_METATYPE(QByteArrayList) typedef QMap QVariantMapMap; Q_DECLARE_METATYPE(QVariantMapMap) typedef QMap DBUSManagerStruct; Q_DECLARE_METATYPE(DBUSManagerStruct) /* UDisks2 */ #define UD2_DBUS_SERVICE "org.freedesktop.UDisks2" #define UD2_DBUS_PATH "/org/freedesktop/UDisks2" #define UD2_UDI_DISKS_PREFIX "/org/freedesktop/UDisks2" #define UD2_DBUS_PATH_MANAGER "/org/freedesktop/UDisks2/Manager" #define UD2_DBUS_PATH_DRIVES "/org/freedesktop/UDisks2/drives/" #define UD2_DBUS_PATH_JOBS "/org/freedesktop/UDisks2/jobs/" #define DBUS_INTERFACE_PROPS "org.freedesktop.DBus.Properties" #define DBUS_INTERFACE_INTROSPECT "org.freedesktop.DBus.Introspectable" #define DBUS_INTERFACE_MANAGER "org.freedesktop.DBus.ObjectManager" #define UD2_DBUS_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block" #define UD2_DBUS_INTERFACE_DRIVE "org.freedesktop.UDisks2.Drive" #define UD2_DBUS_INTERFACE_PARTITION "org.freedesktop.UDisks2.Partition" #define UD2_DBUS_INTERFACE_PARTITIONTABLE "org.freedesktop.UDisks2.PartitionTable" #define UD2_DBUS_INTERFACE_FILESYSTEM "org.freedesktop.UDisks2.Filesystem" #define UD2_DBUS_INTERFACE_ENCRYPTED "org.freedesktop.UDisks2.Encrypted" #define UD2_DBUS_INTERFACE_SWAP "org.freedesktop.UDisks2.Swapspace" #define UD2_DBUS_INTERFACE_LOOP "org.freedesktop.UDisks2.Loop" /* errors */ #define UD2_ERROR_UNAUTHORIZED "org.freedesktop.PolicyKit.Error.NotAuthorized" #define UD2_ERROR_BUSY "org.freedesktop.UDisks2.Error.DeviceBusy" #define UD2_ERROR_FAILED "org.freedesktop.UDisks2.Error.Failed" #define UD2_ERROR_CANCELED "org.freedesktop.UDisks2.Error.Cancelled" #define UD2_ERROR_INVALID_OPTION "org.freedesktop.UDisks2.Error.OptionNotPermitted" #define UD2_ERROR_MISSING_DRIVER "org.freedesktop.UDisks2.Error.NotSupported" #define UD2_ERROR_ALREADY_MOUNTED "org.freedesktop.UDisks2.Error.AlreadyMounted" #define UD2_ERROR_NOT_MOUNTED "org.freedesktop.UDisks2.Error.NotMounted" #define UD2_ERROR_MOUNTED_BY_OTHER_USER "org.freedesktop.UDisks2.Error.MountedByOtherUser" #define UD2_ERROR_ALREADY_UNMOUNTING "org.freedesktop.UDisks2.Error.AlreadyUnmounting" #define UD2_ERROR_TIMED_OUT "org.freedesktop.UDisks2.Error.Timedout" #define UD2_ERROR_WOULD_WAKEUP "org.freedesktop.UDisks2.Error.WouldWakeup" #define UD2_ERROR_ALREADY_CANCELLED "org.freedesktop.UDisks2.Error.AlreadyCancelled" #define UD2_ERROR_NOT_AUTHORIZED "org.freedesktop.UDisks2.Error.NotAuthorized" #define UD2_ERROR_NOT_AUTHORIZED_CAN_OBTAIN "org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain" #define UD2_ERROR_NOT_AUTHORIZED_DISMISSED "org.freedesktop.UDisks2.Error.NotAuthorizedDismissed" #endif // SOLID_BACKENDS_UDISKS2_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksblock.cpp000066400000000000000000000060471316350454000251320ustar00rootroot00000000000000/* Copyright 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include "udisksblock.h" using namespace Solid::Backends::UDisks2; Block::Block(Device *dev) : DeviceInterface(dev) { m_devNum = m_device->prop("DeviceNumber").toULongLong(); m_devFile = QFile::decodeName(m_device->prop("Device").toByteArray()); // we have a drive (non-block device for udisks), so let's find the corresponding (real) block device if (m_devNum == 0 || m_devFile.isEmpty()) { const QString path = "/org/freedesktop/UDisks2/block_devices"; QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, DBUS_INTERFACE_INTROSPECT, "Introspect"); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(call); reply.waitForFinished(); if (reply.isValid()) { QDomDocument dom; dom.setContent(reply.value()); QDomNodeList nodeList = dom.documentElement().elementsByTagName("node"); for (int i = 0; i < nodeList.count(); i++) { QDomElement nodeElem = nodeList.item(i).toElement(); if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) { const QString udi = path + "/" + nodeElem.attribute("name"); Device device(udi); if (device.drivePath() == dev->udi()) { m_devNum = device.prop("DeviceNumber").toULongLong(); m_devFile = QFile::decodeName(device.prop("Device").toByteArray()); break; } } } } else qWarning() << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message(); } //qDebug() << "devnum:" << m_devNum << "dev file:" << m_devFile; } Block::~Block() { } QString Block::device() const { return m_devFile; } int Block::deviceMinor() const { return MINOR(m_devNum); } int Block::deviceMajor() const { return MAJOR(m_devNum); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksblock.h000066400000000000000000000027311316350454000245730ustar00rootroot00000000000000/* Copyright 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2BLOCK_H #define UDISKS2BLOCK_H #include #include "udisksdeviceinterface.h" namespace Solid { namespace Backends { namespace UDisks2 { class Block: public DeviceInterface, virtual public Solid::Ifaces::Block { Q_OBJECT Q_INTERFACES(Solid::Ifaces::Block) public: Block(Device *dev); virtual ~Block(); virtual QString device() const; virtual int deviceMinor() const; virtual int deviceMajor() const; private: dev_t m_devNum; QString m_devFile; }; } } } #endif // UDISKS2BLOCK_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdevice.cpp000066400000000000000000000705001316350454000252720ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksdevice.h" #include "udisksdevicebackend.h" #include "udisksblock.h" #include "udisksdeviceinterface.h" #include "udisksstoragevolume.h" #include "udisksopticaldisc.h" #include "udisksopticaldrive.h" #include "udisksstorageaccess.h" #include "udisksgenericinterface.h" #include #include #include #include #include #include #include #include #include using namespace Solid::Backends::UDisks2; // Adapted from KLocale as Solid needs to be Qt-only static QString formatByteSize(double size) { // Per IEC 60027-2 // Binary prefixes //Tebi-byte TiB 2^40 1,099,511,627,776 bytes //Gibi-byte GiB 2^30 1,073,741,824 bytes //Mebi-byte MiB 2^20 1,048,576 bytes //Kibi-byte KiB 2^10 1,024 bytes QString s; // Gibi-byte if ( size >= 1073741824.0 ) { size /= 1073741824.0; if ( size > 1024 ) // Tebi-byte s = QCoreApplication::translate("", "%1 TiB").arg(QLocale().toString(size / 1024.0, 'f', 1)); else s = QCoreApplication::translate("", "%1 GiB").arg(QLocale().toString(size, 'f', 1)); } // Mebi-byte else if ( size >= 1048576.0 ) { size /= 1048576.0; s = QCoreApplication::translate("", "%1 MiB").arg(QLocale().toString(size, 'f', 1)); } // Kibi-byte else if ( size >= 1024.0 ) { size /= 1024.0; s = QCoreApplication::translate("", "%1 KiB").arg(QLocale().toString(size, 'f', 1)); } // Just byte else if ( size > 0 ) { s = QCoreApplication::translate("", "%1 B").arg(QLocale().toString(size, 'f', 1)); } // Nothing else { s = QCoreApplication::translate("", "0 B"); } return s; } Device::Device(const QString &udi) : Solid::Ifaces::Device() , m_backend(DeviceBackend::backendForUDI(udi)) { if (m_backend) { connect(m_backend, SIGNAL(changed()), this, SIGNAL(changed())); connect(m_backend, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); } else { qDebug() << "Created invalid Device for udi" << udi; } } Device::~Device() { } QString Device::udi() const { if (m_backend) { return m_backend->udi(); } return QString(); } QVariant Device::prop(const QString &key) const { if (m_backend) { return m_backend->prop(key); } return QVariant(); } bool Device::propertyExists(const QString &key) const { if (m_backend) { return m_backend->propertyExists(key); } return false; } QVariantMap Device::allProperties() const { if (m_backend) { return m_backend->allProperties(); } return QVariantMap(); } bool Device::hasInterface(const QString &name) const { if (m_backend) { return m_backend->interfaces().contains(name); } return false; } QStringList Device::interfaces() const { if (m_backend) { return m_backend->interfaces(); } return QStringList(); } QObject* Device::createDeviceInterface(const Solid::DeviceInterface::Type& type) { if (!queryDeviceInterface(type)) { return 0; } DeviceInterface *iface = 0; switch (type) { case Solid::DeviceInterface::GenericInterface: iface = new GenericInterface(this); break; case Solid::DeviceInterface::Block: iface = new Block(this); break; case Solid::DeviceInterface::StorageAccess: iface = new StorageAccess(this); break; case Solid::DeviceInterface::StorageDrive: iface = new StorageDrive(this); break; case Solid::DeviceInterface::OpticalDrive: iface = new OpticalDrive(this); break; case Solid::DeviceInterface::StorageVolume: iface = new StorageVolume(this); break; case Solid::DeviceInterface::OpticalDisc: iface = new OpticalDisc(this); break; default: break; } return iface; } bool Device::queryDeviceInterface(const Solid::DeviceInterface::Type& type) const { switch (type) { case Solid::DeviceInterface::GenericInterface: return true; case Solid::DeviceInterface::Block: return isBlock() || isDrive(); case Solid::DeviceInterface::StorageVolume: return isStorageVolume(); case Solid::DeviceInterface::StorageAccess: return isStorageAccess(); case Solid::DeviceInterface::StorageDrive: return isDrive(); case Solid::DeviceInterface::OpticalDrive: return isOpticalDrive(); case Solid::DeviceInterface::OpticalDisc: return isOpticalDisc(); default: return false; } } QStringList Device::emblems() const { QStringList res; if (queryDeviceInterface(Solid::DeviceInterface::StorageAccess)) { const UDisks2::StorageAccess accessIface(const_cast(this)); if (accessIface.isAccessible()) { if (isEncryptedContainer()) res << "emblem-encrypted-unlocked"; else res << "emblem-mounted"; } else { if (isEncryptedContainer()) res << "emblem-encrypted-locked"; else res << "emblem-unmounted"; } } return res; } QString Device::description() const { const QString hintName = property("HintName").toString(); // non-cached if (!hintName.isEmpty()) return hintName; if (isLoop()) return QObject::tr("Loop Device"); else if (isSwap()) return QObject::tr("Swap Space"); else if (queryDeviceInterface(Solid::DeviceInterface::StorageDrive)) return storageDescription(); else if (queryDeviceInterface(Solid::DeviceInterface::StorageVolume)) return volumeDescription(); else return product(); } QString Device::storageDescription() const { QString description; const UDisks2::StorageDrive storageDrive(const_cast(this)); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); const bool drive_is_hotpluggable = storageDrive.isHotpluggable(); if (drive_type == Solid::StorageDrive::CdromDrive) { const UDisks2::OpticalDrive opticalDrive(const_cast(this)); Solid::OpticalDrive::MediumTypes mediumTypes = opticalDrive.supportedMedia(); QString first; QString second; first = QCoreApplication::translate("", "CD-ROM", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdr) first = QCoreApplication::translate("", "CD-R", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Cdrw) first = QCoreApplication::translate("", "CD-RW", "First item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvd) second = QCoreApplication::translate("", "/DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusr) second = QCoreApplication::translate("", "/DVD+R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdplusrw) second = QCoreApplication::translate("", "/DVD+RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdr) second = QCoreApplication::translate("", "/DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdrw) second = QCoreApplication::translate("", "/DVD-RW", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Dvdram) second = QCoreApplication::translate("", "/DVD-RAM", "Second item of %1%2 Drive sentence"); if ((mediumTypes & Solid::OpticalDrive::Dvdr) && (mediumTypes & Solid::OpticalDrive::Dvdplusr)) { if(mediumTypes & Solid::OpticalDrive::Dvdplusdl) second = QObject::trUtf8("/DVD±R DL", "Second item of %1%2 Drive sentence"); else second = QObject::trUtf8("/DVD±R", "Second item of %1%2 Drive sentence"); } if ((mediumTypes & Solid::OpticalDrive::Dvdrw) && (mediumTypes & Solid::OpticalDrive::Dvdplusrw)) { if((mediumTypes & Solid::OpticalDrive::Dvdplusdl) || (mediumTypes & Solid::OpticalDrive::Dvdplusdlrw)) second = QObject::trUtf8("/DVD±RW DL", "Second item of %1%2 Drive sentence"); else second = QObject::trUtf8("/DVD±RW", "Second item of %1%2 Drive sentence"); } if (mediumTypes & Solid::OpticalDrive::Bd) second = QCoreApplication::translate("", "/BD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdr) second = QCoreApplication::translate("", "/BD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::Bdre) second = QCoreApplication::translate("", "/BD-RE", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvd) second = QCoreApplication::translate("", "/HD DVD-ROM", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdr) second = QCoreApplication::translate("", "/HD DVD-R", "Second item of %1%2 Drive sentence"); if (mediumTypes & Solid::OpticalDrive::HdDvdrw) second = QCoreApplication::translate("", "/HD DVD-RW", "Second item of %1%2 Drive sentence"); if (drive_is_hotpluggable) description = QCoreApplication::translate("", "External %1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); else description = QCoreApplication::translate("", "%1%2 Drive", "%1 is CD-ROM/CD-R/etc; %2 is '/DVD-ROM'/'/DVD-R'/etc (with leading slash)").arg(first).arg(second); return description; } if (drive_type == Solid::StorageDrive::Floppy) { if (drive_is_hotpluggable) description = QCoreApplication::translate("", "External Floppy Drive"); else description = QCoreApplication::translate("", "Floppy Drive"); return description; } const bool drive_is_removable = storageDrive.isRemovable(); if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { QString size_str = formatByteSize(storageDrive.size()); if (!size_str.isEmpty()) { if (drive_is_hotpluggable) description = QCoreApplication::translate("", "%1 External Hard Drive", "%1 is the size").arg(size_str); else description = QCoreApplication::translate("", "%1 Hard Drive", "%1 is the size").arg(size_str); } else { if (drive_is_hotpluggable) description = QCoreApplication::translate("", "External Hard Drive"); else description = QCoreApplication::translate("", "Hard Drive"); } return description; } QString vendormodel_str; QString model = product(); QString vendor_str = vendor(); if (vendor_str.isEmpty()) { if (!model.isEmpty()) vendormodel_str = model; } else { if (model.isEmpty()) vendormodel_str = vendor_str; else { if (model.startsWith(vendor_str)) { // e.g. vendor is "Nokia" and model is "Nokia N950" we do not want "Nokia Nokia N950" as description vendormodel_str = model; } else { vendormodel_str = QCoreApplication::translate("", "%1 %2", "%1 is the vendor, %2 is the model of the device").arg(vendor_str).arg(model); } } } if (vendormodel_str.isEmpty()) description = QCoreApplication::translate("", "Drive"); else description = vendormodel_str; return description; } QString Device::volumeDescription() const { QString description; const UDisks2::StorageVolume storageVolume(const_cast(this)); QString volume_label = prop("IdLabel").toString(); if (volume_label.isEmpty()) volume_label = prop("Name").toString(); if (!volume_label.isEmpty()) return volume_label; UDisks2::Device storageDevice(drivePath()); const UDisks2::StorageDrive storageDrive(&storageDevice); Solid::StorageDrive::DriveType drive_type = storageDrive.driveType(); // Handle media in optical drives if (drive_type == Solid::StorageDrive::CdromDrive) { const UDisks2::OpticalDisc disc(const_cast(this)); switch (disc.discType()) { case Solid::OpticalDisc::UnknownDiscType: case Solid::OpticalDisc::CdRom: description = QCoreApplication::translate("", "CD-ROM"); break; case Solid::OpticalDisc::CdRecordable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank CD-R"); else description = QCoreApplication::translate("", "CD-R"); break; case Solid::OpticalDisc::CdRewritable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank CD-RW"); else description = QCoreApplication::translate("", "CD-RW"); break; case Solid::OpticalDisc::DvdRom: description = QCoreApplication::translate("", "DVD-ROM"); break; case Solid::OpticalDisc::DvdRam: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD-RAM"); else description = QCoreApplication::translate("", "DVD-RAM"); break; case Solid::OpticalDisc::DvdRecordable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD-R"); else description = QCoreApplication::translate("", "DVD-R"); break; case Solid::OpticalDisc::DvdPlusRecordableDuallayer: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD+R Dual-Layer"); else description = QCoreApplication::translate("", "DVD+R Dual-Layer"); break; case Solid::OpticalDisc::DvdRewritable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD-RW"); else description = QCoreApplication::translate("", "DVD-RW"); break; case Solid::OpticalDisc::DvdPlusRecordable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD+R"); else description = QCoreApplication::translate("", "DVD+R"); break; case Solid::OpticalDisc::DvdPlusRewritable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD+RW"); else description = QCoreApplication::translate("", "DVD+RW"); break; case Solid::OpticalDisc::DvdPlusRewritableDuallayer: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank DVD+RW Dual-Layer"); else description = QCoreApplication::translate("", "DVD+RW Dual-Layer"); break; case Solid::OpticalDisc::BluRayRom: description = QCoreApplication::translate("", "BD-ROM"); break; case Solid::OpticalDisc::BluRayRecordable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank BD-R"); else description = QCoreApplication::translate("", "BD-R"); break; case Solid::OpticalDisc::BluRayRewritable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank BD-RE"); else description = QCoreApplication::translate("", "BD-RE"); break; case Solid::OpticalDisc::HdDvdRom: description = QCoreApplication::translate("", "HD DVD-ROM"); break; case Solid::OpticalDisc::HdDvdRecordable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank HD DVD-R"); else description = QCoreApplication::translate("", "HD DVD-R"); break; case Solid::OpticalDisc::HdDvdRewritable: if (disc.isBlank()) description = QCoreApplication::translate("", "Blank HD DVD-RW"); else description = QCoreApplication::translate("", "HD DVD-RW"); break; } // Special case for pure audio disc if (disc.availableContent() == Solid::OpticalDisc::Audio) description = QCoreApplication::translate("", "Audio CD"); return description; } const bool drive_is_removable = storageDrive.isRemovable(); const bool drive_is_hotpluggable = storageDrive.isHotpluggable(); QString size_str = formatByteSize(storageVolume.size()); if (isEncryptedContainer()) { if (!size_str.isEmpty()) description = QCoreApplication::translate("", "%1 Encrypted Drive", "%1 is the size").arg(size_str); else description = QCoreApplication::translate("", "Encrypted Drive"); } else if (drive_type == Solid::StorageDrive::HardDisk && !drive_is_removable) { if (!size_str.isEmpty()) { if (drive_is_hotpluggable) description = QCoreApplication::translate("", "%1 External Hard Drive", "%1 is the size").arg(size_str); else description = QCoreApplication::translate("", "%1 Hard Drive", "%1 is the size").arg(size_str); } else { if (drive_is_hotpluggable) description = QCoreApplication::translate("", "External Hard Drive"); else description = QCoreApplication::translate("", "Hard Drive"); } } else { if (drive_is_removable) description = QCoreApplication::translate("", "%1 Removable Media", "%1 is the size").arg(size_str); else description = QCoreApplication::translate("", "%1 Media", "%1 is the size").arg(size_str); } return description; } QString Device::icon() const { QString iconName = property( "HintIconName" ).toString(); // non-cached if ( !iconName.isEmpty() ) { return iconName; } else if (isLoop() || isSwap()) { return "drive-harddisk"; } else if (isDrive()) { const bool isRemovable = prop("Removable").toBool(); const QString conn = prop("ConnectionBus").toString(); if (isOpticalDrive()) return "drive-optical"; else if (isRemovable && !prop("Optical").toBool()) { if (conn == "usb") return "drive-removable-media-usb"; else return "drive-removable-media"; } } else if (isBlock()) { const QString drv = drivePath(); if (drv.isEmpty() || drv == "/") return "drive-harddisk"; // stuff like loop devices or swap which don't have the Drive prop set Device drive(drv); // handle media const QString media = drive.prop("Media").toString(); if ( !media.isEmpty() ) { if ( drive.prop("Optical").toBool() ) // optical stuff { bool isWritable = drive.prop("OpticalBlank").toBool(); const UDisks2::OpticalDisc disc(const_cast(this)); Solid::OpticalDisc::ContentTypes availContent = disc.availableContent(); if (availContent & Solid::OpticalDisc::VideoDvd) // Video DVD return "media-optical-dvd-video"; else if ((availContent & Solid::OpticalDisc::VideoCd) || (availContent & Solid::OpticalDisc::SuperVideoCd)) // Video CD return "media-optical-video"; else if ((availContent & Solid::OpticalDisc::Data) && (availContent & Solid::OpticalDisc::Audio)) // Mixed CD return "media-optical-mixed-cd"; else if (availContent & Solid::OpticalDisc::Audio) // Audio CD return "media-optical-audio"; else if (availContent & Solid::OpticalDisc::Data) // Data CD return "media-optical-data"; else if ( isWritable ) return "media-optical-recordable"; else { if ( media.startsWith( "optical_dvd" ) || media.startsWith( "optical_hddvd" ) ) // DVD return "media-optical-dvd"; else if ( media.startsWith( "optical_bd" ) ) // BluRay return "media-optical-blu-ray"; } // fallback for every other optical disc return "media-optical"; } if ( media == "flash_ms" ) // Flash & Co. return "media-flash-memory-stick"; else if ( media == "flash_sd" || media == "flash_sdhc" || media == "flash_sdxc" || media == "flash_mmc" ) return "media-flash-sd-mmc"; else if ( media == "flash_sm" ) return "media-flash-smart-media"; else if ( media == "thumb" ) return "drive-removable-media-usb-pendrive"; else if ( media.startsWith( "flash" ) ) return "media-flash"; else if ( media == "floppy" ) // the good ol' floppy return "media-floppy"; } if (drive.prop("ConnectionBus").toString() == "sdio") // hack for SD cards connected thru sdio bus return "media-flash-sd-mmc"; return drive.icon(); } return "drive-harddisk"; // general fallback } QString Device::product() const { if (!isDrive()) { Device drive(drivePath()); return drive.prop("Model").toString(); } return prop("Model").toString(); } QString Device::vendor() const { if (!isDrive()) { Device drive(drivePath()); return drive.prop("Vendor").toString(); } return prop("Vendor").toString(); } QString Device::parentUdi() const { QString parent; if (propertyExists("Drive")) // block parent = drivePath(); else if (propertyExists("Table")) // partition parent = prop("Table").value().path(); else if (parent.isEmpty() || parent=="/") { parent = UD2_UDI_DISKS_PREFIX; } return parent; } QString Device::errorToString(const QString & error) const { if (error == UD2_ERROR_UNAUTHORIZED || error == UD2_ERROR_NOT_AUTHORIZED) return QCoreApplication::translate("", "You are not authorized to perform this operation"); else if (error == UD2_ERROR_BUSY) return QCoreApplication::translate("", "The device is currently busy"); else if (error == UD2_ERROR_FAILED) return QCoreApplication::translate("", "The requested operation has failed"); else if (error == UD2_ERROR_CANCELED) return QCoreApplication::translate("", "The requested operation has been canceled"); else if (error == UD2_ERROR_INVALID_OPTION) return QCoreApplication::translate("", "An invalid or malformed option has been given"); else if (error == UD2_ERROR_MISSING_DRIVER) return QCoreApplication::translate("", "The kernel driver for this filesystem type is not available"); else if (error == UD2_ERROR_ALREADY_MOUNTED) return QCoreApplication::translate("", "The device is already mounted"); else if (error == UD2_ERROR_NOT_MOUNTED) return QCoreApplication::translate("", "The device is not mounted"); else if (error == UD2_ERROR_MOUNTED_BY_OTHER_USER) return QCoreApplication::translate("", "The device is mounted by another user"); else if (error == UD2_ERROR_ALREADY_UNMOUNTING) return QCoreApplication::translate("", "The device is already unmounting"); else if (error == UD2_ERROR_TIMED_OUT) return QCoreApplication::translate("", "The operation timed out"); else if (error == UD2_ERROR_WOULD_WAKEUP) return QCoreApplication::translate("", "The operation would wake up a disk that is in a deep-sleep state"); else if (error == UD2_ERROR_ALREADY_CANCELLED) return QCoreApplication::translate("", "The operation has already been canceled"); else return QCoreApplication::translate("", "An unspecified error has occurred"); } Solid::ErrorType Device::errorToSolidError(const QString & error) const { if (error == UD2_ERROR_BUSY) return Solid::DeviceBusy; else if (error == UD2_ERROR_FAILED) return Solid::OperationFailed; else if (error == UD2_ERROR_CANCELED) return Solid::UserCanceled; else if (error == UD2_ERROR_INVALID_OPTION) return Solid::InvalidOption; else if (error == UD2_ERROR_MISSING_DRIVER) return Solid::MissingDriver; else return Solid::UnauthorizedOperation; } bool Device::isBlock() const { return hasInterface(UD2_DBUS_INTERFACE_BLOCK); } bool Device::isPartition() const { return hasInterface(UD2_DBUS_INTERFACE_PARTITION); } bool Device::isPartitionTable() const { return hasInterface(UD2_DBUS_INTERFACE_PARTITIONTABLE); } bool Device::isStorageVolume() const { return isPartition() || isPartitionTable() || isStorageAccess() || isOpticalDisc(); } bool Device::isStorageAccess() const { return hasInterface(UD2_DBUS_INTERFACE_FILESYSTEM) || isEncryptedContainer(); } bool Device::isDrive() const { return hasInterface(UD2_DBUS_INTERFACE_DRIVE); } bool Device::isOpticalDrive() const { return isDrive() && !prop("MediaCompatibility").toStringList().filter("optical_").isEmpty(); } bool Device::isOpticalDisc() const { const QString drv = drivePath(); if (drv.isEmpty() || drv == "/") return false; Device drive(drv); return drive.prop("Optical").toBool(); } bool Device::mightBeOpticalDisc() const { const QString drv = drivePath(); if (drv.isEmpty() || drv == "/") return false; Device drive(drv); return drive.isOpticalDrive(); } bool Device::isMounted() const { return propertyExists("MountPoints") && !qdbus_cast(prop("MountPoints")).isEmpty(); } bool Device::isEncryptedContainer() const { return hasInterface(UD2_DBUS_INTERFACE_ENCRYPTED); } bool Device::isEncryptedCleartext() const { const QString holderDevice = prop("CryptoBackingDevice").toString(); if (holderDevice.isEmpty() || holderDevice == "/") return false; else return true; } bool Device::isSwap() const { return hasInterface(UD2_DBUS_INTERFACE_SWAP); } bool Device::isLoop() const { return hasInterface(UD2_DBUS_INTERFACE_LOOP); } QString Device::drivePath() const { return prop("Drive").value().path(); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdevice.h000066400000000000000000000056461316350454000247500ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2DEVICE_H #define UDISKS2DEVICE_H #include "udisks2.h" #include #include #include #include #include #include namespace Solid { namespace Backends { namespace UDisks2 { class DeviceBackend; class Device: public Solid::Ifaces::Device { Q_OBJECT public: Device(const QString &udi); virtual ~Device(); virtual QObject* createDeviceInterface(const Solid::DeviceInterface::Type& type); virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type& type) const; virtual QString description() const; virtual QStringList emblems() const; virtual QString icon() const; virtual QString product() const; virtual QString vendor() const; virtual QString udi() const; virtual QString parentUdi() const; QVariant prop(const QString &key) const; bool propertyExists(const QString &key) const; QVariantMap allProperties() const; bool hasInterface(const QString & name) const; QStringList interfaces() const; QString errorToString(const QString & error) const; Solid::ErrorType errorToSolidError(const QString & error) const; bool isBlock() const; bool isPartition() const; bool isPartitionTable() const; bool isStorageVolume() const; bool isStorageAccess() const; bool isDrive() const; bool isOpticalDrive() const; bool isOpticalDisc() const; bool mightBeOpticalDisc() const; bool isMounted() const; bool isEncryptedContainer() const; bool isEncryptedCleartext() const; bool isSwap() const; bool isLoop() const; QString drivePath() const; Q_SIGNALS: void changed(); void propertyChanged(const QMap &changes); protected: QPointer m_backend; private: QString storageDescription() const; QString volumeDescription() const; }; } } } #endif // UDISKS2DEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdevicebackend.cpp000066400000000000000000000175411316350454000266100ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl Copyright 2012 Dan Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksdevicebackend.h" #include #include #include #include "solid-lite/deviceinterface.h" #include "solid-lite/genericinterface.h" using namespace Solid::Backends::UDisks2; /* Static cache for DeviceBackends for all UDIs */ QMap DeviceBackend::s_backends; DeviceBackend* DeviceBackend::backendForUDI(const QString& udi, bool create) { DeviceBackend *backend = 0; if (udi.isEmpty()) { return backend; } if (s_backends.contains(udi)) { backend = s_backends.value(udi); } else if (create) { backend = new DeviceBackend(udi); s_backends.insert(udi, backend); } return backend; } void DeviceBackend::destroyBackend(const QString& udi) { if (s_backends.contains(udi)) { DeviceBackend *backend = s_backends.value(udi); s_backends.remove(udi); delete backend; } } DeviceBackend::DeviceBackend(const QString& udi) : m_udi(udi) { //qDebug() << "Creating backend for device" << m_udi; m_device = new QDBusInterface(UD2_DBUS_SERVICE, m_udi, QString(), // no interface, we aggregate them QDBusConnection::systemBus(), this); if (m_device->isValid()) { QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "PropertiesChanged", this, SLOT(slotPropertiesChanged(QString,QVariantMap,QStringList))); QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, UD2_DBUS_PATH, DBUS_INTERFACE_MANAGER, "InterfacesAdded", this, SLOT(slotInterfacesAdded(QDBusObjectPath,QVariantMapMap))); QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, UD2_DBUS_PATH, DBUS_INTERFACE_MANAGER, "InterfacesRemoved", this, SLOT(slotInterfacesRemoved(QDBusObjectPath,QStringList))); initInterfaces(); } } DeviceBackend::~DeviceBackend() { //qDebug() << "Destroying backend for device" << m_udi; } void DeviceBackend::initInterfaces() { m_interfaces.clear(); const QString xmlData = introspect(); if (xmlData.isEmpty()) { qDebug() << m_udi << "has no interfaces!"; return; } QDomDocument dom; dom.setContent(xmlData); QDomNodeList ifaceNodeList = dom.elementsByTagName("interface"); for (int i = 0; i < ifaceNodeList.count(); i++) { QDomElement ifaceElem = ifaceNodeList.item(i).toElement(); /* Accept only org.freedesktop.UDisks2.* interfaces so that when the device is unplugged, * m_interfaces goes empty and we can easily verify that the device is gone. */ if (!ifaceElem.isNull() && ifaceElem.attribute("name").startsWith(UD2_DBUS_SERVICE)) { m_interfaces.append(ifaceElem.attribute("name")); } } //qDebug() << m_udi << "has interfaces:" << m_interfaces; } QStringList DeviceBackend::interfaces() const { return m_interfaces; } const QString& DeviceBackend::udi() const { return m_udi; } QVariant DeviceBackend::prop(const QString& key) const { checkCache(key); return m_propertyCache.value(key); } bool DeviceBackend::propertyExists(const QString& key) const { checkCache(key); /* checkCache() will put an invalid QVariant in cache when the property * does not exist, so check for validity, not for an actual presence. */ return m_propertyCache.value(key).isValid(); } QVariantMap DeviceBackend::allProperties() const { QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "GetAll"); Q_FOREACH (const QString & iface, m_interfaces) { call.setArguments(QVariantList() << iface); QDBusPendingReply reply = QDBusConnection::systemBus().call(call); if (reply.isValid()) { m_propertyCache.unite(reply.value()); } else { qWarning() << "Error getting props:" << reply.error().name() << reply.error().message(); } //qDebug() << "After iface" << iface << ", cache now contains" << m_cache.size() << "items"; } return m_propertyCache; } void DeviceBackend::invalidateProperties() { m_propertyCache.clear(); } QString DeviceBackend::introspect() const { QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_INTROSPECT, "Introspect"); QDBusPendingReply reply = QDBusConnection::systemBus().call(call); if (reply.isValid()) return reply.value(); else { return QString(); } } void DeviceBackend::checkCache(const QString& key) const { if (m_propertyCache.isEmpty()) { // recreate the cache allProperties(); } if (m_propertyCache.contains(key)) { return; } QVariant reply = m_device->property(key.toUtf8()); m_propertyCache.insert(key, reply); if (!reply.isValid()) { /* Store the item in the cache anyway so next time we don't have to * do the DBus call to find out it does not exist but just check whether * prop(key).isValid() */ //qDebug() << m_udi << ": property" << key << "does not exist"; } } void DeviceBackend::slotPropertiesChanged(const QString& ifaceName, const QVariantMap& changedProps, const QStringList& invalidatedProps) { //qDebug() << m_udi << "'s interface" << ifaceName << "changed props:"; QMap changeMap; Q_FOREACH(const QString & key, invalidatedProps) { m_propertyCache.remove(key); changeMap.insert(key, Solid::GenericInterface::PropertyRemoved); //qDebug() << "\t invalidated:" << key; } QMapIterator i(changedProps); while (i.hasNext()) { i.next(); const QString key = i.key(); m_propertyCache.insert(key, i.value()); // replace the value changeMap.insert(key, Solid::GenericInterface::PropertyModified); //qDebug() << "\t modified:" << key << ":" << m_propertyCache.value(key); } Q_EMIT propertyChanged(changeMap); Q_EMIT changed(); } void DeviceBackend::slotInterfacesAdded(const QDBusObjectPath& object_path, const QVariantMapMap& interfaces_and_properties) { if (object_path.path() != m_udi) { return; } Q_FOREACH(const QString & iface, interfaces_and_properties.keys()) { /* Don't store generic DBus interfaces */ if (iface.startsWith(UD2_DBUS_SERVICE)) { m_interfaces.append(interfaces_and_properties.keys()); } } } void DeviceBackend::slotInterfacesRemoved(const QDBusObjectPath& object_path, const QStringList& interfaces) { if (object_path.path() != m_udi) { return; } Q_FOREACH(const QString & iface, interfaces) { m_interfaces.removeAll(iface); } } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdevicebackend.h000066400000000000000000000051701316350454000262500ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl Copyright 2012 Dan Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKSDEVICEBACKEND_H #define UDISKSDEVICEBACKEND_H #include #include #include #include #include #include "udisks2.h" namespace Solid { namespace Backends { namespace UDisks2 { class DeviceBackend: public QObject { Q_OBJECT public: static DeviceBackend* backendForUDI(const QString &udi, bool create = true); static void destroyBackend(const QString &udi); DeviceBackend(const QString &udi); ~DeviceBackend(); QVariant prop(const QString &key) const; bool propertyExists(const QString &key) const; QVariantMap allProperties() const; QStringList interfaces() const; const QString & udi() const; void invalidateProperties(); Q_SIGNALS: void propertyChanged(const QMap &changeMap); void changed(); private Q_SLOTS: void slotInterfacesAdded(const QDBusObjectPath &object_path, const QVariantMapMap &interfaces_and_properties); void slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces); void slotPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps); private: void initInterfaces(); QString introspect() const; void checkCache(const QString &key) const; QDBusInterface *m_device; mutable QVariantMap m_propertyCache; QStringList m_interfaces; QString m_udi; static QMap s_backends; }; } /* namespace UDisks2 */ } /* namespace Backends */ } /* namespace Solid */ #endif /* UDISKSDEVICEBACKEND_H */ cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdeviceinterface.cpp000066400000000000000000000021761316350454000271570ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksdeviceinterface.h" using namespace Solid::Backends::UDisks2; DeviceInterface::DeviceInterface(Device *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksdeviceinterface.h000066400000000000000000000113131316350454000266150ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2DEVICEINTERFACE_H #define UDISKS2DEVICEINTERFACE_H #include #include "udisksdevice.h" #include #include namespace Solid { namespace Backends { namespace UDisks2 { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(Device *device); virtual ~DeviceInterface(); protected: Device *m_device; public: inline static QStringList toStringList(Solid::DeviceInterface::Type type) { QStringList list; switch(type) { case Solid::DeviceInterface::GenericInterface: list << "generic"; break; /* case Solid::DeviceInterface::Processor: // Doesn't exist with UDisks break; */ case Solid::DeviceInterface::Block: list << "block"; break; case Solid::DeviceInterface::StorageAccess: list << "volume"; break; case Solid::DeviceInterface::StorageDrive: list << "storage"; break; case Solid::DeviceInterface::OpticalDrive: list << "storage.cdrom"; break; case Solid::DeviceInterface::StorageVolume: list << "volume"; break; case Solid::DeviceInterface::OpticalDisc: list << "volume.disc"; break; /* case Solid::DeviceInterface::Camera: // Doesn't exist with UDisks break; */ case Solid::DeviceInterface::PortableMediaPlayer: // Doesn't exist with UDisks break; /* case Solid::DeviceInterface::NetworkInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::AcAdapter: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Battery: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Button: // Doesn't exist with UDisks break; case Solid::DeviceInterface::AudioInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::DvbInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::Video: // Doesn't exist with UDisks break; case Solid::DeviceInterface::SerialInterface: // Doesn't exist with UDisks break; case Solid::DeviceInterface::InternetGateway: break; case Solid::DeviceInterface::SmartCardReader: // Doesn't exist with UDisks case Solid::DeviceInterface::NetworkShare: // Doesn't exist with UDisks break; */ case Solid::DeviceInterface::Unknown: break; case Solid::DeviceInterface::Last: break; } return list; } inline static Solid::DeviceInterface::Type fromString(const QString &capability) { if (capability == "generic") return Solid::DeviceInterface::GenericInterface; else if (capability == "block") return Solid::DeviceInterface::Block; else if (capability == "storage") return Solid::DeviceInterface::StorageDrive; else if (capability == "storage.cdrom") return Solid::DeviceInterface::OpticalDrive; else if (capability == "volume") return Solid::DeviceInterface::StorageVolume; else if (capability == "volume.disc") return Solid::DeviceInterface::OpticalDisc; else return Solid::DeviceInterface::Unknown; } }; } } } #endif // UDISKS2DEVICEINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksgenericinterface.cpp000066400000000000000000000031621316350454000273300ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano Copyright 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksgenericinterface.h" #include "udisksdevice.h" using namespace Solid::Backends::UDisks2; GenericInterface::GenericInterface(Device *device) : DeviceInterface(device) { connect(device, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->prop(key); } QVariantMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksgenericinterface.h000066400000000000000000000034641316350454000270020ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_UDISKS2_GENERICINTERFACE_H #define SOLID_BACKENDS_UDISKS2_GENERICINTERFACE_H #include #include #include "udisksdeviceinterface.h" namespace Solid { namespace Backends { namespace UDisks2 { class Device; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(Device *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QVariantMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif // SOLID_BACKENDS_UDISKS2_GENERICINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksmanager.cpp000066400000000000000000000224451316350454000254520ustar00rootroot00000000000000/* Copyright 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksmanager.h" #include "udisksdevicebackend.h" #include #include #include #include #include "../shared/rootdevice.h" using namespace Solid::Backends::UDisks2; using namespace Solid::Backends::Shared; Manager::Manager(QObject *parent) : Solid::Ifaces::DeviceManager(parent), m_manager(UD2_DBUS_SERVICE, UD2_DBUS_PATH, QDBusConnection::systemBus()) { m_supportedInterfaces << Solid::DeviceInterface::GenericInterface << Solid::DeviceInterface::Block << Solid::DeviceInterface::StorageAccess << Solid::DeviceInterface::StorageDrive << Solid::DeviceInterface::OpticalDrive << Solid::DeviceInterface::OpticalDisc << Solid::DeviceInterface::StorageVolume; qDBusRegisterMetaType >(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); bool serviceFound = m_manager.isValid(); if (!serviceFound) { // find out whether it will be activated automatically QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListActivatableNames"); QDBusReply reply = QDBusConnection::systemBus().call(message); if (reply.isValid() && reply.value().contains(UD2_DBUS_SERVICE)) { QDBusConnection::systemBus().interface()->startService(UD2_DBUS_SERVICE); serviceFound = true; } } if (serviceFound) { connect(&m_manager, SIGNAL(InterfacesAdded(QDBusObjectPath, QVariantMapMap)), this, SLOT(slotInterfacesAdded(QDBusObjectPath,QVariantMapMap))); connect(&m_manager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), this, SLOT(slotInterfacesRemoved(QDBusObjectPath,QStringList))); } } Manager::~Manager() { while (!m_deviceCache.isEmpty()) { QString udi = m_deviceCache.takeFirst(); DeviceBackend::destroyBackend(udi); } } QObject* Manager::createDevice(const QString& udi) { if (udi==udiPrefix()) { RootDevice *root = new RootDevice(udi); root->setProduct(QCoreApplication::translate("", "Storage")); root->setDescription(QCoreApplication::translate("", "Storage devices")); root->setIcon("server-database"); // Obviously wasn't meant for that, but maps nicely in oxygen icon set :-p return root; } else if (deviceCache().contains(udi)) { return new Device(udi); } else { return 0; } } QStringList Manager::devicesFromQuery(const QString& parentUdi, Solid::DeviceInterface::Type type) { QStringList result; if (!parentUdi.isEmpty()) { Q_FOREACH (const QString &udi, deviceCache()) { Device device(udi); if (device.queryDeviceInterface(type) && device.parentUdi() == parentUdi) result << udi; } return result; } else if (type != Solid::DeviceInterface::Unknown) { Q_FOREACH (const QString &udi, deviceCache()) { Device device(udi); if (device.queryDeviceInterface(type)) result << udi; } return result; } return deviceCache(); } QStringList Manager::allDevices() { /* Clear the cache, destroy all backends */ while (!m_deviceCache.isEmpty()) { QString udi= m_deviceCache.takeFirst(); DeviceBackend::destroyBackend(udi); } introspect("/org/freedesktop/UDisks2/block_devices", true /*checkOptical*/); introspect("/org/freedesktop/UDisks2/drives"); return m_deviceCache; } void Manager::introspect(const QString & path, bool checkOptical) { QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, DBUS_INTERFACE_INTROSPECT, "Introspect"); QDBusPendingReply reply = QDBusConnection::systemBus().call(call); if (reply.isValid()) { QDomDocument dom; dom.setContent(reply.value()); QDomNodeList nodeList = dom.documentElement().elementsByTagName("node"); for (int i = 0; i < nodeList.count(); i++) { QDomElement nodeElem = nodeList.item(i).toElement(); if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) { const QString udi = path + "/" + nodeElem.attribute("name"); if (checkOptical) { Device device(udi); if (device.mightBeOpticalDisc()) { QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, udi, DBUS_INTERFACE_PROPS, "PropertiesChanged", this, SLOT(slotMediaChanged(QDBusMessage))); if (!device.isOpticalDisc()) // skip empty CD disc continue; } } m_deviceCache.append(udi); } } } else qWarning() << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message(); } QSet< Solid::DeviceInterface::Type > Manager::supportedInterfaces() const { return m_supportedInterfaces; } QString Manager::udiPrefix() const { return UD2_UDI_DISKS_PREFIX; } void Manager::slotInterfacesAdded(const QDBusObjectPath &object_path, const QVariantMapMap &interfaces_and_properties) { const QString udi = object_path.path(); /* Ignore jobs */ if (udi.startsWith(UD2_DBUS_PATH_JOBS)) { return; } qDebug() << udi << "has new interfaces:" << interfaces_and_properties.keys(); updateBackend(udi); // new device, we don't know it yet if (!m_deviceCache.contains(udi)) { m_deviceCache.append(udi); Q_EMIT deviceAdded(udi); } // re-emit in case of 2-stage devices like N9 or some Android phones else if (m_deviceCache.contains(udi) && interfaces_and_properties.keys().contains(UD2_DBUS_INTERFACE_FILESYSTEM)) { Q_EMIT deviceAdded(udi); } } void Manager::slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces) { const QString udi = object_path.path(); /* Ignore jobs */ if (udi.startsWith(UD2_DBUS_PATH_JOBS)) { return; } qDebug() << udi << "lost interfaces:" << interfaces; updateBackend(udi); Device device(udi); if (!udi.isEmpty() && (interfaces.isEmpty() || device.interfaces().isEmpty())) { Q_EMIT deviceRemoved(udi); m_deviceCache.removeAll(udi); DeviceBackend::destroyBackend(udi); } } void Manager::slotMediaChanged(const QDBusMessage & msg) { const QVariantMap properties = qdbus_cast(msg.arguments().at(1)); if (!properties.contains("Size")) // react only on Size changes return; const QString udi = msg.path(); updateBackend(udi); qulonglong size = properties.value("Size").toULongLong(); qDebug() << "MEDIA CHANGED in" << udi << "; size is:" << size; if (!m_deviceCache.contains(udi) && size > 0) { // we don't know the optdisc, got inserted m_deviceCache.append(udi); Q_EMIT deviceAdded(udi); } if (m_deviceCache.contains(udi) && size == 0) { // we know the optdisc, got removed Q_EMIT deviceRemoved(udi); m_deviceCache.removeAll(udi); DeviceBackend::destroyBackend(udi); } } const QStringList & Manager::deviceCache() { if (m_deviceCache.isEmpty()) allDevices(); return m_deviceCache; } void Manager::updateBackend(const QString & udi) { DeviceBackend *backend = DeviceBackend::backendForUDI(udi); if (!backend) return; //This doesn't emit "changed" signals. Signals are emitted later by DeviceBackend's slots backend->allProperties(); QVariant driveProp = backend->prop("Drive"); if (!driveProp.isValid()) return; QDBusObjectPath drivePath = qdbus_cast(driveProp); DeviceBackend *driveBackend = DeviceBackend::backendForUDI(drivePath.path(), false); if (!driveBackend) return; driveBackend->invalidateProperties(); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksmanager.h000066400000000000000000000044441316350454000251160ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2MANAGER_H #define UDISKS2MANAGER_H #include "udisks2.h" #include "udisksdevice.h" #include "dbus/manager.h" #include "solid-lite/ifaces/devicemanager.h" #include #include namespace Solid { namespace Backends { namespace UDisks2 { class Manager: public Solid::Ifaces::DeviceManager { Q_OBJECT public: Manager(QObject *parent); virtual QObject* createDevice(const QString& udi); virtual QStringList devicesFromQuery(const QString& parentUdi, Solid::DeviceInterface::Type type); virtual QStringList allDevices(); virtual QSet< Solid::DeviceInterface::Type > supportedInterfaces() const; virtual QString udiPrefix() const; virtual ~Manager(); private Q_SLOTS: void slotInterfacesAdded(const QDBusObjectPath &object_path, const QVariantMapMap &interfaces_and_properties); void slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces); void slotMediaChanged(const QDBusMessage &msg); private: const QStringList &deviceCache(); void introspect(const QString & path, bool checkOptical = false); void updateBackend(const QString & udi); QSet m_supportedInterfaces; org::freedesktop::DBus::ObjectManager m_manager; QStringList m_deviceCache; }; } } } #endif // UDISKS2MANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksopticaldisc.cpp000066400000000000000000000245001316350454000263300ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 - 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include "../shared/udevqt.h" #include "udisks2.h" #include "udisksdevice.h" #include "udisksopticaldisc.h" #include "soliddefs_p.h" typedef QMap ContentTypesCache; SOLID_GLOBAL_STATIC(ContentTypesCache, cache) SOLID_GLOBAL_STATIC(QMutex, cacheLock) // inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c static Solid::OpticalDisc::ContentType advancedDiscDetect(const QByteArray & device_file) { /* the discs block size */ unsigned short bs; /* the path table size */ unsigned short ts; /* the path table location (in blocks) */ unsigned int tl; /* length of the directory name in current path table entry */ unsigned char len_di = 0; /* the number of the parent directory's path table entry */ unsigned int parent = 0; /* filename for the current path table entry */ char dirname[256]; /* our position into the path table */ int pos = 0; /* the path table record we're on */ int curr_record = 1; Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent; int fd = open(device_file.constData(), O_RDONLY); /* read the block size */ lseek (fd, 0x8080, SEEK_CUR); if (read (fd, &bs, 2) != 2) { qDebug("Advanced probing on %s failed while reading block size", qPrintable(device_file)); goto out; } /* read in size of path table */ lseek (fd, 2, SEEK_CUR); if (read (fd, &ts, 2) != 2) { qDebug("Advanced probing on %s failed while reading path table size", qPrintable(device_file)); goto out; } /* read in which block path table is in */ lseek (fd, 6, SEEK_CUR); if (read (fd, &tl, 4) != 4) { qDebug("Advanced probing on %s failed while reading path table block", qPrintable(device_file)); goto out; } /* seek to the path table */ lseek (fd, bs * tl, SEEK_SET); /* loop through the path table entries */ while (pos < ts) { /* get the length of the filename of the current entry */ if (read (fd, &len_di, 1) != 1) { qDebug("Advanced probing on %s failed, cannot read more entries", qPrintable(device_file)); break; } /* get the record number of this entry's parent i'm pretty sure that the 1st entry is always the top directory */ lseek (fd, 5, SEEK_CUR); if (read (fd, &parent, 2) != 2) { qDebug("Advanced probing on %s failed, couldn't read parent entry", qPrintable(device_file)); break; } /* read the name */ if (read (fd, dirname, len_di) != len_di) { qDebug("Advanced probing on %s failed, couldn't read the entry name", qPrintable(device_file)); break; } dirname[len_di] = 0; /* if we found a folder that has the root as a parent, and the directory name matches one of the special directories then set the properties accordingly */ if (parent == 1) { if (!strcasecmp (dirname, "VIDEO_TS")) { qDebug("Disc in %s is a Video DVD", qPrintable(device_file)); result = Solid::OpticalDisc::VideoDvd; break; } else if (!strcasecmp (dirname, "BDMV")) { qDebug("Disc in %s is a Blu-ray video disc", qPrintable(device_file)); result = Solid::OpticalDisc::VideoBluRay; break; } else if (!strcasecmp (dirname, "VCD")) { qDebug("Disc in %s is a Video CD", qPrintable(device_file)); result = Solid::OpticalDisc::VideoCd; break; } else if (!strcasecmp (dirname, "SVCD")) { qDebug("Disc in %s is a Super Video CD", qPrintable(device_file)); result = Solid::OpticalDisc::SuperVideoCd; break; } } /* all path table entries are padded to be even, so if this is an odd-length table, seek a byte to fix it */ if (len_di%2 == 1) { lseek (fd, 1, SEEK_CUR); pos++; } /* update our position */ pos += 8 + len_di; curr_record++; } close(fd); return result; out: /* go back to the start of the file */ lseek (fd, 0, SEEK_SET); close(fd); return result; } using namespace Solid::Backends::UDisks2; OpticalDisc::OpticalDisc(Device *dev) : StorageVolume(dev), m_needsReprobe(true), m_cachedContent(Solid::OpticalDisc::NoContent) { UdevQt::Client client(this); m_udevDevice = client.deviceByDeviceFile(device()); //qDebug() << "udev device:" << m_udevDevice.name() << "valid:" << m_udevDevice.isValid(); /*qDebug() << "\tProperties:" << */ m_udevDevice.deviceProperties(); // initialize the properties DB so that it doesn't crash further down, #298416 m_drive = new Device(m_device->drivePath()); QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, m_drive->udi(), DBUS_INTERFACE_PROPS, "PropertiesChanged", this, SLOT(slotDrivePropertiesChanged(QString,QVariantMap,QStringList))); } OpticalDisc::~OpticalDisc() { delete m_drive; } qulonglong OpticalDisc::capacity() const { return m_device->prop("Size").toULongLong(); } bool OpticalDisc::isRewritable() const { // the hard way, udisks has no notion of a disc "rewritability" const QString mediaType = media(); return mediaType == "optical_cd_rw" || mediaType == "optical_dvd_rw" || mediaType == "optical_dvd_ram" || mediaType == "optical_dvd_plus_rw" || mediaType == "optical_dvd_plus_rw_dl" || mediaType == "optical_bd_re" || mediaType == "optical_hddvd_rw"; } bool OpticalDisc::isBlank() const { return m_drive->prop("OpticalBlank").toBool(); } bool OpticalDisc::isAppendable() const { //qDebug() << "appendable prop" << m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE"); return m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE").toString() == QLatin1String("appendable"); } Solid::OpticalDisc::DiscType OpticalDisc::discType() const { QMap map; map[Solid::OpticalDisc::CdRom] = "optical_cd"; map[Solid::OpticalDisc::CdRecordable] = "optical_cd_r"; map[Solid::OpticalDisc::CdRewritable] = "optical_cd_rw"; map[Solid::OpticalDisc::DvdRom] = "optical_dvd"; map[Solid::OpticalDisc::DvdRecordable] = "optical_dvd_r"; map[Solid::OpticalDisc::DvdRewritable] ="optical_dvd_rw"; map[Solid::OpticalDisc::DvdRam] ="optical_dvd_ram"; map[Solid::OpticalDisc::DvdPlusRecordable] ="optical_dvd_plus_r"; map[Solid::OpticalDisc::DvdPlusRewritable] ="optical_dvd_plus_rw"; map[Solid::OpticalDisc::DvdPlusRecordableDuallayer] ="optical_dvd_plus_r_dl"; map[Solid::OpticalDisc::DvdPlusRewritableDuallayer] ="optical_dvd_plus_rw_dl"; map[Solid::OpticalDisc::BluRayRom] ="optical_bd"; map[Solid::OpticalDisc::BluRayRecordable] ="optical_bd_r"; map[Solid::OpticalDisc::BluRayRewritable] ="optical_bd_re"; map[Solid::OpticalDisc::HdDvdRom] ="optical_hddvd"; map[Solid::OpticalDisc::HdDvdRecordable] ="optical_hddvd_r"; map[Solid::OpticalDisc::HdDvdRewritable] ="optical_hddvd_rw"; // TODO add these to Solid //map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo"; //map[Solid::OpticalDisc::MountRainer] ="optical_mrw"; //map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w"; return map.key(media(), Solid::OpticalDisc::UnknownDiscType); // FIXME optimize, lookup by value, not key } Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const { if (isBlank()) { m_needsReprobe = false; return Solid::OpticalDisc::NoContent; } if (m_needsReprobe) { QMutexLocker lock(cacheLock); const QByteArray deviceFile = m_device->prop("Device").toByteArray(); if (cache->contains(deviceFile)) { m_cachedContent = cache->value(deviceFile); m_needsReprobe = false; return m_cachedContent; } m_cachedContent = Solid::OpticalDisc::NoContent; const bool hasData = m_drive->prop("OpticalNumDataTracks").toUInt() > 0; const bool hasAudio = m_drive->prop("OpticalNumAudioTracks").toUInt() > 0; if ( hasData ) { m_cachedContent |= Solid::OpticalDisc::Data; m_cachedContent |= advancedDiscDetect(deviceFile); } if ( hasAudio ) m_cachedContent |= Solid::OpticalDisc::Audio; m_needsReprobe = false; cache->insert(deviceFile, m_cachedContent); } return m_cachedContent; } void OpticalDisc::slotDrivePropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps) { Q_UNUSED(ifaceName); if (changedProps.keys().contains("Media") || invalidatedProps.contains("Media")) { QMutexLocker lock(cacheLock); m_needsReprobe = true; m_cachedContent = Solid::OpticalDisc::NoContent; cache->remove(m_device->prop("Device").toByteArray()); } } QString OpticalDisc::media() const { return m_drive->prop("Media").toString(); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksopticaldisc.h000066400000000000000000000041161316350454000257760ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010 - 2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2OPTICALDISC_H #define UDISKS2OPTICALDISC_H #include #include "../shared/udevqt.h" #include "udisksstoragevolume.h" #include "udisksdevice.h" namespace Solid { namespace Backends { namespace UDisks2 { class OpticalDisc: public StorageVolume, virtual public Solid::Ifaces::OpticalDisc { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDisc) public: OpticalDisc(Device *dev); virtual ~OpticalDisc(); virtual qulonglong capacity() const; virtual bool isRewritable() const; virtual bool isBlank() const; virtual bool isAppendable() const; virtual Solid::OpticalDisc::DiscType discType() const; virtual Solid::OpticalDisc::ContentTypes availableContent() const; private Q_SLOTS: void slotDrivePropertiesChanged(const QString & ifaceName, const QVariantMap & changedProps, const QStringList & invalidatedProps); private: QString media() const; mutable bool m_needsReprobe; mutable Solid::OpticalDisc::ContentTypes m_cachedContent; Device * m_drive; UdevQt::Device m_udevDevice; }; } } } #endif // UDISKS2OPTICALDISC_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksopticaldrive.cpp000066400000000000000000000165151316350454000265260ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include "udisksopticaldrive.h" #include "udisks2.h" #include "udisksdevice.h" #include "dbus/manager.h" using namespace Solid::Backends::UDisks2; OpticalDrive::OpticalDrive(Device *device) : StorageDrive(device) , m_ejectInProgress(false) , m_readSpeed(0) , m_writeSpeed(0) , m_speedsInit(false) { m_device->registerAction("eject", this, SLOT(slotEjectRequested()), SLOT(slotEjectDone(int, const QString&))); connect(m_device, SIGNAL(changed()), this, SLOT(slotChanged())); } OpticalDrive::~OpticalDrive() { } bool OpticalDrive::eject() { if (m_ejectInProgress) return false; m_ejectInProgress = true; m_device->broadcastActionRequested("eject"); const QString path = m_device->udi(); QDBusConnection c = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "Solid::Udisks2::OpticalDrive::" + path); // if the device is mounted, unmount first QString blockPath; org::freedesktop::DBus::ObjectManager manager(UD2_DBUS_SERVICE, UD2_DBUS_PATH, c); QDBusPendingReply reply = manager.GetManagedObjects(); reply.waitForFinished(); if (!reply.isError()) { // enum devices Q_FOREACH(const QDBusObjectPath &objPath, reply.value().keys()) { const QString udi = objPath.path(); //qDebug() << "Inspecting" << udi; if (udi == UD2_DBUS_PATH_MANAGER || udi == UD2_UDI_DISKS_PREFIX || udi.startsWith(UD2_DBUS_PATH_JOBS)) continue; Device device(udi); if (device.drivePath() == path && device.isMounted()) { //qDebug() << "Got mounted block device:" << udi; blockPath = udi; break; } } } else // show error { qWarning() << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message(); } if (!blockPath.isEmpty()) { //qDebug() << "Calling unmount on" << blockPath; QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, blockPath, UD2_DBUS_INTERFACE_FILESYSTEM, "Unmount"); msg << QVariantMap(); // options, unused now c.call(msg, QDBus::BlockWithGui); } QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_DRIVE, "Eject"); msg << QVariantMap(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(const QDBusMessage &)), SLOT(slotDBusError(const QDBusError &))); } void OpticalDrive::slotDBusReply(const QDBusMessage &/*reply*/) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } void OpticalDrive::slotDBusError(const QDBusError &error) { m_ejectInProgress = false; m_device->broadcastActionDone("eject", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " +error.message()); } void OpticalDrive::slotEjectRequested() { m_ejectInProgress = true; Q_EMIT ejectRequested(m_device->udi()); } void OpticalDrive::slotEjectDone(int error, const QString &errorString) { m_ejectInProgress = false; Q_EMIT ejectDone(static_cast(error), errorString, m_device->udi()); } void OpticalDrive::initReadWriteSpeeds() const { #if 0 int read_speed, write_speed; char *write_speeds = 0; QByteArray device_file = QFile::encodeName(m_device->property("Device").toString()); //qDebug("Doing open (\"%s\", O_RDONLY | O_NONBLOCK)", device_file.constData()); int fd = open(device_file, O_RDONLY | O_NONBLOCK); if (fd < 0) { qWarning("Cannot open %s: %s", device_file.constData(), strerror (errno)); return; } if (get_read_write_speed(fd, &read_speed, &write_speed, &write_speeds) >= 0) { m_readSpeed = read_speed; m_writeSpeed = write_speed; QStringList list = QString::fromLatin1(write_speeds).split(',', QString::SkipEmptyParts); Q_FOREACH (const QString & speed, list) m_writeSpeeds.append(speed.toInt()); free(write_speeds); m_speedsInit = true; } close(fd); #endif } QList OpticalDrive::writeSpeeds() const { if (!m_speedsInit) initReadWriteSpeeds(); //qDebug() << "solid write speeds:" << m_writeSpeeds; return m_writeSpeeds; } int OpticalDrive::writeSpeed() const { if (!m_speedsInit) initReadWriteSpeeds(); return m_writeSpeed; } int OpticalDrive::readSpeed() const { if (!m_speedsInit) initReadWriteSpeeds(); return m_readSpeed; } Solid::OpticalDrive::MediumTypes OpticalDrive::supportedMedia() const { const QStringList mediaTypes = m_device->prop("MediaCompatibility").toStringList(); Solid::OpticalDrive::MediumTypes supported; QMap map; map[Solid::OpticalDrive::Cdr] = "optical_cd_r"; map[Solid::OpticalDrive::Cdrw] = "optical_cd_rw"; map[Solid::OpticalDrive::Dvd] = "optical_dvd"; map[Solid::OpticalDrive::Dvdr] = "optical_dvd_r"; map[Solid::OpticalDrive::Dvdrw] ="optical_dvd_rw"; map[Solid::OpticalDrive::Dvdram] ="optical_dvd_ram"; map[Solid::OpticalDrive::Dvdplusr] ="optical_dvd_plus_r"; map[Solid::OpticalDrive::Dvdplusrw] ="optical_dvd_plus_rw"; map[Solid::OpticalDrive::Dvdplusdl] ="optical_dvd_plus_r_dl"; map[Solid::OpticalDrive::Dvdplusdlrw] ="optical_dvd_plus_rw_dl"; map[Solid::OpticalDrive::Bd] ="optical_bd"; map[Solid::OpticalDrive::Bdr] ="optical_bd_r"; map[Solid::OpticalDrive::Bdre] ="optical_bd_re"; map[Solid::OpticalDrive::HdDvd] ="optical_hddvd"; map[Solid::OpticalDrive::HdDvdr] ="optical_hddvd_r"; map[Solid::OpticalDrive::HdDvdrw] ="optical_hddvd_rw"; // TODO add these to Solid //map[Solid::OpticalDrive::Mo] ="optical_mo"; //map[Solid::OpticalDrive::Mr] ="optical_mrw"; //map[Solid::OpticalDrive::Mrw] ="optical_mrw_w"; Q_FOREACH ( const Solid::OpticalDrive::MediumType & type, map.keys() ) { if ( mediaTypes.contains( map[type] ) ) { supported |= type; } } return supported; } void OpticalDrive::slotChanged() { m_speedsInit = false; // reset the read/write speeds, changes eg. with an inserted media } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksopticaldrive.h000066400000000000000000000044241316350454000261670ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2OPTICALDRIVE_H #define UDISKS2OPTICALDRIVE_H #include #include "udisksstoragedrive.h" namespace Solid { namespace Backends { namespace UDisks2 { class OpticalDrive: public StorageDrive, virtual public Solid::Ifaces::OpticalDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDrive) public: OpticalDrive(Device *device); virtual ~OpticalDrive(); Q_SIGNALS: void ejectPressed(const QString &udi); void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void ejectRequested(const QString &udi); public: virtual bool eject(); virtual QList writeSpeeds() const; virtual int writeSpeed() const; virtual int readSpeed() const; virtual Solid::OpticalDrive::MediumTypes supportedMedia() const; private Q_SLOTS: void slotDBusReply(const QDBusMessage &reply); void slotDBusError(const QDBusError &error); void slotEjectRequested(); void slotEjectDone(int error, const QString &errorString); void slotChanged(); private: void initReadWriteSpeeds() const; bool m_ejectInProgress; // read/write speeds mutable int m_readSpeed; mutable int m_writeSpeed; mutable QList m_writeSpeeds; mutable bool m_speedsInit; }; } } } #endif // UDISKS2OPTICALDRIVE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstorageaccess.cpp000066400000000000000000000315761316350454000266730ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano Copyright 2009-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstorageaccess.h" #include "udisks2.h" #include #include #include #include using namespace Solid::Backends::UDisks2; StorageAccess::StorageAccess(Device *device) : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false) { connect(device, SIGNAL(changed()), this, SLOT(checkAccessibility())); updateCache(); // Delay connecting to DBus signals to avoid the related time penalty // in hot paths such as predicate matching QTimer::singleShot(0, this, SLOT(connectDBusSignals())); } StorageAccess::~StorageAccess() { } void StorageAccess::connectDBusSignals() { m_device->registerAction("setup", this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int, const QString&))); m_device->registerAction("teardown", this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int, const QString&))); } bool StorageAccess::isLuksDevice() const { return m_device->isEncryptedContainer(); // encrypted device } bool StorageAccess::isAccessible() const { if (isLuksDevice()) { // check if the cleartext slave is mounted const QString path = clearTextPath(); //qDebug() << Q_FUNC_INFO << "CLEARTEXT device path: " << path; if (path.isEmpty() || path == "/") return false; Device holderDevice(path); return holderDevice.isMounted(); } return m_device->isMounted(); } QString StorageAccess::filePath() const { QByteArrayList mntPoints; if (isLuksDevice()) { // encrypted (and unlocked) device const QString path = clearTextPath(); if (path.isEmpty() || path == "/") return QString(); Device holderDevice(path); mntPoints = qdbus_cast(holderDevice.prop("MountPoints")); if (!mntPoints.isEmpty()) return QFile::decodeName(mntPoints.first()); // FIXME Solid doesn't support multiple mount points else return QString(); } mntPoints = qdbus_cast(m_device->prop("MountPoints")); if (!mntPoints.isEmpty()) return QFile::decodeName(mntPoints.first()); // FIXME Solid doesn't support multiple mount points else return QString(); } bool StorageAccess::isIgnored() const { return m_device->prop("HintIgnore").toBool(); } bool StorageAccess::setup() { if ( m_teardownInProgress || m_setupInProgress ) return false; m_setupInProgress = true; m_device->broadcastActionRequested("setup"); if (m_device->isEncryptedContainer() && clearTextPath().isEmpty()) return requestPassphrase(); else return mount(); } bool StorageAccess::teardown() { if ( m_teardownInProgress || m_setupInProgress ) return false; m_teardownInProgress = true; m_device->broadcastActionRequested("teardown"); return unmount(); } void StorageAccess::updateCache() { m_isAccessible = isAccessible(); } void StorageAccess::checkAccessibility() { const bool old_isAccessible = m_isAccessible; updateCache(); if (old_isAccessible != m_isAccessible) { Q_EMIT accessibilityChanged(m_isAccessible, m_device->udi()); } } void StorageAccess::slotDBusReply( const QDBusMessage & /*reply*/ ) { const QString ctPath = clearTextPath(); if (m_setupInProgress) { if (isLuksDevice() && !isAccessible()) { // unlocked device, now mount it mount(); } else // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156) { m_setupInProgress = false; m_device->broadcastActionDone("setup"); checkAccessibility(); } } else if (m_teardownInProgress) // FIXME { if (isLuksDevice() && !ctPath.isEmpty() && ctPath != "/") // unlocked device, lock it { callCryptoTeardown(); } else if (!ctPath.isEmpty() && ctPath != "/") { callCryptoTeardown(true); // Lock crypted parent } else { // try to "eject" (aka safely remove) from the (parent) drive, e.g. SD card from a reader QString drivePath = m_device->drivePath(); if (!drivePath.isEmpty() || drivePath != "/") { Device drive(drivePath); if (drive.prop("Ejectable").toBool() && drive.prop("MediaAvailable").toBool() && !m_device->isOpticalDisc()) // optical drives have their Eject method { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, drivePath, UD2_DBUS_INTERFACE_DRIVE, "Eject"); msg << QVariantMap(); // options, unused now c.call(msg, QDBus::NoBlock); } } m_teardownInProgress = false; m_device->broadcastActionDone("teardown"); checkAccessibility(); } } } void StorageAccess::slotDBusError( const QDBusError & error ) { //qDebug() << Q_FUNC_INFO << "DBUS ERROR:" << error.name() << error.message(); if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " +error.message()); checkAccessibility(); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " + error.message()); checkAccessibility(); } } void StorageAccess::slotSetupRequested() { m_setupInProgress = true; //qDebug() << "SETUP REQUESTED:" << m_device->udi(); Q_EMIT setupRequested(m_device->udi()); } void StorageAccess::slotSetupDone(int error, const QString &errorString) { m_setupInProgress = false; //qDebug() << "SETUP DONE:" << m_device->udi(); Q_EMIT setupDone(static_cast(error), errorString, m_device->udi()); checkAccessibility(); } void StorageAccess::slotTeardownRequested() { m_teardownInProgress = true; Q_EMIT teardownRequested(m_device->udi()); } void StorageAccess::slotTeardownDone(int error, const QString &errorString) { m_teardownInProgress = false; Q_EMIT teardownDone(static_cast(error), errorString, m_device->udi()); checkAccessibility(); } bool StorageAccess::mount() { QString path = m_device->udi(); const QString ctPath = clearTextPath(); if (isLuksDevice() && !ctPath.isEmpty()) { // mount options for the cleartext volume path = ctPath; } QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Mount"); QVariantMap options; if (m_device->prop("IdType").toString() == "vfat") options.insert("options", "flush"); msg << options; return c.callWithCallback(msg, this, SLOT(slotDBusReply(const QDBusMessage &)), SLOT(slotDBusError(const QDBusError &))); } bool StorageAccess::unmount() { QString path = m_device->udi(); const QString ctPath = clearTextPath(); if (isLuksDevice() && !ctPath.isEmpty()) { // unmount options for the cleartext volume path = ctPath; } QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Unmount"); msg << QVariantMap(); // options, unused now return c.callWithCallback(msg, this, SLOT(slotDBusReply(const QDBusMessage &)), SLOT(slotDBusError(const QDBusError &)), s_unmountTimeout); } QString StorageAccess::generateReturnObjectPath() { static int number = 1; return "/org/kde/solid/UDisks2StorageAccess_"+QString::number(number++); } QString StorageAccess::clearTextPath() const { const QString prefix = "/org/freedesktop/UDisks2/block_devices"; QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, prefix, DBUS_INTERFACE_INTROSPECT, "Introspect"); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(call); reply.waitForFinished(); if (reply.isValid()) { QDomDocument dom; dom.setContent(reply.value()); QDomNodeList nodeList = dom.documentElement().elementsByTagName("node"); for (int i = 0; i < nodeList.count(); i++) { QDomElement nodeElem = nodeList.item(i).toElement(); if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) { const QString udi = prefix + "/" + nodeElem.attribute("name"); Device holderDevice(udi); if (m_device->udi() == holderDevice.prop("CryptoBackingDevice").value().path()) { //qDebug() << Q_FUNC_INFO << "CLEARTEXT device path: " << udi; return udi; } } } } return QString(); } bool StorageAccess::requestPassphrase() { QString udi = m_device->udi(); QString returnService = QDBusConnection::sessionBus().baseService(); m_lastReturnObject = generateReturnObjectPath(); QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots); QWidget *activeWindow = QApplication::activeWindow(); uint wId = 0; if (activeWindow!=0) wId = (uint)activeWindow->winId(); QString appId = QCoreApplication::applicationName(); QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer"); QDBusReply reply = soliduiserver.call("showPassphraseDialog", udi, returnService, m_lastReturnObject, wId, appId); m_passphraseRequested = reply.isValid(); if (!m_passphraseRequested) qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error(); return m_passphraseRequested; } void StorageAccess::passphraseReply(const QString & passphrase) { if (m_passphraseRequested) { QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject); m_passphraseRequested = false; if (!passphrase.isEmpty()) callCryptoSetup(passphrase); else { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } } } void StorageAccess::callCryptoSetup(const QString & passphrase) { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_device->udi(), UD2_DBUS_INTERFACE_ENCRYPTED, "Unlock"); msg << passphrase; msg << QVariantMap(); // options, unused now c.callWithCallback(msg, this, SLOT(slotDBusReply(const QDBusMessage &)), SLOT(slotDBusError(const QDBusError &))); } bool StorageAccess::callCryptoTeardown(bool actOnParent) { QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, actOnParent ? (m_device->prop("CryptoBackingDevice").value().path()) : m_device->udi(), UD2_DBUS_INTERFACE_ENCRYPTED, "Lock"); msg << QVariantMap(); // options, unused now return c.callWithCallback(msg, this, SLOT(slotDBusReply(const QDBusMessage &)), SLOT(slotDBusError(const QDBusError &))); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstorageaccess.h000066400000000000000000000060131316350454000263240ustar00rootroot00000000000000/* Copyright 2009 Pino Toscano Copyright 2009-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2STORAGEACCESS_H #define UDISKS2STORAGEACCESS_H #include #include "udisksdeviceinterface.h" #include #include namespace Solid { namespace Backends { namespace UDisks2 { class StorageAccess : public DeviceInterface, virtual public Solid::Ifaces::StorageAccess { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageAccess) public: StorageAccess(Device *device); virtual ~StorageAccess(); virtual bool isAccessible() const; virtual QString filePath() const; virtual bool isIgnored() const; virtual bool setup(); virtual bool teardown(); Q_SIGNALS: void accessibilityChanged(bool accessible, const QString &udi); void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void setupRequested(const QString &udi); void teardownRequested(const QString &udi); public Q_SLOTS: Q_SCRIPTABLE Q_NOREPLY void passphraseReply(const QString & passphrase); private Q_SLOTS: void slotDBusReply(const QDBusMessage & reply); void slotDBusError(const QDBusError & error); void connectDBusSignals(); void slotSetupRequested(); void slotSetupDone(int error, const QString &errorString); void slotTeardownRequested(); void slotTeardownDone(int error, const QString &errorString); void checkAccessibility(); private: /// @return true if this device is luks and unlocked bool isLuksDevice() const; void updateCache(); bool mount(); bool unmount(); bool requestPassphrase(); void callCryptoSetup( const QString & passphrase ); bool callCryptoTeardown( bool actOnParent=false ); QString generateReturnObjectPath(); QString clearTextPath() const; private: bool m_isAccessible; bool m_setupInProgress; bool m_teardownInProgress; bool m_passphraseRequested; QString m_lastReturnObject; static const int s_unmountTimeout = 0x7fffffff; }; } } } #endif // UDISKS2STORAGEACCESS_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstoragedrive.cpp000066400000000000000000000101731316350454000265310ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstoragedrive.h" #include "../shared/udevqt.h" #include #include using namespace Solid::Backends::UDisks2; StorageDrive::StorageDrive(Device *dev) : Block(dev) { UdevQt::Client client(this); m_udevDevice = client.deviceByDeviceFile(device()); m_udevDevice.deviceProperties(); } StorageDrive::~StorageDrive() { } qulonglong StorageDrive::size() const { return m_device->prop("Size").toULongLong(); } bool StorageDrive::isHotpluggable() const { const Solid::StorageDrive::Bus _bus = bus(); return _bus == Solid::StorageDrive::Usb || _bus == Solid::StorageDrive::Ieee1394; } bool StorageDrive::isRemovable() const { return m_device->prop("MediaRemovable").toBool() || m_device->prop("Removable").toBool(); } Solid::StorageDrive::DriveType StorageDrive::driveType() const { const QStringList mediaTypes = m_device->prop("MediaCompatibility").toStringList(); if ( m_device->isOpticalDrive() ) // optical disks { return Solid::StorageDrive::CdromDrive; } else if ( mediaTypes.contains( "floppy" ) ) { return Solid::StorageDrive::Floppy; } #if 0 // TODO add to Solid else if ( mediaTypes.contains( "floppy_jaz" ) ) { return Solid::StorageDrive::Jaz; } else if ( mediaTypes.contains( "floppy_zip" ) ) { return Solid::StorageDrive::Zip; } else if ( mediaTypes.contains( "flash" ) ) { return Solid::StorageDrive::Flash; } #endif else if ( mediaTypes.contains( "flash_cf" ) ) { return Solid::StorageDrive::CompactFlash; } else if ( mediaTypes.contains( "flash_ms" ) ) { return Solid::StorageDrive::MemoryStick; } else if ( mediaTypes.contains( "flash_sm" ) ) { return Solid::StorageDrive::SmartMedia; } else if ( mediaTypes.contains( "flash_sd" ) || mediaTypes.contains( "flash_sdhc" ) || mediaTypes.contains( "flash_mmc" ) || mediaTypes.contains("flash_sdxc") ) { return Solid::StorageDrive::SdMmc; } // FIXME: udisks2 doesn't know about xD cards else { return Solid::StorageDrive::HardDisk; } } Solid::StorageDrive::Bus StorageDrive::bus() const { const QString bus = m_device->prop("ConnectionBus").toString(); const QString udevBus = m_udevDevice.deviceProperty("ID_BUS").toString(); //qDebug() << "bus:" << bus << "udev bus:" << udevBus; if (udevBus == "ata") { if (m_udevDevice.deviceProperty("ID_ATA_SATA").toInt() == 1) // serial ATA return Solid::StorageDrive::Sata; else // parallel (classical) ATA return Solid::StorageDrive::Ide; } else if (bus == "usb") { return Solid::StorageDrive::Usb; } else if (bus == "ieee1394") { return Solid::StorageDrive::Ieee1394; } else if (udevBus == "scsi") { return Solid::StorageDrive::Scsi; } #if 0 // TODO add these to Solid else if ( bus == "sdio" ) { return Solid::StorageDrive::SDIO; } else if ( bus == "virtual" ) { return Solid::StorageDrive::Virtual; } #endif else return Solid::StorageDrive::Platform; } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstoragedrive.h000066400000000000000000000033001316350454000261700ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2STORAGEDRIVE_H #define UDISKS2STORAGEDRIVE_H #include #include "../shared/udevqt.h" #include "udisksblock.h" namespace Solid { namespace Backends { namespace UDisks2 { class StorageDrive: public Block, virtual public Solid::Ifaces::StorageDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageDrive) public: StorageDrive(Device *dev); virtual ~StorageDrive(); virtual qulonglong size() const; virtual bool isHotpluggable() const; virtual bool isRemovable() const; virtual Solid::StorageDrive::DriveType driveType() const; virtual Solid::StorageDrive::Bus bus() const; private: UdevQt::Device m_udevDevice; }; } } } #endif // UDISK2SSTORAGEDRIVE_H cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstoragevolume.cpp000066400000000000000000000057711316350454000267370ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti Copyright 2010-2012 Lukáš Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "udisksstoragevolume.h" #include "udisks2.h" using namespace Solid::Backends::UDisks2; StorageVolume::StorageVolume(Device *device) : Block(device) { } StorageVolume::~StorageVolume() { } QString StorageVolume::encryptedContainerUdi() const { const QString path = m_device->prop("CryptoBackingDevice").value().path(); if ( path.isEmpty() || path == "/") return QString(); else return path; } qulonglong StorageVolume::size() const { return m_device->prop("Size").toULongLong(); } QString StorageVolume::uuid() const { return m_device->prop("IdUUID").toString(); } QString StorageVolume::label() const { QString label = m_device->prop("HintName").toString(); if (label.isEmpty()) label = m_device->prop("IdLabel").toString(); if (label.isEmpty()) label = m_device->prop("Name").toString(); return label; } QString StorageVolume::fsType() const { return m_device->prop("IdType").toString(); } Solid::StorageVolume::UsageType StorageVolume::usage() const { const QString usage = m_device->prop("IdUsage").toString(); if (m_device->hasInterface(UD2_DBUS_INTERFACE_FILESYSTEM)) { return Solid::StorageVolume::FileSystem; } else if (m_device->isPartitionTable()) { return Solid::StorageVolume::PartitionTable; } else if (usage == "raid") { return Solid::StorageVolume::Raid; } else if (m_device->isEncryptedContainer()) { return Solid::StorageVolume::Encrypted; } else if (usage == "unused" || usage.isEmpty()) { return Solid::StorageVolume::Unused; } else { return Solid::StorageVolume::Other; } } bool StorageVolume::isIgnored() const { const Solid::StorageVolume::UsageType usg = usage(); return m_device->prop("HintIgnore").toBool() || m_device->isSwap() || ((usg == Solid::StorageVolume::Unused || usg == Solid::StorageVolume::Other || usg == Solid::StorageVolume::PartitionTable) && !m_device->isOpticalDisc()); } cantata-2.2.0/3rdparty/solid-lite/backends/udisks2/udisksstoragevolume.h000066400000000000000000000032041316350454000263710ustar00rootroot00000000000000/* Copyright 2010 Michael Zanetti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef UDISKS2STORAGEVOLUME_H #define UDISKS2STORAGEVOLUME_H #include #include "udisksblock.h" namespace Solid { namespace Backends { namespace UDisks2 { class StorageVolume: public Block, virtual public Solid::Ifaces::StorageVolume { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageVolume) public: StorageVolume(Device *device); virtual ~StorageVolume(); virtual QString encryptedContainerUdi() const; virtual qulonglong size() const; virtual QString uuid() const; virtual QString label() const; virtual QString fsType() const; virtual Solid::StorageVolume::UsageType usage() const; virtual bool isIgnored() const; }; } } } #endif // UDISKS2STORAGEVOLUME_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/000077500000000000000000000000001316350454000213125ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiblock.cpp000066400000000000000000000033041316350454000236250ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmiblock.h" #include "wmidevice.h" using namespace Solid::Backends::Wmi; Block::Block(WmiDevice *device) : DeviceInterface(device) { } Block::~Block() { } int Block::deviceMajor() const { return 0; } int Block::deviceMinor() const { return 0; } QString Block::device() const { QString drive; switch(m_device->type()){ case Solid::DeviceInterface::StorageVolume: { drive = WmiDevice::win32LogicalDiskByDiskPartitionID(m_device->property("DeviceID").toString()).getProperty("DeviceID").toString(); } break; case Solid::DeviceInterface::OpticalDrive: case Solid::DeviceInterface::OpticalDisc: drive = m_device->property("Drive").toString(); break; } return drive; } //#include "backends/wmi/wmiblock.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiblock.h000066400000000000000000000027011316350454000232720ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_BLOCK_H #define SOLID_BACKENDS_WMI_BLOCK_H #include #include "wmideviceinterface.h" namespace Solid { namespace Backends { namespace Wmi { class Block : public DeviceInterface, virtual public Solid::Ifaces::Block { Q_OBJECT Q_INTERFACES(Solid::Ifaces::Block) public: Block(WmiDevice *device); virtual ~Block(); virtual int deviceMajor() const; virtual int deviceMinor() const; virtual QString device() const; }; } } } #endif // SOLID_BACKENDS_WMI_BLOCK_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmicdrom.cpp000066400000000000000000000107261316350454000236450ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmicdrom.h" #include using namespace Solid::Backends::Wmi; Cdrom::Cdrom(WmiDevice *device) : Storage(device), m_ejectInProgress(false) { connect(device, SIGNAL(conditionRaised(QString,QString)), this, SLOT(slotCondition(QString,QString))); } Cdrom::~Cdrom() { } Solid::OpticalDrive::MediumTypes Cdrom::supportedMedia() const { Solid::OpticalDrive::MediumTypes supported; QString type = m_device->property("MediaType").toString(); if (type == "CdRomOnly" || type == "CD-ROM") { supported |= Solid::OpticalDrive::Cdr; } else if (type == "CdRomWrite") { supported |= Solid::OpticalDrive::Cdr|Solid::OpticalDrive::Cdrw; } else if (type == "DVDRomOnly") { supported |= Solid::OpticalDrive::Dvd; } else if (type == "DVDRomWrite" || type == "DVD Writer") { supported |= Solid::OpticalDrive::Dvd|Solid::OpticalDrive::Dvdr|Solid::OpticalDrive::Dvdrw; } return supported; } int Cdrom::readSpeed() const { return m_device->property("TransferRate").toInt(); } int Cdrom::writeSpeed() const { return m_device->property("TransferRate").toInt(); } QList Cdrom::writeSpeeds() const { QList speeds; return speeds; } void Cdrom::slotCondition(const QString &name, const QString &/*reason */) { if (name == "EjectPressed") { emit ejectPressed(m_device->udi()); } } bool Cdrom::eject() { if (m_ejectInProgress) { return false; } m_ejectInProgress = true; return callWmiDriveEject(); } bool Cdrom::callWmiDriveEject() { // QString udi = m_device->udi(); // QString interface = "org.freedesktop.Wmi.Device.Storage"; // HACK: Eject doesn't work on cdrom drives when there's a mounted disc, // let's try to workaround this by calling a child volume... // if (m_device->property("storage.removable.media_available").toBool()) { // QDBusInterface manager("org.freedesktop.Wmi", // "/org/freedesktop/Wmi/Manager", // "org.freedesktop.Wmi.Manager", // QDBusConnection::systemBus()); // QDBusReply reply = manager.call("FindDeviceStringMatch", "info.parent", udi); // if (reply.isValid()) // { // QStringList udis = reply; // if (!udis.isEmpty()) { // udi = udis[0]; // interface = "org.freedesktop.Wmi.Device.Volume"; // } // } // } // QDBusConnection c = QDBusConnection::systemBus(); // QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Wmi", udi, // interface, "Eject"); // msg << QStringList(); // return c.callWithCallback(msg, this, // SLOT(slotDBusReply(QDBusMessage)), // SLOT(slotDBusError(QDBusError))); return false; } void Solid::Backends::Wmi::Cdrom::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (m_ejectInProgress) { m_ejectInProgress = false; if (exitCode==0) { emit ejectDone(Solid::NoError, QVariant(), m_device->udi()); } else { emit ejectDone(Solid::UnauthorizedOperation, m_process->readAllStandardError(), m_device->udi()); } } delete m_process; } #include "backends/wmi/wmicdrom.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmicdrom.h000066400000000000000000000040031316350454000233010ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_CDROM_H #define SOLID_BACKENDS_WMI_CDROM_H #include #include "wmistorage.h" #include namespace Solid { namespace Backends { namespace Wmi { class Cdrom : public Storage, virtual public Solid::Ifaces::OpticalDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDrive) public: Cdrom(WmiDevice *device); virtual ~Cdrom(); virtual Solid::OpticalDrive::MediumTypes supportedMedia() const; virtual int readSpeed() const; virtual int writeSpeed() const; virtual QList writeSpeeds() const; virtual bool eject(); Q_SIGNALS: void ejectPressed(const QString &udi); void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); private Q_SLOTS: void slotCondition(const QString &name, const QString &reason); void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: bool callWmiDriveEject(); bool m_ejectInProgress; QProcess *m_process; }; } } } #endif // SOLID_BACKENDS_WMI_CDROM_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmidevice.cpp000066400000000000000000000510071316350454000237750ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmidevice.h" #include #include "wmiquery.h" #include "wmimanager.h" #include "wmideviceinterface.h" #include "wmigenericinterface.h" //#include "wmiprocessor.h" #include "wmiblock.h" #include "wmistorageaccess.h" #include "wmistorage.h" #include "wmicdrom.h" #include "wmivolume.h" #include "wmiopticaldisc.h" //#include "wmicamera.h" #include "wmiportablemediaplayer.h" //#include "wminetworkinterface.h" //#include "wmiacadapter.h" //#include "wmibattery.h" //#include "wmibutton.h" //#include "wmiaudiointerface.h" //#include "wmidvbinterface.h" //#include "wmivideo.h" #include using namespace Solid::Backends::Wmi; class Solid::Backends::Wmi::WmiDevicePrivate { public: WmiDevicePrivate(const QString &_udi) : parent(0) , m_udi(_udi) , m_wmiTable() , m_wmiProperty() , m_wmiValue() { } ~WmiDevicePrivate() { } void discoverType() { if (!convertUDItoWMI(m_udi,m_wmiTable,m_wmiProperty,m_wmiValue,m_type)) return; interfaceList< 0) return true; qWarning()<<"Device UDI:"< getInterfaces(const Solid::DeviceInterface::Type &type) { QList interfaceList; interfaceList< 0 ? list[0] : QString(); return value; } static QString getWMITable(const Solid::DeviceInterface::Type &type) { QString wmiTable; switch (type) { case Solid::DeviceInterface::GenericInterface: break; //case Solid::DeviceInterface::Processor: // wmiTable = "Win32_Processor"; // break; case Solid::DeviceInterface::Block: break; case Solid::DeviceInterface::StorageAccess: wmiTable = "Win32_DiskPartition"; break; case Solid::DeviceInterface::StorageDrive: wmiTable = "Win32_DiskDrive"; break; case Solid::DeviceInterface::OpticalDrive: wmiTable = "Win32_CDROMDrive"; break; case Solid::DeviceInterface::StorageVolume: wmiTable = "Win32_DiskPartition"; break; case Solid::DeviceInterface::OpticalDisc: wmiTable = "Win32_CDROMDrive"; break; //case Solid::DeviceInterface::Camera: // break; case Solid::DeviceInterface::PortableMediaPlayer: break; /*case Solid::DeviceInterface::NetworkInterface: break; case Solid::DeviceInterface::AcAdapter: case Solid::DeviceInterface::Battery: wmiTable = "Win32_Battery"; break; case Solid::DeviceInterface::Button: break; case Solid::DeviceInterface::AudioInterface: break; case Solid::DeviceInterface::DvbInterface: break; case Solid::DeviceInterface::Video: break; */ case Solid::DeviceInterface::Unknown: case Solid::DeviceInterface::Last: default: wmiTable = "unknown"; break; } return wmiTable; } static QString getIgnorePattern(const Solid::DeviceInterface::Type &type) { QString propertyName; switch(type){ case Solid::DeviceInterface::OpticalDisc: propertyName = " WHERE MediaLoaded=TRUE"; break; } return propertyName; } static QString getPropertyNameForUDI(const Solid::DeviceInterface::Type &type) { QString propertyName; switch(type){ case Solid::DeviceInterface::OpticalDrive: case Solid::DeviceInterface::OpticalDisc: propertyName = "Drive"; break; case Solid::DeviceInterface::StorageDrive: propertyName = "Index"; break; default: propertyName = "DeviceID"; } return propertyName; } static QStringList generateUDIList(const Solid::DeviceInterface::Type &type) { QStringList result; WmiQuery::ItemList list = WmiQuery::instance().sendQuery( "SELECT * FROM " + getWMITable(type) + getIgnorePattern(type)); foreach(const WmiQuery::Item& item, list) { QString propertyName = getPropertyNameForUDI(type); QString property = item.getProperty(propertyName).toString(); result << generateUDI(getUDIKey(type),property.toLower()); } return result; } WmiDevice *parent; static int m_instanceCount; QString m_parent_uid; QString m_udi; QString m_wmiTable; QString m_wmiProperty; QString m_wmiValue; Solid::DeviceInterface::Type m_type; WmiQuery::Item m_item; QList interfaceList; }; Q_DECLARE_METATYPE(ChangeDescription) Q_DECLARE_METATYPE(QList) WmiDevice::WmiDevice(const QString &udi) : Device(), d(new WmiDevicePrivate(udi)) { d->discoverType(); foreach (Solid::DeviceInterface::Type type, d->interfaceList) { createDeviceInterface(type); } } WmiDevice::~WmiDevice() { //delete d->parent; delete d; } QStringList WmiDevice::generateUDIList(const Solid::DeviceInterface::Type &type) { return WmiDevicePrivate::generateUDIList(type); } bool WmiDevice::exists(const QString &udi) { return WmiDevicePrivate::exists(udi); } bool WmiDevice::isValid() const { // does not work //return sendQuery( "SELECT * FROM Win32_SystemDevices WHERE PartComponent='\\\\\\\\BEAST\root\cimv2:Win32_Processor.DeviceID=\"CPU0\"'" ).count() == 1; return true; } QString WmiDevice::udi() const { return d->udi(); } QString WmiDevice::parentUdi() const { if(!d->m_parent_uid.isEmpty()) return d->m_parent_uid; QString result; const QString value = udi().split("/").last(); switch(d->m_type){ case Solid::DeviceInterface::StorageVolume: case Solid::DeviceInterface::StorageAccess: result = "/org/kde/solid/wmi/storage/"+property("DiskIndex").toString(); break; case Solid::DeviceInterface::OpticalDisc: result = "/org/kde/solid/wmi/storage.cdrom/"+property("Drive").toString().toLower(); break; } if(result.isEmpty() && !value.isEmpty()){ result = udi(); result = result.remove("/"+value); } d->m_parent_uid = result; return d->m_parent_uid; } QString WmiDevice::vendor() const { QString propertyName; switch(type()){ //case Solid::DeviceInterface::Processor: // propertyName = "Manufacturer"; // break; case Solid::DeviceInterface::OpticalDrive: case Solid::DeviceInterface::OpticalDisc: propertyName = "Caption"; break; //case Solid::DeviceInterface::Battery: // propertyName = "DeviceID"; // break; case Solid::DeviceInterface::StorageAccess: case Solid::DeviceInterface::StorageVolume: { WmiDevice parent(parentUdi()); return parent.vendor(); } break; case Solid::DeviceInterface::StorageDrive: propertyName = "Model"; break; default: propertyName = "DeviceID";//TODO: } return property(propertyName).toString(); } QString WmiDevice::product() const { QString propertyName; switch(type()){ //case Solid::DeviceInterface::Processor: // propertyName = "Name"; // break; case Solid::DeviceInterface::StorageAccess: case Solid::DeviceInterface::StorageVolume: { WmiQuery::Item item = win32LogicalDiskByDiskPartitionID(property("DeviceID").toString()); return item.getProperty("VolumeName").toString(); } break; //case Solid::DeviceInterface::AcAdapter: // return description(); default: propertyName = "Caption"; } return property(propertyName).toString(); } QString WmiDevice::icon() const { QString propertyName; switch(type()){ //case Solid::DeviceInterface::Processor: // propertyName = "cpu"; // break; case Solid::DeviceInterface::OpticalDisc: { WmiDevice dev(udi()); OpticalDisc disk(&dev); if(disk.availableContent() | Solid::OpticalDisc::Audio)//no other are recognized yet propertyName = "media-optical-audio"; else propertyName = "drive-optical"; break; } //case Solid::DeviceInterface::Battery: // propertyName = "battery"; // break; case Solid::DeviceInterface::StorageAccess: case Solid::DeviceInterface::StorageVolume: { WmiDevice parent(parentUdi()); Storage storage(&parent); if(storage.bus() == Solid::StorageDrive::Usb) propertyName = "drive-removable-media-usb-pendrive"; else propertyName = "drive-harddisk"; } break; } return propertyName; } QStringList WmiDevice::emblems() const { return QStringList(); // TODO } QString WmiDevice::description() const { switch(type()){ case Solid::DeviceInterface::OpticalDisc: return property("VolumeName").toString(); /*case Solid::DeviceInterface::AcAdapter: return QObject::tr("A/C Adapter"); case Solid::DeviceInterface::Battery: { WmiDevice dev(udi()); Battery bat(&dev); return QObject::tr("%1 Battery", "%1 is battery technology").arg(bat.batteryTechnology()); } */ default: return product(); } } QVariant WmiDevice::property(const QString &key) const { WmiQuery::Item item = d->sendQuery(); QVariant result = item.getProperty(key); // qDebug()<<"property"<m_type; } /** * @brief WmiDevice::win32DiskPartitionByDeviceIndex * @param deviceID something like "\\.\PHYSICALDRIVE0" * @return */ WmiQuery::Item WmiDevice::win32DiskPartitionByDeviceIndex(const QString &deviceID){ WmiQuery::Item result; QString id = deviceID; QString query("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='"+ id +"'} WHERE AssocClass = Win32_DiskDriveToDiskPartition"); WmiQuery::ItemList items = WmiQuery::instance().sendQuery(query); if(items.length()>0){ result = items[0]; } return result; } /** * @brief WmiDevice::win32DiskDriveByDiskPartitionID * @param deviceID something like "disk #1, partition #0" * @return */ WmiQuery::Item WmiDevice::win32DiskDriveByDiskPartitionID(const QString &deviceID){ WmiQuery::Item result; QString id = deviceID; QString query("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='"+ id +"'} WHERE AssocClass = Win32_DiskDriveToDiskPartition"); WmiQuery::ItemList items = WmiQuery::instance().sendQuery(query); if(items.length()>0){ result = items[0]; } return result; } /** * @brief WmiDevice::win32LogicalDiskByDiskPartitionID * @param deviceID something like "disk #1, partition #0" * @return */ WmiQuery::Item WmiDevice::win32LogicalDiskByDiskPartitionID(const QString &deviceID){ WmiQuery::Item result; QString id = deviceID; QString query("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + id + "'} WHERE AssocClass = Win32_LogicalDiskToPartition"); WmiQuery::ItemList items = WmiQuery::instance().sendQuery(query); if(items.length()>0){ result = items[0]; } return result; } /** * @brief WmiDevice::win32DiskPartitionByDriveLetter * @param driveLetter something lik "D:" * @return */ WmiQuery::Item WmiDevice::win32DiskPartitionByDriveLetter(const QString &driveLetter){ WmiQuery::Item result; QString id = driveLetter; QString query("ASSOCIATORS OF {Win32_LogicalDisk.DeviceID='" + id + "'} WHERE AssocClass = Win32_LogicalDiskToPartition"); WmiQuery::ItemList items = WmiQuery::instance().sendQuery(query); if(items.length()>0){ result = items[0]; } return result; } /** * @brief WmiDevice::win32LogicalDiskByDriveLetter * @param driveLetter something lik "D:" * @return */ WmiQuery::Item WmiDevice::win32LogicalDiskByDriveLetter(const QString &driveLetter){ WmiQuery::Item result; QString id = driveLetter; QString query("SELECT * FROM Win32_LogicalDisk WHERE DeviceID='"+id+"'"); WmiQuery::ItemList items = WmiQuery::instance().sendQuery(query); if(items.length()>0){ result = items[0]; } return result; } QMap WmiDevice::allProperties() const { WmiQuery::Item item = d->sendQuery(); return item.getAllProperties(); } bool WmiDevice::propertyExists(const QString &key) const { WmiQuery::Item item = d->sendQuery(); const bool isEmpty = item.getProperty( key ).isValid(); return isEmpty; } bool WmiDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const { // Special cases not matching with WMI capabilities if (type==Solid::DeviceInterface::GenericInterface) { return true; } return d->interfaceList.contains(type); } QObject *WmiDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type) { if (!queryDeviceInterface(type)) { return 0; } DeviceInterface *iface = 0; switch (type) { case Solid::DeviceInterface::GenericInterface: iface = new GenericInterface(this); break; //case Solid::DeviceInterface::Processor: // iface = new Processor(this); // break; case Solid::DeviceInterface::Block: iface = new Block(this); break; case Solid::DeviceInterface::StorageAccess: iface = new StorageAccess(this); break; case Solid::DeviceInterface::StorageDrive: iface = new Storage(this); break; case Solid::DeviceInterface::OpticalDrive: iface = new Cdrom(this); break; case Solid::DeviceInterface::StorageVolume: iface = new Volume(this); break; case Solid::DeviceInterface::OpticalDisc: iface = new OpticalDisc(this); break; //case Solid::DeviceInterface::Camera: // iface = new Camera(this); // break; case Solid::DeviceInterface::PortableMediaPlayer: iface = new PortableMediaPlayer(this); break; /*case Solid::DeviceInterface::NetworkInterface: iface = new NetworkInterface(this); break; case Solid::DeviceInterface::AcAdapter: iface = new AcAdapter(this); break; case Solid::DeviceInterface::Battery: iface = new Battery(this); break; case Solid::DeviceInterface::Button: iface = new Button(this); break; case Solid::DeviceInterface::AudioInterface: iface = new AudioInterface(this); break; case Solid::DeviceInterface::DvbInterface: iface = new DvbInterface(this); break; case Solid::DeviceInterface::Video: iface = new Video(this); break; */ case Solid::DeviceInterface::Unknown: case Solid::DeviceInterface::Last: break; } return iface; } void WmiDevice::slotPropertyModified(int /*count */, const QList &changes) { QMap result; foreach (const ChangeDescription &change, changes) { QString key = change.key; bool added = change.added; bool removed = change.removed; Solid::GenericInterface::PropertyChange type = Solid::GenericInterface::PropertyModified; if (added) { type = Solid::GenericInterface::PropertyAdded; } else if (removed) { type = Solid::GenericInterface::PropertyRemoved; } result[key] = type; } emit propertyChanged(result); } void WmiDevice::slotCondition(const QString &condition, const QString &reason) { emit conditionRaised(condition, reason); } //#include "backends/wmi/wmidevice.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmidevice.h000066400000000000000000000061651316350454000234470ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_WMIDEVICE_H #define SOLID_BACKENDS_WMI_WMIDEVICE_H #include #include "wmiquery.h" namespace Solid { namespace Backends { namespace Wmi { class WmiManager; class WmiDevicePrivate; struct ChangeDescription { QString key; bool added; bool removed; }; class WmiDevice : public Solid::Ifaces::Device { Q_OBJECT public: WmiDevice(const QString &udi); virtual ~WmiDevice(); virtual QString udi() const; virtual QString parentUdi() const; virtual QString vendor() const; virtual QString product() const; virtual QString icon() const; virtual QStringList emblems() const; virtual QString description() const; virtual bool isValid() const; virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const; virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type); static QStringList generateUDIList(const Solid::DeviceInterface::Type &type); static bool exists(const QString &udi); const Solid::DeviceInterface::Type type() const; //TODO:rename the folowing methodes... static WmiQuery::Item win32LogicalDiskByDiskPartitionID(const QString &deviceID); static WmiQuery::Item win32DiskDriveByDiskPartitionID(const QString &deviceID); static WmiQuery::Item win32DiskPartitionByDeviceIndex(const QString &deviceID); static WmiQuery::Item win32DiskPartitionByDriveLetter(const QString &driveLetter); static WmiQuery::Item win32LogicalDiskByDriveLetter(const QString &driveLetter); Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); private Q_SLOTS: void slotPropertyModified(int count, const QList &changes); void slotCondition(const QString &condition, const QString &reason); private: WmiDevicePrivate *d; friend class WmiDevicePrivate; }; } } } #endif // SOLID_BACKENDS_WMI_WMIDEVICE_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmideviceinterface.cpp000066400000000000000000000022451316350454000256560ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmideviceinterface.h" using namespace Solid::Backends::Wmi; DeviceInterface::DeviceInterface(WmiDevice *device) : QObject(device), m_device(device) { } DeviceInterface::~DeviceInterface() { } //#include "backends/wmi/wmideviceinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmideviceinterface.h000066400000000000000000000127741316350454000253330ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_DEVICEINTERFACE_H #define SOLID_BACKENDS_WMI_DEVICEINTERFACE_H #include #include "wmidevice.h" #include #include namespace Solid { namespace Backends { namespace Wmi { class DeviceInterface : public QObject, virtual public Solid::Ifaces::DeviceInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::DeviceInterface) public: DeviceInterface(WmiDevice *device); virtual ~DeviceInterface(); protected: WmiDevice *m_device; public: inline static QStringList toStringList(Solid::DeviceInterface::Type type) { QStringList list; switch(type) { case Solid::DeviceInterface::GenericInterface: // Doesn't exist with WMI break; case Solid::DeviceInterface::Processor: list << "processor"; break; case Solid::DeviceInterface::Block: list << "block"; break; case Solid::DeviceInterface::StorageAccess: // Doesn't exist with WMI, but let's assume volume always cover this type list << "volume"; break; case Solid::DeviceInterface::StorageDrive: list << "storage"; break; case Solid::DeviceInterface::OpticalDrive: list << "storage.cdrom"; break; case Solid::DeviceInterface::StorageVolume: list << "volume"; break; case Solid::DeviceInterface::OpticalDisc: list << "volume.disc"; break; case Solid::DeviceInterface::Camera: list << "camera"; break; case Solid::DeviceInterface::PortableMediaPlayer: list << "portable_audio_player"; break; case Solid::DeviceInterface::NetworkInterface: list << "net"; break; case Solid::DeviceInterface::AcAdapter: list << "ac_adapter"; break; case Solid::DeviceInterface::Battery: list << "battery"; break; case Solid::DeviceInterface::Button: list << "button"; break; case Solid::DeviceInterface::AudioInterface: list << "alsa" << "oss"; break; case Solid::DeviceInterface::DvbInterface: list << "dvb"; break; case Solid::DeviceInterface::Video: list << "video4linux"; break; case Solid::DeviceInterface::InternetGateway: list << "internet_gateway"; break; case Solid::DeviceInterface::NetworkShare: list << "networkshare"; break; case Solid::DeviceInterface::Unknown: break; case Solid::DeviceInterface::Last: break; } return list; } inline static Solid::DeviceInterface::Type fromString(const QString &capability) { if (capability == "processor") return Solid::DeviceInterface::Processor; else if (capability == "block") return Solid::DeviceInterface::Block; else if (capability == "storage") return Solid::DeviceInterface::StorageDrive; else if (capability == "storage.cdrom") return Solid::DeviceInterface::OpticalDrive; else if (capability == "volume") return Solid::DeviceInterface::StorageVolume; else if (capability == "volume.disc") return Solid::DeviceInterface::OpticalDisc; else if (capability == "camera") return Solid::DeviceInterface::Camera; else if (capability == "portable_audio_player") return Solid::DeviceInterface::PortableMediaPlayer; else if (capability == "net") return Solid::DeviceInterface::NetworkInterface; else if (capability == "ac_adapter") return Solid::DeviceInterface::AcAdapter; else if (capability == "battery") return Solid::DeviceInterface::Battery; else if (capability == "button") return Solid::DeviceInterface::Button; else if (capability == "alsa" || capability == "oss") return Solid::DeviceInterface::AudioInterface; else if (capability == "dvb") return Solid::DeviceInterface::DvbInterface; else if (capability == "video4linux") return Solid::DeviceInterface::Video; else if (capability == "networkshare") return Solid::DeviceInterface::NetworkShare; else return Solid::DeviceInterface::Unknown; } }; } } } #endif // SOLID_BACKENDS_WMI_DEVICEINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmigenericinterface.cpp000066400000000000000000000033651316350454000260370ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmigenericinterface.h" #include "wmidevice.h" using namespace Solid::Backends::Wmi; GenericInterface::GenericInterface(WmiDevice *device) : DeviceInterface(device) { connect(device, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); connect(device, SIGNAL(conditionRaised(QString,QString)), this, SIGNAL(conditionRaised(QString,QString))); } GenericInterface::~GenericInterface() { } QVariant GenericInterface::property(const QString &key) const { return m_device->property(key); } QMap GenericInterface::allProperties() const { return m_device->allProperties(); } bool GenericInterface::propertyExists(const QString &key) const { return m_device->propertyExists(key); } //#include "backends/wmi/wmigenericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmigenericinterface.h000066400000000000000000000034631316350454000255030ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_GENERICINTERFACE_H #define SOLID_BACKENDS_WMI_GENERICINTERFACE_H #include #include #include "wmideviceinterface.h" namespace Solid { namespace Backends { namespace Wmi { class WmiDevice; class GenericInterface : public DeviceInterface, virtual public Solid::Ifaces::GenericInterface { Q_OBJECT Q_INTERFACES(Solid::Ifaces::GenericInterface) public: GenericInterface(WmiDevice *device); virtual ~GenericInterface(); virtual QVariant property(const QString &key) const; virtual QMap allProperties() const; virtual bool propertyExists(const QString &key) const; Q_SIGNALS: void propertyChanged(const QMap &changes); void conditionRaised(const QString &condition, const QString &reason); }; } } } #endif // SOLID_BACKENDS_WMI_GENERICINTERFACE_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmimanager.cpp000066400000000000000000000203601316350454000241460ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmimanager.h" #include #include "wmidevice.h" #include "wmideviceinterface.h" #include "wmiquery.h" using namespace Solid::Backends::Wmi; class Solid::Backends::Wmi::WmiManagerPrivate { public: WmiManagerPrivate(WmiManager *parent) :m_parent(parent) { supportedInterfaces << Solid::DeviceInterface::GenericInterface << Solid::DeviceInterface::Processor // << Solid::DeviceInterface::Block << Solid::DeviceInterface::StorageAccess << Solid::DeviceInterface::StorageDrive << Solid::DeviceInterface::OpticalDrive << Solid::DeviceInterface::StorageVolume << Solid::DeviceInterface::OpticalDisc // << Solid::DeviceInterface::Camera // << Solid::DeviceInterface::PortableMediaPlayer // << Solid::DeviceInterface::NetworkInterface << Solid::DeviceInterface::AcAdapter << Solid::DeviceInterface::Battery // << Solid::DeviceInterface::Button // << Solid::DeviceInterface::AudioInterface // << Solid::DeviceInterface::DvbInterface // << Solid::DeviceInterface::Video // << Solid::DeviceInterface::SerialInterface // << Solid::DeviceInterface::SmartCardReader ; update(); } ~WmiManagerPrivate() { } void update(){ init(); } void init(){ if(m_deviceCache.isEmpty()) { foreach(const Solid::DeviceInterface::Type &dev, supportedInterfaces){ updateDeviceCache(dev); } } } void updateDeviceCache(const Solid::DeviceInterface::Type & type){ QSet devSet = m_parent->findDeviceByDeviceInterface(type).toSet(); if(m_deviceCache.contains(type)){ QSet added = devSet - m_deviceCache[type]; foreach(const QString & s,added){ m_parent->slotDeviceAdded(s); } QSet removed = m_deviceCache[type] - devSet; foreach(const QString & s,removed){ m_parent->slotDeviceRemoved(s); } } m_deviceCache[type] = devSet; } WmiQuery::ItemList sendQuery( const QString &wql ) { return WmiQuery::instance().sendQuery( wql ); } WmiManager *m_parent; QSet supportedInterfaces; QMap > m_deviceCache; }; WmiManager::WmiManager(QObject *parent) : DeviceManager(parent) { d = new WmiManagerPrivate(this); QList types; types< WmiManager::supportedInterfaces() const { return d->supportedInterfaces; } QStringList WmiManager::allDevices() { QStringList list; foreach(const Solid::DeviceInterface::Type &type,d->supportedInterfaces){ list<m_deviceCache[type].toList(); } return list; } bool WmiManager::deviceExists(const QString &udi) { return WmiDevice::exists(udi); } QStringList WmiManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type) { QStringList result; if (!parentUdi.isEmpty()) { foreach(const QString &udi,allDevices()){ WmiDevice device(udi); if(device.type() == type && device.parentUdi() == parentUdi ){ result< &types): m_parent(parent), m_query(query), m_types(types), m_count(0) {} WmiManager::WmiEventSink::~WmiEventSink() {} ulong STDMETHODCALLTYPE WmiManager::WmiEventSink::AddRef() { return InterlockedIncrement(&m_count); } ulong STDMETHODCALLTYPE WmiManager::WmiEventSink::Release() { long lRef = InterlockedDecrement(&m_count); if(lRef == 0) delete this; return lRef; } HRESULT STDMETHODCALLTYPE WmiManager::WmiEventSink::QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IWbemObjectSink) { *ppv = (IWbemObjectSink *) this; AddRef(); return WBEM_S_NO_ERROR; } else return E_NOINTERFACE; } HRESULT STDMETHODCALLTYPE WmiManager::WmiEventSink::Indicate(long lObjectCount,IWbemClassObject **apObjArray) { foreach(const Solid::DeviceInterface::Type &type,m_types){ m_parent->d->updateDeviceCache(type); } return WBEM_S_NO_ERROR; } HRESULT STDMETHODCALLTYPE WmiManager::WmiEventSink::SetStatus(long lFlags,HRESULT hResult,BSTR strParam,IWbemClassObject *pObjParam) { return WBEM_S_NO_ERROR; } const QString& WmiManager::WmiEventSink::query() const { return m_query; } //#include "backends/wmi/wmimanager.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmimanager.h000066400000000000000000000057731316350454000236260ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2005,2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_WMIMANAGER_H #define SOLID_BACKENDS_WMI_WMIMANAGER_H #include #include #include #include #include namespace Solid { namespace Backends { namespace Wmi { class WmiManagerPrivate; class WmiManager : public Solid::Ifaces::DeviceManager { Q_OBJECT public: class WmiEventSink : public IWbemObjectSink { public: WmiEventSink(class WmiManager* parent,const QString &query,const QList &types); ~WmiEventSink(); virtual ulong STDMETHODCALLTYPE AddRef(); virtual ulong STDMETHODCALLTYPE Release(); virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv); virtual HRESULT STDMETHODCALLTYPE Indicate(long lObjectCount,IWbemClassObject **apObjArray); virtual HRESULT STDMETHODCALLTYPE SetStatus(long lFlags,HRESULT hResult,BSTR strParam,IWbemClassObject *pObjParam); const QString& query() const; private: WmiManager *m_parent; QString m_query; QList m_types; long m_count; }; WmiManager(QObject *parent=0); virtual ~WmiManager(); virtual QString udiPrefix() const ; virtual QSet supportedInterfaces() const; virtual QStringList allDevices(); virtual bool deviceExists(const QString &udi); virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type); virtual QObject *createDevice(const QString &udi); private Q_SLOTS: void slotDeviceAdded(const QString &udi); void slotDeviceRemoved(const QString &udi); private: QStringList findDeviceStringMatch(const QString &key, const QString &value); QStringList findDeviceByDeviceInterface(Solid::DeviceInterface::Type type); WmiManagerPrivate *d; friend class WmiManagerPrivate; }; } } } #endif // SOLID_BACKENDS_WMI_WMIMANAGER_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiopticaldisc.cpp000066400000000000000000000056111316350454000250340ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmiopticaldisc.h" #include using namespace Solid::Backends::Wmi; OpticalDisc::OpticalDisc(WmiDevice *device) : Volume(device) { m_logicalDisk = WmiDevice::win32LogicalDiskByDriveLetter(m_device->property("Drive").toString()); } OpticalDisc::~OpticalDisc() { } Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const { Solid::OpticalDisc::ContentTypes content; QDir dir(m_device->property("Drive").toString()); QStringList files = dir.entryList(); if(files.length()>0) if(files[0].endsWith(".cda")) content |= Solid::OpticalDisc::Audio; return content; } Solid::OpticalDisc::DiscType OpticalDisc::discType() const { QString type = m_logicalDisk.getProperty("FileSystem").toString(); if (type == "CDFS") { return Solid::OpticalDisc::CdRom; } // else if (type == "CdRomWrite") // { // return Solid::OpticalDisc::CdRecordable; // } else if (type == "UDF") { return Solid::OpticalDisc::DvdRom; } // else if (type == "DVDRomWrite") // { // return Solid::OpticalDisc::DvdRecordable; // } else { qDebug()<<"Solid::OpticalDisc::DiscType OpticalDisc::discType(): Unknown Type"<property("FileSystemFlagsEx").toUInt(); if(val == 0) return true; return false; } bool OpticalDisc::isRewritable() const { //TODO: return capacity()>0 && isWriteable(); } qulonglong OpticalDisc::capacity() const { return m_device->property("Size").toULongLong(); } bool OpticalDisc::isWriteable() const { ushort val = m_device->property("FileSystemFlagsEx").toUInt(); if(val == 0) return true; return !val & 0x80001;//read only } #include "backends/wmi/wmiopticaldisc.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiopticaldisc.h000066400000000000000000000034141316350454000245000ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_OPTICALDISC_H #define SOLID_BACKENDS_WMI_OPTICALDISC_H #include #include "wmivolume.h" namespace Solid { namespace Backends { namespace Wmi { class OpticalDisc : public Volume, virtual public Solid::Ifaces::OpticalDisc { Q_OBJECT Q_INTERFACES(Solid::Ifaces::OpticalDisc) public: OpticalDisc(WmiDevice *device); virtual ~OpticalDisc(); virtual Solid::OpticalDisc::ContentTypes availableContent() const; virtual Solid::OpticalDisc::DiscType discType() const; virtual bool isAppendable() const; virtual bool isBlank() const; virtual bool isRewritable() const; virtual qulonglong capacity() const; private: bool isWriteable() const; WmiQuery::Item m_logicalDisk; }; } } } #endif // SOLID_BACKENDS_WMI_OPTICALDISC_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiportablemediaplayer.cpp000066400000000000000000000042541316350454000265650ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmiportablemediaplayer.h" using namespace Solid::Backends::Wmi; PortableMediaPlayer::PortableMediaPlayer(WmiDevice *device) : DeviceInterface(device) { } PortableMediaPlayer::~PortableMediaPlayer() { } QStringList PortableMediaPlayer::supportedProtocols() const { return m_device->property("portable_audio_player.access_method.protocols").toStringList(); } QStringList PortableMediaPlayer::supportedDrivers(QString protocol) const { QStringList drivers = m_device->property("portable_audio_player.access_method.drivers").toStringList(); if(protocol.isNull()) return drivers; QStringList returnedDrivers; QString temp; for(int i = 0; i < drivers.size(); i++) { temp = drivers.at(i); if(m_device->property("portable_audio_player." + temp + ".protocol") == protocol) returnedDrivers << temp; } return returnedDrivers; } QVariant Solid::Backends::Wmi::PortableMediaPlayer::driverHandle(const QString &driver) const { if (driver=="mtp") { return m_device->property("usb.serial"); } // TODO: Fill in the blank for other drivers return QVariant(); } //#include "backends/wmi/wmiportablemediaplayer.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiportablemediaplayer.h000066400000000000000000000034071316350454000262310ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_PORTABLEMEDIAPLAYER_H #define SOLID_BACKENDS_WMI_PORTABLEMEDIAPLAYER_H #include #include "wmideviceinterface.h" #include namespace Solid { namespace Backends { namespace Wmi { class WmiDevice; class PortableMediaPlayer : public DeviceInterface, virtual public Solid::Ifaces::PortableMediaPlayer { Q_OBJECT Q_INTERFACES(Solid::Ifaces::PortableMediaPlayer) public: PortableMediaPlayer(WmiDevice *device); virtual ~PortableMediaPlayer(); virtual QStringList supportedProtocols() const; virtual QStringList supportedDrivers(QString protocol = QString()) const; virtual QVariant driverHandle(const QString &driver) const; }; } } } #endif // SOLID_BACKENDS_WMI_PORTABLEMEDIAPLAYER_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmiquery.cpp000066400000000000000000000250451316350454000237060ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2008 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ //#define _WIN32_DCOM //#include #define INSIDE_WMIQUERY #include "wmiquery.h" #include "wmimanager.h" #ifdef _DEBUG # pragma comment(lib, "comsuppwd.lib") #else # pragma comment(lib, "comsuppw.lib") #endif # pragma comment(lib, "wbemuuid.lib") #include #include #include # pragma comment(lib, "wbemuuid.lib") #include #include #include #include #include //needed for mingw inline OLECHAR* SysAllocString(const QString &s){ const OLECHAR *olename = reinterpret_cast(s.utf16()); return ::SysAllocString(olename); } using namespace Solid::Backends::Wmi; QString bstrToQString(BSTR bstr) { QString str((QChar*)bstr,::SysStringLen(bstr)); return str; } QVariant WmiQuery::Item::msVariantToQVariant(VARIANT msVariant, CIMTYPE variantType) { QVariant returnVariant(QVariant::Invalid); if(msVariant.vt == VT_NULL){ return QVariant(QVariant::String); } switch(variantType) { case CIM_STRING: case CIM_CHAR16: case CIM_UINT64://bug I get a wrong value from ullVal { QString str = bstrToQString(msVariant.bstrVal); QVariant vs(str); returnVariant = vs; } break; case CIM_BOOLEAN: { QVariant vb(msVariant.boolVal); returnVariant = vb; } break; case CIM_UINT8: { QVariant vb(msVariant.uintVal); returnVariant = vb; } break; case CIM_UINT16: { QVariant vb(msVariant.uintVal); returnVariant = vb; } break; case CIM_UINT32: { QVariant vb(msVariant.uintVal); returnVariant = vb; } break; // case CIM_UINT64: // { // QVariant vb(msVariant.ullVal); //// wprintf(L"ulonglong %d %I64u\r\n",variantType, msVariant.ullVal); // 32-bit on x86, 64-bit on x64 // returnVariant = vb; // } // break; // default: // qDebug()<<"Unsupported variant"<ConnectServer. Please DO NOT USE the following or similar statement in the global space or a class. static WmiQuery instance; */ QVariant WmiQuery::Item::getProperty(BSTR bstrProp) const { QVariant result; if(m_p == NULL) return result; VARIANT vtProp; CIMTYPE variantType; HRESULT hr = m_p->Get(bstrProp, 0, &vtProp, &variantType, 0); if (SUCCEEDED(hr)) { result = msVariantToQVariant(vtProp,variantType); }else{ QString className = getProperty(L"__CLASS").toString(); QString qprop = bstrToQString(bstrProp); qFatal("\r\n--------------------------------------------------------------\r\n" "Error: Property: %s not found in %s\r\n" "--------------------------------------------------------------\r\n",qPrintable(qprop),qPrintable(className)); } return result; } QVariant WmiQuery::Item::getProperty(const QString &property) const { QVariant result; BSTR bstrProp; bstrProp = ::SysAllocString(property); result = getProperty(bstrProp); ::SysFreeString(bstrProp); return result; } QVariantMap WmiQuery::Item::getAllProperties() { if(m_properies.isEmpty()){ SAFEARRAY *psaNames; HRESULT hr = m_p->GetNames(NULL, WBEM_FLAG_ALWAYS | WBEM_FLAG_NONSYSTEM_ONLY,NULL,&psaNames); if (SUCCEEDED(hr)) { long lLower, lUpper; SafeArrayGetLBound( psaNames, 1, &lLower ); SafeArrayGetUBound( psaNames, 1, &lUpper ); for(long i=lLower;iAddRef(); } WmiQuery::Item::Item(const Item& other) : m_p(other.m_p) { if(m_p != NULL) m_p->AddRef(); } WmiQuery::Item& WmiQuery::Item::operator=(const Item& other) { if(m_p != NULL){ m_p->Release(); m_p = NULL; } if(other.m_p != NULL){ m_p = other.m_p; m_p->AddRef(); } return *this; } WmiQuery::Item::~Item() { if(m_p != NULL && !(qApp->closingDown() || WmiQuery::instance().m_bNeedUninit))//this means we are in a QApplication, so qt already called CoUninitialize and all COM references are all ready freed m_p->Release(); } IWbemClassObject* WmiQuery::Item::data() const { if(m_p != NULL) m_p->AddRef(); return m_p; } bool WmiQuery::Item::isNull() const { return m_p == NULL; } WmiQuery::WmiQuery() : m_failed(false) , pLoc(0) , pSvc(0) { HRESULT hres; hres = CoInitializeEx(0,COINIT_MULTITHREADED); if( FAILED(hres) && hres != S_FALSE && hres != RPC_E_CHANGED_MODE ) { qCritical() << "Failed to initialize COM library. Error code = 0x" << hex << quint32(hres) << endl; m_failed = true; } m_bNeedUninit = ( hres != S_FALSE && hres != RPC_E_CHANGED_MODE ); if( !m_failed ) { hres = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL ); // RPC_E_TOO_LATE --> security already initialized if( FAILED(hres) && hres != RPC_E_TOO_LATE ) { qCritical() << "Failed to initialize security. " << "Error code = " << hres << endl; if ( m_bNeedUninit ) CoUninitialize(); m_failed = true; } } if( !m_failed ) { hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc ); if (FAILED(hres)) { qCritical() << "Failed to create IWbemLocator object. " << "Error code = " << hres << endl; if ( m_bNeedUninit ) CoUninitialize(); m_failed = true; } } if( !m_failed ) { hres = pLoc->ConnectServer( L"ROOT\\CIMV2", NULL, NULL, 0, NULL, 0, 0, &pSvc ); if( FAILED(hres) ) { qCritical() << "Could not connect. Error code = " << hres << endl; pLoc->Release(); if ( m_bNeedUninit ) CoUninitialize(); m_failed = true; } // else // qDebug() << "Connected to ROOT\\CIMV2 WMI namespace" << endl; } if( !m_failed ) { hres = CoSetProxyBlanket( pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); if( FAILED(hres) ) { qCritical() << "Could not set proxy blanket. Error code = " << hres << endl; pSvc->Release(); pLoc->Release(); if ( m_bNeedUninit ) CoUninitialize(); m_failed = true; } } } WmiQuery::~WmiQuery() { if( m_failed ) return; // already cleaned up properly if( pSvc ) pSvc->Release(); if( pLoc ) pLoc->Release(); if( m_bNeedUninit ) CoUninitialize(); } void WmiQuery::addDeviceListeners(WmiManager::WmiEventSink *sink){ if(m_failed) return; BSTR bstrQuery; bstrQuery = ::SysAllocString(sink->query()); HRESULT hr = pSvc->ExecNotificationQueryAsync(L"WQL",bstrQuery,0, NULL,sink); ::SysFreeString(bstrQuery); if(FAILED(hr)){ qWarning()<<"WmiQuery::addDeviceListeners "<query()<<" failed!"; } } WmiQuery::ItemList WmiQuery::sendQuery( const QString &wql ) { // qDebug()<<"WmiQuery::ItemList WmiQuery::sendQuery"<ExecQuery( L"WQL",bstrQuery , WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); ::SysFreeString(bstrQuery); if( FAILED(hres) ) { qDebug() << "Query with string \"" << wql << "\" failed. Error code = " << hres << endl; } else { ULONG uReturn = 0; while( pEnumerator ) { IWbemClassObject *pclsObj; hres = pEnumerator->Next( WBEM_INFINITE, 1, &pclsObj, &uReturn ); if( !uReturn ) break; // pclsObj will be released on destruction of Item retList.append( Item( pclsObj ) ); pclsObj->Release(); } if( pEnumerator ) pEnumerator->Release(); else qDebug() << "failed to release enumerator!"; } // if(retList.size()== 0) // qDebug()<<"querying"< Copyright 2008 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_WMIQUERY_H #define SOLID_BACKENDS_WMI_WMIQUERY_H #include #include #include #include #include #include #include #include #include #include #include #include "wmimanager.h" namespace Solid { namespace Backends { namespace Wmi { class WmiQuery { public: class Item { public: Item(); Item(IWbemClassObject *p); Item(const Item& other); Item& operator=(const Item& other); ~Item(); IWbemClassObject* data() const; bool isNull() const; QVariant getProperty(const QString &property) const; QVariantMap getAllProperties(); private: static QVariant msVariantToQVariant(VARIANT msVariant, CIMTYPE variantType); QVariant getProperty(BSTR property) const; // QSharedPointer alone doesn't help because we need to call Release() IWbemClassObject* m_p; QVariantMap m_properies; }; typedef QList ItemList; WmiQuery(); ~WmiQuery(); ItemList sendQuery( const QString &wql ); void addDeviceListeners(WmiManager::WmiEventSink *sink); bool isLegit() const; static WmiQuery &instance(); private: bool m_failed; bool m_bNeedUninit; IWbemLocator *pLoc; IWbemServices *pSvc; }; } } } #endif cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmistorage.cpp000066400000000000000000000062151316350454000242030ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmistorage.h" #include "wmiquery.h" using namespace Solid::Backends::Wmi; Storage::Storage(WmiDevice *device) : Block(device) { if(m_device->type() == Solid::DeviceInterface::StorageDrive) { WmiQuery::Item item = WmiDevice::win32DiskPartitionByDeviceIndex(m_device->property("DeviceID").toString()); QString id = item.getProperty("DeviceID").toString(); m_logicalDisk = WmiDevice::win32LogicalDiskByDiskPartitionID(id); }else if(m_device->type() == Solid::DeviceInterface::OpticalDrive) { QString id = m_device->property("Drive").toString(); m_logicalDisk = WmiDevice::win32LogicalDiskByDriveLetter(id); } } Storage::~Storage() { } Solid::StorageDrive::Bus Storage::bus() const { if(m_device->type() == Solid::DeviceInterface::OpticalDrive) return Solid::StorageDrive::Platform; QString bus = m_device->property("InterfaceType").toString().toLower(); if (bus=="ide") { return Solid::StorageDrive::Ide; } else if (bus=="usb") { return Solid::StorageDrive::Usb; } else if (bus=="1394") { return Solid::StorageDrive::Ieee1394; } else if (bus=="scsi") { return Solid::StorageDrive::Scsi; } // else if (bus=="sata")//not availible http://msdn.microsoft.com/en-us/library/windows/desktop/aa394132(v=vs.85).aspx // { // return Solid::StorageDrive::Sata; // } else { return Solid::StorageDrive::Platform; } } Solid::StorageDrive::DriveType Storage::driveType() const { ushort type = m_logicalDisk.getProperty("DriveType").toUInt(); switch(type){ case 2: return Solid::StorageDrive::MemoryStick; case 3: return Solid::StorageDrive::HardDisk; case 5: return Solid::StorageDrive::CdromDrive; default: return Solid::StorageDrive::HardDisk; } } bool Storage::isRemovable() const { return driveType() != Solid::StorageDrive::HardDisk; } bool Storage::isHotpluggable() const { return bus() == Solid::StorageDrive::Usb; } qulonglong Storage::size() const { return m_device->property("Size").toULongLong(); } //#include "backends/wmi/wmistorage.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmistorage.h000066400000000000000000000032741316350454000236520ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_STORAGE_H #define SOLID_BACKENDS_WMI_STORAGE_H #include #include "wmiblock.h" #include "wmiquery.h" namespace Solid { namespace Backends { namespace Wmi { class Storage : public Block, virtual public Solid::Ifaces::StorageDrive { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageDrive) public: Storage(WmiDevice *device); virtual ~Storage(); virtual Solid::StorageDrive::Bus bus() const; virtual Solid::StorageDrive::DriveType driveType() const; virtual bool isRemovable() const; virtual bool isHotpluggable() const; virtual qulonglong size() const; private: WmiQuery::Item m_logicalDisk; }; } } } #endif // SOLID_BACKENDS_WMI_STORAGE_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmistorageaccess.cpp000066400000000000000000000146321316350454000253670ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmistorageaccess.h" #include #include #include #include using namespace Solid::Backends::Wmi; StorageAccess::StorageAccess(WmiDevice *device) : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false) { connect(device, SIGNAL(propertyChanged(QMap)), this, SLOT(slotPropertyChanged(QMap))); // qDebug()<<"StorageAccess"<type(); m_logicalDisk = WmiDevice::win32LogicalDiskByDiskPartitionID(m_device->property("DeviceID").toString()); } StorageAccess::~StorageAccess() { } bool StorageAccess::isAccessible() const { return !m_logicalDisk.isNull(); } QString StorageAccess::filePath() const { QString path = m_logicalDisk.getProperty("DeviceID").toString(); if(!path.isNull()) path.append("\\"); return path; } bool Solid::Backends::Wmi::StorageAccess::isIgnored() const { return m_logicalDisk.isNull(); } bool StorageAccess::setup() { if (m_teardownInProgress || m_setupInProgress) { return false; } m_setupInProgress = true; // if (m_device->property("info.interfaces").toStringList().contains("org.freedesktop.Wmi.Device.Volume.Crypto")) { // return requestPassphrase(); // } else if (FstabHandling::isInFstab(m_device->property("block.device").toString())) { // return callSystemMount(); // } else { // return callWmiVolumeMount(); // } return false; } bool StorageAccess::teardown() { if (m_teardownInProgress || m_setupInProgress) { return false; } m_teardownInProgress = true; // if (m_device->property("info.interfaces").toStringList().contains("org.freedesktop.Wmi.Device.Volume.Crypto")) { // return callCryptoTeardown(); // } else if (FstabHandling::isInFstab(m_device->property("block.device").toString())) { // return callSystemUnmount(); // } else { // return callWmiVolumeUnmount(); // } return false; } void StorageAccess::slotPropertyChanged(const QMap &changes) { if (changes.contains("volume.is_mounted")) { emit accessibilityChanged(isAccessible(), m_device->udi()); } } void Solid::Backends::Wmi::StorageAccess::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { /* Q_UNUSED(exitStatus); if (m_setupInProgress) { m_setupInProgress = false; if (exitCode==0) { emit setupDone(Solid::NoError, QVariant(), m_device->udi()); } else { emit setupDone(Solid::UnauthorizedOperation, m_process->readAllStandardError(), m_device->udi()); } } else if (m_teardownInProgress) { m_teardownInProgress = false; if (exitCode==0) { emit teardownDone(Solid::NoError, QVariant(), m_device->udi()); } else { emit teardownDone(Solid::UnauthorizedOperation, m_process->readAllStandardError(), m_device->udi()); } } delete m_process; */ } QString generateReturnObjectPath() { static int number = 1; return "/org/kde/solid/WmiStorageAccess_"+QString::number(number++); } bool StorageAccess::callWmiVolumeMount() { // QDBusConnection c = QDBusConnection::systemBus(); // QString udi = m_device->udi(); // QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Wmi", udi, // "org.freedesktop.Wmi.Device.Volume", // "Mount"); // QStringList options; // QStringList wmiOptions = m_device->property("volume.mount.valid_options").toStringList(); // if (wmiOptions.contains("uid=")) { // options << "uid="+QString::number(::getuid()); // } // msg << "" << "" << options; // return c.callWithCallback(msg, this, // SLOT(slotDBusReply(QDBusMessage)), // SLOT(slotDBusError(QDBusError))); return false; } bool StorageAccess::callWmiVolumeUnmount() { // QDBusConnection c = QDBusConnection::systemBus(); // QString udi = m_device->udi(); // QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Wmi", udi, // "org.freedesktop.Wmi.Device.Volume", // "Unmount"); // msg << QStringList(); // return c.callWithCallback(msg, this, // SLOT(slotDBusReply(QDBusMessage)), // SLOT(slotDBusError(QDBusError))); return false; } bool Solid::Backends::Wmi::StorageAccess::callSystemMount() { /* const QString device = m_device->property("block.device").toString(); m_process = FstabHandling::callSystemCommand("mount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=0; */ return 0; } bool Solid::Backends::Wmi::StorageAccess::callSystemUnmount() { /* const QString device = m_device->property("block.device").toString(); m_process = FstabHandling::callSystemCommand("umount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=0; */ return 0; } //#include "backends/wmi/wmistorageaccess.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmistorageaccess.h000066400000000000000000000047121316350454000250320ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_STORAGEACCESS_H #define SOLID_BACKENDS_WMI_STORAGEACCESS_H #include #include "wmideviceinterface.h" #include namespace Solid { namespace Backends { namespace Wmi { class StorageAccess : public DeviceInterface, virtual public Solid::Ifaces::StorageAccess { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageAccess) public: StorageAccess(WmiDevice *device); virtual ~StorageAccess(); virtual bool isAccessible() const; virtual QString filePath() const; virtual bool isIgnored() const; virtual bool setup(); virtual bool teardown(); Q_SIGNALS: void accessibilityChanged(bool accessible, const QString &udi); void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void setupRequested(const QString &udi); void teardownRequested(const QString &udi); private Q_SLOTS: void slotPropertyChanged(const QMap &changes); void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: bool callWmiVolumeMount(); bool callWmiVolumeUnmount(); bool callSystemMount(); bool callSystemUnmount(); private: bool m_setupInProgress; bool m_teardownInProgress; bool m_passphraseRequested; QString m_lastReturnObject; QProcess *m_process; WmiQuery::Item m_logicalDisk; }; } } } #endif // SOLID_BACKENDS_WMI_STORAGEACCESS_H cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmivolume.cpp000066400000000000000000000042571316350454000240520ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "wmivolume.h" using namespace Solid::Backends::Wmi; Volume::Volume(WmiDevice *device) : Block(device) { if(m_device->type() == Solid::DeviceInterface::StorageVolume) { m_logicalDisk = WmiDevice::win32LogicalDiskByDiskPartitionID(m_device->property("DeviceID").toString()); }else if(m_device->type() == Solid::DeviceInterface::OpticalDisc) { m_logicalDisk = WmiDevice::win32LogicalDiskByDriveLetter(m_device->property("Drive").toString()); } } Volume::~Volume() { } bool Volume::isIgnored() const { return m_logicalDisk.isNull(); } Solid::StorageVolume::UsageType Volume::usage() const { return Solid::StorageVolume::FileSystem;//TODO:??? } QString Volume::fsType() const { return m_logicalDisk.getProperty("FileSystem").toString(); } QString Volume::label() const { return m_logicalDisk.getProperty("VolumeName").toString(); } QString Volume::uuid() const { return m_logicalDisk.getProperty("VolumeSerialNumber").toString(); } qulonglong Volume::size() const { return m_device->property("Size").toULongLong(); } QString Solid::Backends::Wmi::Volume::encryptedContainerUdi() const { return this->uuid(); } //#include "backends/wmi/wmivolume.moc" cantata-2.2.0/3rdparty/solid-lite/backends/wmi/wmivolume.h000066400000000000000000000033371316350454000235150ustar00rootroot00000000000000/* Copyright 2012 Patrick von Reth Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BACKENDS_WMI_VOLUME_H #define SOLID_BACKENDS_WMI_VOLUME_H #include #include "wmiblock.h" namespace Solid { namespace Backends { namespace Wmi { class Volume : public Block, virtual public Solid::Ifaces::StorageVolume { Q_OBJECT Q_INTERFACES(Solid::Ifaces::StorageVolume) public: Volume(WmiDevice *device); virtual ~Volume(); virtual bool isIgnored() const; virtual Solid::StorageVolume::UsageType usage() const; virtual QString fsType() const; virtual QString label() const; virtual QString uuid() const; virtual qulonglong size() const; virtual QString encryptedContainerUdi() const; private: WmiQuery::Item m_logicalDisk; }; } } } #endif // SOLID_BACKENDS_WMI_VOLUME_H cantata-2.2.0/3rdparty/solid-lite/block.cpp000066400000000000000000000031351316350454000205440ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "block.h" #include "block_p.h" #include "soliddefs_p.h" #include Solid::Block::Block(QObject *backendObject) : DeviceInterface(*new BlockPrivate(), backendObject) { } Solid::Block::~Block() { } int Solid::Block::deviceMajor() const { Q_D(const Block); return_SOLID_CALL(Ifaces::Block *, d->backendObject(), 0, deviceMajor()); } int Solid::Block::deviceMinor() const { Q_D(const Block); return_SOLID_CALL(Ifaces::Block *, d->backendObject(), 0, deviceMinor()); } QString Solid::Block::device() const { Q_D(const Block); return_SOLID_CALL(Ifaces::Block *, d->backendObject(), QString(), device()); } //#include "block.moc" cantata-2.2.0/3rdparty/solid-lite/block.h000066400000000000000000000060441316350454000202130ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BLOCK_H #define SOLID_BLOCK_H #include #include namespace Solid { class BlockPrivate; class Device; /** * This device interface is available on block devices. * * A block device is an adressable device such as drive or partition. * It is possible to interact with such a device using a special file * in the system. */ class SOLID_EXPORT Block : public DeviceInterface { Q_OBJECT Q_PROPERTY(int major READ deviceMajor) Q_PROPERTY(int minor READ deviceMinor) Q_PROPERTY(QString device READ device) Q_DECLARE_PRIVATE(Block) friend class Device; private: /** * Creates a new Block object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit Block(QObject *backendObject); public: /** * Destroys a Block object. */ virtual ~Block(); /** * Get the Solid::DeviceInterface::Type of the Block device interface. * * @return the Block device interface type * @see Solid::Ifaces::Enums::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::Block; } /** * Retrieves the major number of the node file to interact with * the device. * * @return the device major number */ int deviceMajor() const; /** * Retrieves the minor number of the node file to interact with * the device. * * @return the device minor number */ int deviceMinor() const; /** * Retrieves the absolute path of the special file to interact * with the device. * * @return the absolute path of the special file to interact with * the device */ QString device() const; }; } #endif cantata-2.2.0/3rdparty/solid-lite/block_p.h000066400000000000000000000022461316350454000205320ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_BLOCK_P_H #define SOLID_BLOCK_P_H #include "deviceinterface_p.h" namespace Solid { class BlockPrivate : public DeviceInterfacePrivate { public: BlockPrivate() : DeviceInterfacePrivate() { } }; } #endif cantata-2.2.0/3rdparty/solid-lite/config-solid.h.cmake000066400000000000000000000017531316350454000225570ustar00rootroot00000000000000/* Copyright 2010 Paulo Romulo Alves Barros This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #cmakedefine UDEV_FOUND #cmakedefine HUPNP_FOUND cantata-2.2.0/3rdparty/solid-lite/device.cpp000066400000000000000000000246611316350454000207200ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "device.h" #include "device_p.h" #include "devicenotifier.h" #include "devicemanager_p.h" #include "deviceinterface_p.h" #include "soliddefs_p.h" #include #include #include //#include //#include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include #include #include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include //#include Solid::Device::Device(const QString &udi) { DeviceManagerPrivate *manager = static_cast(Solid::DeviceNotifier::instance()); d = manager->findRegisteredDevice(udi); } Solid::Device::Device(const Device &device) : d(device.d) { } Solid::Device::~Device() { } Solid::Device &Solid::Device::operator=(const Solid::Device &device) { d = device.d; return *this; } bool Solid::Device::isValid() const { return d->backendObject()!=0; } QString Solid::Device::udi() const { return d->udi(); } QString Solid::Device::parentUdi() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QString(), parentUdi()); } Solid::Device Solid::Device::parent() const { QString udi = parentUdi(); if (udi.isEmpty()) { return Device(); } else { return Device(udi); } } QString Solid::Device::vendor() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QString(), vendor()); } QString Solid::Device::product() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QString(), product()); } QString Solid::Device::icon() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QString(), icon()); } QStringList Solid::Device::emblems() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QStringList(), emblems()); } QString Solid::Device::description() const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), QString(), description()); } bool Solid::Device::isDeviceInterface(const DeviceInterface::Type &type) const { return_SOLID_CALL(Ifaces::Device *, d->backendObject(), false, queryDeviceInterface(type)); } #define deviceinterface_cast(IfaceType, DevType, backendObject) \ (qobject_cast(backendObject) ? new DevType(backendObject) : 0) Solid::DeviceInterface *Solid::Device::asDeviceInterface(const DeviceInterface::Type &type) { const Solid::DeviceInterface *interface = const_cast(this)->asDeviceInterface(type); return const_cast(interface); } const Solid::DeviceInterface *Solid::Device::asDeviceInterface(const DeviceInterface::Type &type) const { Ifaces::Device *device = qobject_cast(d->backendObject()); if (device!=0) { DeviceInterface *iface = d->interface(type); if (iface!=0) { return iface; } QObject *dev_iface = device->createDeviceInterface(type); if (dev_iface!=0) { switch (type) { case DeviceInterface::GenericInterface: iface = deviceinterface_cast(Ifaces::GenericInterface, GenericInterface, dev_iface); break; //case DeviceInterface::Processor: // iface = deviceinterface_cast(Ifaces::Processor, Processor, dev_iface); // break; case DeviceInterface::Block: iface = deviceinterface_cast(Ifaces::Block, Block, dev_iface); break; case DeviceInterface::StorageAccess: iface = deviceinterface_cast(Ifaces::StorageAccess, StorageAccess, dev_iface); break; case DeviceInterface::StorageDrive: iface = deviceinterface_cast(Ifaces::StorageDrive, StorageDrive, dev_iface); break; case DeviceInterface::OpticalDrive: iface = deviceinterface_cast(Ifaces::OpticalDrive, OpticalDrive, dev_iface); break; case DeviceInterface::StorageVolume: iface = deviceinterface_cast(Ifaces::StorageVolume, StorageVolume, dev_iface); break; case DeviceInterface::OpticalDisc: iface = deviceinterface_cast(Ifaces::OpticalDisc, OpticalDisc, dev_iface); break; //case DeviceInterface::Camera: // iface = deviceinterface_cast(Ifaces::Camera, Camera, dev_iface); // break; case DeviceInterface::PortableMediaPlayer: iface = deviceinterface_cast(Ifaces::PortableMediaPlayer, PortableMediaPlayer, dev_iface); break; /*case DeviceInterface::NetworkInterface: iface = deviceinterface_cast(Ifaces::NetworkInterface, NetworkInterface, dev_iface); break; case DeviceInterface::AcAdapter: iface = deviceinterface_cast(Ifaces::AcAdapter, AcAdapter, dev_iface); break; case DeviceInterface::Battery: iface = deviceinterface_cast(Ifaces::Battery, Battery, dev_iface); break; case DeviceInterface::Button: iface = deviceinterface_cast(Ifaces::Button, Button, dev_iface); break; case DeviceInterface::AudioInterface: iface = deviceinterface_cast(Ifaces::AudioInterface, AudioInterface, dev_iface); break; case DeviceInterface::DvbInterface: iface = deviceinterface_cast(Ifaces::DvbInterface, DvbInterface, dev_iface); break; case DeviceInterface::Video: iface = deviceinterface_cast(Ifaces::Video, Video, dev_iface); break; case DeviceInterface::SerialInterface: iface = deviceinterface_cast(Ifaces::SerialInterface, SerialInterface, dev_iface); break; case DeviceInterface::SmartCardReader: iface = deviceinterface_cast(Ifaces::SmartCardReader, SmartCardReader, dev_iface); break; case DeviceInterface::InternetGateway: iface = deviceinterface_cast(Ifaces::InternetGateway, InternetGateway, dev_iface); break; case DeviceInterface::NetworkShare: iface = deviceinterface_cast(Ifaces::NetworkShare, NetworkShare, dev_iface); break; */ case DeviceInterface::Unknown: case DeviceInterface::Last: break; } } if (iface!=0) { // Lie on the constness since we're simply doing caching here const_cast(this)->d->setInterface(type, iface); iface->d_ptr->setDevicePrivate(d.data()); } return iface; } else { return 0; } } ////////////////////////////////////////////////////////////////////// Solid::DevicePrivate::DevicePrivate(const QString &udi) : QObject(), QSharedData(), m_udi(udi) { } Solid::DevicePrivate::~DevicePrivate() { foreach (DeviceInterface *iface, m_ifaces) { delete iface->d_ptr->backendObject(); } setBackendObject(0); } void Solid::DevicePrivate::_k_destroyed(QObject *object) { Q_UNUSED(object); setBackendObject(0); } void Solid::DevicePrivate::setBackendObject(Ifaces::Device *object) { if (m_backendObject) { m_backendObject.data()->disconnect(this); } delete m_backendObject.data(); m_backendObject = object; if (object) { connect(object, SIGNAL(destroyed(QObject*)), this, SLOT(_k_destroyed(QObject*))); } if (!m_ifaces.isEmpty()) { foreach (DeviceInterface *iface, m_ifaces) { delete iface; } m_ifaces.clear(); if (!ref.deref()) deleteLater(); } } Solid::DeviceInterface *Solid::DevicePrivate::interface(const DeviceInterface::Type &type) const { return m_ifaces[type]; } void Solid::DevicePrivate::setInterface(const DeviceInterface::Type &type, DeviceInterface *interface) { if(m_ifaces.isEmpty()) ref.ref(); m_ifaces[type] = interface; } // #include "device_p.moc" cantata-2.2.0/3rdparty/solid-lite/device.h000066400000000000000000000217761316350454000203710ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICE_H #define SOLID_DEVICE_H #include #include #include #include #include #include namespace Solid { class DevicePrivate; /** * This class allows applications to deal with devices available in the * underlying system. * * Device stores a reference to device data provided by the backend. * Device objects are designed to be used by value. Copying these objects * is quite cheap, so using pointers to the me is generally not needed. * * @author Kevin Ottens */ class SOLID_EXPORT Device { public: /** * Retrieves all the devices available in the underlying system. * * @return the list of the devices available */ static QList allDevices(); /** * Retrieves a list of devices of the system given matching the given * constraints (parent and device interface type) * * @param type device interface type available on the devices we're looking for, or DeviceInterface::Unknown * if there's no constraint on the device interfaces * @param parentUdi UDI of the parent of the devices we're searching for, or QString() * if there's no constraint on the parent * @return the list of devices corresponding to the given constraints * @see Solid::Predicate */ static QList listFromType(const DeviceInterface::Type &type, const QString &parentUdi = QString()); /** * Retrieves a list of devices of the system given matching the given * constraints (parent and predicate) * * @param predicate Predicate that the devices we're searching for must verify * @param parentUdi UDI of the parent of the devices we're searching for, or QString() * if there's no constraint on the parent * @return the list of devices corresponding to the given constraints * @see Solid::Predicate */ static QList listFromQuery(const Predicate &predicate, const QString &parentUdi = QString()); /** * Convenience function see above. * * @param predicate * @param parentUdi * @return the list of devices */ static QList listFromQuery(const QString &predicate, const QString &parentUdi = QString()); /** * Constructs a device for a given Universal Device Identifier (UDI). * * @param udi the udi of the device to create */ explicit Device(const QString &udi = QString()); /** * Constructs a copy of a device. * * @param device the device to copy */ Device(const Device &device); /** * Destroys the device. */ ~Device(); /** * Assigns a device to this device and returns a reference to it. * * @param device the device to assign * @return a reference to the device */ Device &operator=(const Device &device); /** * Indicates if this device is valid. * A device is considered valid if it's available in the system. * * @return true if this device is available, false otherwise */ bool isValid() const; /** * Retrieves the Universal Device Identifier (UDI). * * \warning Don't use the UDI for anything except communication with Solid. Also don't store * UDIs as there's no guarantee that the UDI stays the same when the hardware setup changed. * The UDI is a unique identifier that is local to the computer in question and for the * current boot session. The UDIs may change after a reboot. * Similar hardware in other computers may have different values; different * hardware could have the same UDI. * * @return the udi of the device */ QString udi() const; /** * Retrieves the Universal Device Identifier (UDI) * of the Device's parent. * * @return the udi of the device's parent */ QString parentUdi() const; /** * Retrieves the parent of the Device. * * @return the device's parent * @see parentUdi() */ Device parent() const; /** * Retrieves the name of the device vendor. * * @return the vendor name */ QString vendor() const; /** * Retrieves the name of the product corresponding to this device. * * @return the product name */ QString product() const; /** * Retrieves the name of the icon representing this device. * The naming follows the freedesktop.org specification. * * @return the icon name */ QString icon() const; /** * Retrieves the names of the emblems representing the state of this device. * The naming follows the freedesktop.org specification. * * @return the emblem names * @since 4.4 */ QStringList emblems() const; /** * Retrieves the description of device. * * @return the description * @since 4.4 */ QString description() const; /** * Tests if a device interface is available from the device. * * @param type the device interface type to query * @return true if the device interface is available, false otherwise */ bool isDeviceInterface(const DeviceInterface::Type &type) const; /** * Retrieves a specialized interface to interact with the device corresponding to * a particular device interface. * * @param type the device interface type * @returns a pointer to the device interface interface if it exists, 0 otherwise */ DeviceInterface *asDeviceInterface(const DeviceInterface::Type &type); /** * Retrieves a specialized interface to interact with the device corresponding to * a particular device interface. * * @param type the device interface type * @returns a pointer to the device interface interface if it exists, 0 otherwise */ const DeviceInterface *asDeviceInterface(const DeviceInterface::Type &type) const; /** * Retrieves a specialized interface to interact with the device corresponding * to a given device interface. * * @returns a pointer to the device interface if it exists, 0 otherwise */ template DevIface *as() { DeviceInterface::Type type = DevIface::deviceInterfaceType(); DeviceInterface *iface = asDeviceInterface(type); return qobject_cast(iface); } /** * Retrieves a specialized interface to interact with the device corresponding * to a given device interface. * * @returns a pointer to the device interface if it exists, 0 otherwise */ template const DevIface *as() const { DeviceInterface::Type type = DevIface::deviceInterfaceType(); const DeviceInterface *iface = asDeviceInterface(type); return qobject_cast(iface); } /** * Tests if a device provides a given device interface. * * @returns true if the interface is available, false otherwise */ template bool is() const { return isDeviceInterface(DevIface::deviceInterfaceType()); } private: QExplicitlySharedDataPointer d; friend class DeviceManagerPrivate; }; } #endif cantata-2.2.0/3rdparty/solid-lite/device_p.h000066400000000000000000000042021316350454000206710ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICE_P_H #define SOLID_DEVICE_P_H #include #include #if QT_VERSION < 0x050000 #include #else #include #include "ifaces/device.h" #endif #include #include "deviceinterface.h" namespace Solid { #if QT_VERSION < 0x050000 namespace Ifaces { class Device; } #endif class DevicePrivate : public QObject, public QSharedData { Q_OBJECT public: explicit DevicePrivate(const QString &udi); ~DevicePrivate(); QString udi() const { return m_udi; } Ifaces::Device *backendObject() const { return m_backendObject.data(); } void setBackendObject(Ifaces::Device *object); DeviceInterface *interface(const DeviceInterface::Type &type) const; void setInterface(const DeviceInterface::Type &type, DeviceInterface *interface); public Q_SLOTS: void _k_destroyed(QObject *object); private: QString m_udi; #if QT_VERSION < 0x050000 QWeakPointer m_backendObject; #else QPointer m_backendObject; #endif QMap m_ifaces; }; } #endif cantata-2.2.0/3rdparty/solid-lite/deviceinterface.cpp000066400000000000000000000113351316350454000225730ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "deviceinterface.h" #include "deviceinterface_p.h" #include #include Solid::DeviceInterface::DeviceInterface(DeviceInterfacePrivate &dd, QObject *backendObject) : d_ptr(&dd) { Q_D(DeviceInterface); d->setBackendObject(backendObject); } Solid::DeviceInterface::~DeviceInterface() { delete d_ptr; d_ptr = 0; } bool Solid::DeviceInterface::isValid() const { Q_D(const DeviceInterface); return d->backendObject()!=0; } QString Solid::DeviceInterface::typeToString(Type type) { int index = staticMetaObject.indexOfEnumerator("Type"); QMetaEnum metaEnum = staticMetaObject.enumerator(index); return QString(metaEnum.valueToKey((int)type)); } Solid::DeviceInterface::Type Solid::DeviceInterface::stringToType(const QString &type) { int index = staticMetaObject.indexOfEnumerator("Type"); QMetaEnum metaEnum = staticMetaObject.enumerator(index); return (Type)metaEnum.keyToValue(type.toUtf8()); } QString Solid::DeviceInterface::typeDescription(Type type) { switch (type) { case Unknown: return QObject::tr("Unknown", "Unknown device type"); case GenericInterface: return QObject::tr("Generic Interface", "Generic Interface device type"); //case Processor: // return QObject::tr("Processor", "Processor device type"); case Block: return QObject::tr("Block", "Block device type"); case StorageAccess: return QObject::tr("Storage Access", "Storage Access device type"); case StorageDrive: return QObject::tr("Storage Drive", "Storage Drive device type"); case OpticalDrive: return QObject::tr("Optical Drive", "Optical Drive device type"); case StorageVolume: return QObject::tr("Storage Volume", "Storage Volume device type"); case OpticalDisc: return QObject::tr("Optical Disc", "Optical Disc device type"); //case Camera: // return QObject::tr("Camera", "Camera device type"); case PortableMediaPlayer: return QObject::tr("Portable Media Player", "Portable Media Player device type"); /*case NetworkInterface: return QObject::tr("Network Interface", "Network Interface device type"); case AcAdapter: return QObject::tr("Ac Adapter", "Ac Adapter device type"); case Battery: return QObject::tr("Battery", "Battery device type"); case Button: return QObject::tr("Button", "Button device type"); case AudioInterface: return QObject::tr("Audio Interface", "Audio Interface device type"); case DvbInterface: return QObject::tr("Dvb Interface", "Dvb Interface device type"); case Video: return QObject::tr("Video", "Video device type"); case SerialInterface: return QObject::tr("Serial Interface", "Serial Interface device type"); case SmartCardReader: return QObject::tr("Smart Card Reader", "Smart Card Reader device type"); case InternetGateway: return QObject::tr("Internet Gateway Device", "Internet Gateway device type"); case NetworkShare: return QObject::tr("Network Share", "Network Share device type"); */ case Last: return QString(); } return QString(); } Solid::DeviceInterfacePrivate::DeviceInterfacePrivate() : m_devicePrivate(0) { } Solid::DeviceInterfacePrivate::~DeviceInterfacePrivate() { } QObject *Solid::DeviceInterfacePrivate::backendObject() const { return m_backendObject.data(); } void Solid::DeviceInterfacePrivate::setBackendObject(QObject *object) { m_backendObject = object; } Solid::DevicePrivate* Solid::DeviceInterfacePrivate::devicePrivate() const { return m_devicePrivate; } void Solid::DeviceInterfacePrivate::setDevicePrivate(DevicePrivate *devicePrivate) { m_devicePrivate = devicePrivate; } //#include "deviceinterface.moc" cantata-2.2.0/3rdparty/solid-lite/deviceinterface.h000066400000000000000000000105121316350454000222340ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICEINTERFACE_H #define SOLID_DEVICEINTERFACE_H #include #if QT_VERSION < 0x050000 #include #endif #include namespace Solid { class Device; class DevicePrivate; class Predicate; class DeviceInterfacePrivate; /** * Base class of all the device interfaces. * * A device interface describes what a device can do. A device generally has * a set of device interfaces. */ class SOLID_EXPORT DeviceInterface : public QObject { Q_OBJECT Q_ENUMS(Type) Q_DECLARE_PRIVATE(DeviceInterface) public: /** * This enum type defines the type of device interface that a Device can have. * * - Unknown : An undetermined device interface * - Processor : A processor * - Block : A block device * - StorageAccess : A mechanism to access data on a storage device * - StorageDrive : A storage drive * - OpticalDrive : An optical drive (CD-ROM, DVD, ...) * - StorageVolume : A volume * - OpticalDisc : An optical disc * - Camera : A digital camera * - PortableMediaPlayer: A portable media player * - NetworkInterface: A network interface * - SerialInterface: A serial interface * - SmartCardReader: A smart card reader interface * - NetworkShare: A network share interface */ enum Type { Unknown = 0, GenericInterface = 1, /*Processor = 2, */ Block = 3, StorageAccess = 4, StorageDrive = 5, OpticalDrive = 6, StorageVolume = 7, OpticalDisc = 8, /*Camera = 9, */PortableMediaPlayer = 10, /*NetworkInterface = 11, AcAdapter = 12, Battery = 13, Button = 14, AudioInterface = 15, DvbInterface = 16, Video = 17, SerialInterface = 18, SmartCardReader = 19, InternetGateway = 20, NetworkShare = 21, */ Last = 0xffff }; /** * Destroys a DeviceInterface object. */ virtual ~DeviceInterface(); /** * Indicates if this device interface is valid. * A device interface is considered valid if the device it is referring is available in the system. * * @return true if this device interface's device is available, false otherwise */ bool isValid() const; /** * * @return the class name of the device interface type */ static QString typeToString(Type type); /** * * @return the device interface type for the given class name */ static Type stringToType(const QString &type); /** * * @return a description suitable to display in the UI of the device interface type * @since 4.4 */ static QString typeDescription(Type type); protected: /** * @internal * Creates a new DeviceInterface object. * * @param dd the private d member. It will take care of deleting it upon destruction. * @param backendObject the device interface object provided by the backend */ DeviceInterface(DeviceInterfacePrivate &dd, QObject *backendObject); DeviceInterfacePrivate *d_ptr; private: friend class Device; friend class DevicePrivate; }; } #endif cantata-2.2.0/3rdparty/solid-lite/deviceinterface_p.h000066400000000000000000000031661316350454000225620ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICEINTERFACE_P_H #define SOLID_DEVICEINTERFACE_P_H #if QT_VERSION < 0x050000 #include #else #include #endif namespace Solid { class DeviceInterfacePrivate { public: DeviceInterfacePrivate(); virtual ~DeviceInterfacePrivate(); QObject *backendObject() const; void setBackendObject(QObject *object); DevicePrivate *devicePrivate() const; void setDevicePrivate(DevicePrivate *devicePrivate); private: #if QT_VERSION < 0x050000 QWeakPointer m_backendObject; #else QPointer m_backendObject; #endif DevicePrivate* m_devicePrivate; }; } #endif cantata-2.2.0/3rdparty/solid-lite/devicemanager.cpp000066400000000000000000000201461316350454000222450ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "devicenotifier.h" #include "devicemanager_p.h" //krazy:exclude=includes (devicenotifier.h is the header file for this class) #include "device.h" #include "device_p.h" #include "predicate.h" #include "ifaces/devicemanager.h" #include "ifaces/device.h" #include "soliddefs_p.h" SOLID_GLOBAL_STATIC(Solid::DeviceManagerStorage, globalDeviceStorage) #if QT_VERSION < 0x050000 #define QtPointer QWeakPointer #else #define QtPointer QPointer #endif Solid::DeviceManagerPrivate::DeviceManagerPrivate() : m_nullDevice(new DevicePrivate(QString())) { loadBackends(); QList backends = managerBackends(); foreach (QObject *backend, backends) { connect(backend, SIGNAL(deviceAdded(QString)), this, SLOT(_k_deviceAdded(QString))); connect(backend, SIGNAL(deviceRemoved(QString)), this, SLOT(_k_deviceRemoved(QString))); } } Solid::DeviceManagerPrivate::~DeviceManagerPrivate() { QList backends = managerBackends(); foreach (QObject *backend, backends) { disconnect(backend, 0, this, 0); } foreach (QtPointer dev, m_devicesMap) { if (!dev.data()->ref.deref()) { delete dev.data(); } } m_devicesMap.clear(); } QList Solid::Device::allDevices() { QList list; QList backends = globalDeviceStorage->managerBackends(); foreach (QObject *backendObj, backends) { Ifaces::DeviceManager *backend = qobject_cast(backendObj); if (backend == 0) continue; QStringList udis = backend->allDevices(); foreach (const QString &udi, udis) { list.append(Device(udi)); } } return list; } QList Solid::Device::listFromQuery(const QString &predicate, const QString &parentUdi) { Predicate p = Predicate::fromString(predicate); if (p.isValid()) { return listFromQuery(p, parentUdi); } else { return QList(); } } QList Solid::Device::listFromType(const DeviceInterface::Type &type, const QString &parentUdi) { QList list; QList backends = globalDeviceStorage->managerBackends(); foreach (QObject *backendObj, backends) { Ifaces::DeviceManager *backend = qobject_cast(backendObj); if (backend == 0) continue; if (!backend->supportedInterfaces().contains(type)) continue; QStringList udis = backend->devicesFromQuery(parentUdi, type); foreach (const QString &udi, udis) { list.append(Device(udi)); } } return list; } QList Solid::Device::listFromQuery(const Predicate &predicate, const QString &parentUdi) { QList list; QList backends = globalDeviceStorage->managerBackends(); QSet usedTypes = predicate.usedTypes(); foreach (QObject *backendObj, backends) { Ifaces::DeviceManager *backend = qobject_cast(backendObj); if (backend == 0) continue; QSet udis; if (predicate.isValid()) { QSet supportedTypes = backend->supportedInterfaces(); if (supportedTypes.intersect(usedTypes).isEmpty()) { continue; } foreach (DeviceInterface::Type type, supportedTypes) { udis+= QSet::fromList(backend->devicesFromQuery(parentUdi, type)); } } else { udis+= QSet::fromList(backend->allDevices()); } foreach (const QString &udi, udis) { Device dev(udi); bool matches = false; if(!predicate.isValid()) { matches = true; } else { matches = predicate.matches(dev); } if (matches) { list.append(dev); } } } return list; } Solid::DeviceNotifier *Solid::DeviceNotifier::instance() { return globalDeviceStorage->notifier(); } void Solid::DeviceManagerPrivate::_k_deviceAdded(const QString &udi) { if (m_devicesMap.contains(udi)) { DevicePrivate *dev = m_devicesMap[udi].data(); // Ok, this one was requested somewhere was invalid // and now becomes magically valid! if (dev && dev->backendObject() == 0) { dev->setBackendObject(createBackendObject(udi)); Q_ASSERT(dev->backendObject()!=0); } } emit deviceAdded(udi); } void Solid::DeviceManagerPrivate::_k_deviceRemoved(const QString &udi) { if (m_devicesMap.contains(udi)) { DevicePrivate *dev = m_devicesMap[udi].data(); // Ok, this one was requested somewhere was valid // and now becomes magically invalid! if (dev) { Q_ASSERT(dev->backendObject()!=0); dev->setBackendObject(0); Q_ASSERT(dev->backendObject()==0); } } emit deviceRemoved(udi); } void Solid::DeviceManagerPrivate::_k_destroyed(QObject *object) { QString udi = m_reverseMap.take(object); if (!udi.isEmpty()) { m_devicesMap.remove(udi); } } Solid::DevicePrivate *Solid::DeviceManagerPrivate::findRegisteredDevice(const QString &udi) { if (udi.isEmpty()) { return m_nullDevice.data(); } else if (m_devicesMap.contains(udi)) { return m_devicesMap[udi].data(); } else { Ifaces::Device *iface = createBackendObject(udi); DevicePrivate *devData = new DevicePrivate(udi); devData->setBackendObject(iface); QtPointer ptr(devData); m_devicesMap[udi] = ptr; m_reverseMap[devData] = udi; connect(devData, SIGNAL(destroyed(QObject*)), this, SLOT(_k_destroyed(QObject*))); return devData; } } Solid::Ifaces::Device *Solid::DeviceManagerPrivate::createBackendObject(const QString &udi) { QList backends = globalDeviceStorage->managerBackends(); foreach (QObject *backendObj, backends) { Ifaces::DeviceManager *backend = qobject_cast(backendObj); if (backend == 0) continue; if (!udi.startsWith(backend->udiPrefix())) continue; Ifaces::Device *iface = 0; QObject *object = backend->createDevice(udi); iface = qobject_cast(object); if (iface==0) { delete object; } return iface; } return 0; } Solid::DeviceManagerStorage::DeviceManagerStorage() { } QList Solid::DeviceManagerStorage::managerBackends() { ensureManagerCreated(); return m_storage.localData()->managerBackends(); } Solid::DeviceNotifier *Solid::DeviceManagerStorage::notifier() { ensureManagerCreated(); return m_storage.localData(); } void Solid::DeviceManagerStorage::ensureManagerCreated() { if (!m_storage.hasLocalData()) { m_storage.setLocalData(new DeviceManagerPrivate()); } } //#include "devicenotifier.moc" // #include "devicemanager_p.moc" cantata-2.2.0/3rdparty/solid-lite/devicemanager_p.h000066400000000000000000000045401316350454000222310ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICEMANAGER_P_H #define SOLID_DEVICEMANAGER_P_H #include "managerbase_p.h" #include "devicenotifier.h" #include #if QT_VERSION < 0x050000 #include #else #include #endif #include #include namespace Solid { namespace Ifaces { class Device; } class DevicePrivate; class DeviceManagerPrivate : public DeviceNotifier, public ManagerBasePrivate { Q_OBJECT public: DeviceManagerPrivate(); ~DeviceManagerPrivate(); DevicePrivate *findRegisteredDevice(const QString &udi); private Q_SLOTS: void _k_deviceAdded(const QString &udi); void _k_deviceRemoved(const QString &udi); void _k_destroyed(QObject *object); private: Ifaces::Device *createBackendObject(const QString &udi); QExplicitlySharedDataPointer m_nullDevice; #if QT_VERSION < 0x050000 QMap > m_devicesMap; #else QMap > m_devicesMap; #endif QMap m_reverseMap; }; class DeviceManagerStorage { public: DeviceManagerStorage(); QList managerBackends(); DeviceNotifier *notifier(); private: void ensureManagerCreated(); QThreadStorage m_storage; }; } #endif cantata-2.2.0/3rdparty/solid-lite/devicenotifier.h000066400000000000000000000041461316350454000221210ustar00rootroot00000000000000/* Copyright 2005-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_DEVICENOTIFIER_H #define SOLID_DEVICENOTIFIER_H #include #include #include namespace Solid { /** * This class allow to query the underlying system to obtain information * about the hardware available. * * It's the unique entry point for hardware discovery. Applications should use * it to find devices, or to be notified about hardware changes. * * Note that it's implemented as a singleton and encapsulates the backend logic. * * @author Kevin Ottens */ class SOLID_EXPORT DeviceNotifier : public QObject //krazy:exclude=dpointer (interface class) { Q_OBJECT public: static DeviceNotifier *instance(); Q_SIGNALS: /** * This signal is emitted when a new device appear in the underlying system. * * @param udi the new device UDI */ void deviceAdded(const QString &udi); /** * This signal is emitted when a device disappear from the underlying system. * * @param udi the old device UDI */ void deviceRemoved(const QString &udi); }; } #endif cantata-2.2.0/3rdparty/solid-lite/genericinterface.cpp000066400000000000000000000042731316350454000227530ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "genericinterface.h" #include "genericinterface_p.h" #include "soliddefs_p.h" #include Solid::GenericInterface::GenericInterface(QObject *backendObject) : DeviceInterface(*new GenericInterfacePrivate(), backendObject) { if (backendObject) { connect(backendObject, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); connect(backendObject, SIGNAL(conditionRaised(QString,QString)), this, SIGNAL(conditionRaised(QString,QString))); } } Solid::GenericInterface::~GenericInterface() { } QVariant Solid::GenericInterface::property(const QString &key) const { Q_D(const GenericInterface); return_SOLID_CALL(Ifaces::GenericInterface *, d->backendObject(), QVariant(), property(key)); } QMap Solid::GenericInterface::allProperties() const { Q_D(const GenericInterface); return_SOLID_CALL(Ifaces::GenericInterface *, d->backendObject(), QVariantMap(), allProperties()); } bool Solid::GenericInterface::propertyExists(const QString &key) const { Q_D(const GenericInterface); return_SOLID_CALL(Ifaces::GenericInterface *, d->backendObject(), false, propertyExists(key)); } //#include "genericinterface.moc" cantata-2.2.0/3rdparty/solid-lite/genericinterface.h000066400000000000000000000127021316350454000224140ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_GENERICINTERFACE_H #define SOLID_GENERICINTERFACE_H #include #include #include #include namespace Solid { class GenericInterfacePrivate; class Device; /** * Generic interface to deal with a device. It exposes a set of properties * and is organized as a key/value set. * * Warning: Using this class could expose some backend specific details * and lead to non portable code. Use it at your own risk, or during * transitional phases when the provided device interfaces don't * provide the necessary methods. */ class SOLID_EXPORT GenericInterface : public DeviceInterface { Q_OBJECT Q_ENUMS(PropertyChange) Q_DECLARE_PRIVATE(GenericInterface) friend class Device; public: /** * This enum type defines the type of change that can occur to a GenericInterface * property. * * - PropertyModified : A property value has changed in the device * - PropertyAdded : A new property has been added to the device * - PropertyRemoved : A property has been removed from the device */ enum PropertyChange { PropertyModified, PropertyAdded, PropertyRemoved }; private: /** * Creates a new GenericInterface object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit GenericInterface(QObject *backendObject); public: /** * Destroys a Processor object. */ virtual ~GenericInterface(); /** * Get the Solid::DeviceInterface::Type of the GenericInterface device interface. * * @return the Processor device interface type * @see Solid::Ifaces::Enums::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::GenericInterface; } /** * Retrieves a property of the device. * * Warning: Using this method could expose some backend specific details * and lead to non portable code. Use it at your own risk, or during * transitional phases when the provided device interfaces don't * provide the necessary methods. * * @param key the property key * @return the actual value of the property, or QVariant() if the * property is unknown */ QVariant property(const QString &key) const; /** * Retrieves a key/value map of all the known properties for the device. * * Warning: Using this method could expose some backend specific details * and lead to non portable code. Use it at your own risk, or during * transitional phases when the provided device interfaces don't * provide the necessary methods. * * @return all the properties of the device */ QMap allProperties() const; /** * Tests if a property exist in the device. * * Warning: Using this method could expose some backend specific details * and lead to non portable code. Use it at your own risk, or during * transitional phases when the provided device interfaces don't * provide the necessary methods. * * @param key the property key * @return true if the property is available in the device, false * otherwise */ bool propertyExists(const QString &key) const; Q_SIGNALS: /** * This signal is emitted when a property is changed in the device. * * @param changes the map describing the property changes that * occurred in the device, keys are property name and values * describe the kind of change done on the device property * (added/removed/modified), it's one of the type Solid::Device::PropertyChange */ void propertyChanged(const QMap &changes); /** * This signal is emitted when an event occurred in the device. * For example when a button is pressed. * * @param condition the condition name * @param reason a message explaining why the condition has been raised */ void conditionRaised(const QString &condition, const QString &reason); }; } #endif cantata-2.2.0/3rdparty/solid-lite/genericinterface_p.h000066400000000000000000000023221316350454000227300ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_GENERICINTERFACE_P_H #define SOLID_GENERICINTERFACE_P_H #include "deviceinterface_p.h" namespace Solid { class GenericInterfacePrivate : public DeviceInterfacePrivate { public: GenericInterfacePrivate() : DeviceInterfacePrivate() { } }; } #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/000077500000000000000000000000001316350454000201765ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/ifaces/CMakeLists.txt000066400000000000000000000000001316350454000227240ustar00rootroot00000000000000cantata-2.2.0/3rdparty/solid-lite/ifaces/block.cpp000066400000000000000000000017321316350454000217770ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "block.h" Solid::Ifaces::Block::~Block() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/block.h000066400000000000000000000043611316350454000214450ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_BLOCK_H #define SOLID_IFACES_BLOCK_H #include namespace Solid { namespace Ifaces { /** * This device interface is available on block devices. * * A block device is an adressable device such as drive or partition. * It is possible to interact with such a device using a special file * in the system. */ class Block : virtual public DeviceInterface { public: /** * Destroys a Block object. */ virtual ~Block(); /** * Retrieves the major number of the node file to interact with * the device. * * @return the device major number */ virtual int deviceMajor() const = 0; /** * Retrieves the minor number of the node file to interact with * the device. * * @return the device minor number */ virtual int deviceMinor() const = 0; /** * Retrieves the absolute path of the special file to interact * with the device. * * @return the absolute path of the special file to interact with * the device */ virtual QString device() const = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::Block, "org.kde.Solid.Ifaces.Block/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/device.cpp000066400000000000000000000053661316350454000221530ustar00rootroot00000000000000/* Copyright 2005 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "ifaces/device.h" #include #include Solid::Ifaces::Device::Device(QObject *parent) : QObject(parent) { } Solid::Ifaces::Device::~Device() { } QString Solid::Ifaces::Device::parentUdi() const { return QString(); } void Solid::Ifaces::Device::registerAction(const QString &actionName, QObject *dest, const char *requestSlot, const char *doneSlot) const { QDBusConnection::sessionBus().connect(QString(), deviceDBusPath(), "org.kde.Solid.Device", actionName+"Requested", dest, requestSlot); QDBusConnection::sessionBus().connect(QString(), deviceDBusPath(), "org.kde.Solid.Device", actionName+"Done", dest, doneSlot); } void Solid::Ifaces::Device::broadcastActionDone(const QString &actionName, int error, const QString &errorString) const { QDBusMessage signal = QDBusMessage::createSignal(deviceDBusPath(), "org.kde.Solid.Device", actionName+"Done"); signal << error << errorString; QDBusConnection::sessionBus().send(signal); } void Solid::Ifaces::Device::broadcastActionRequested(const QString &actionName) const { QDBusMessage signal = QDBusMessage::createSignal(deviceDBusPath(), "org.kde.Solid.Device", actionName+"Requested"); QDBusConnection::sessionBus().send(signal); } QString Solid::Ifaces::Device::deviceDBusPath() const { const QByteArray encodedUdi = udi().toUtf8().toPercentEncoding(QByteArray(), ".~", '_'); return QString("/org/kde/solid/Device_") + QString::fromLatin1(encodedUdi); } //#include "ifaces/device.moc" cantata-2.2.0/3rdparty/solid-lite/ifaces/device.h000066400000000000000000000133661316350454000216170ustar00rootroot00000000000000/* Copyright 2005 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_DEVICE_H #define SOLID_IFACES_DEVICE_H #include #include #include #include #include #include namespace Solid { namespace Ifaces { /** * This class specifies the interface a device will have to comply to in order to be used in the system. * * Backends will have to implement it to gather and modify data in the underlying system. * Each device has a set of key/values pair describing its properties. It has also a list of interfaces * describing what the device actually is (a cdrom drive, a portable media player, etc.) * * @author Kevin Ottens */ class Device : public QObject { Q_OBJECT public: /** * Constructs a Device */ Device(QObject *parent = 0); /** * Destruct the Device object */ virtual ~Device(); /** * Retrieves the Universal Device Identifier (UDI) of the Device. * This identifier is unique for each device in the system. * * @returns the Universal Device Identifier of the current device */ virtual QString udi() const = 0; /** * Retrieves the Universal Device Identifier (UDI) of the Device's * parent. * * @returns the Universal Device Identifier of the parent device */ virtual QString parentUdi() const; /** * Retrieves the name of the device vendor. * * @return the vendor name */ virtual QString vendor() const = 0; /** * Retrieves the name of the product corresponding to this device. * * @return the product name */ virtual QString product() const = 0; /** * Retrieves the name of the icon representing this device. * The naming follows the freedesktop.org specification. * * @return the icon name */ virtual QString icon() const = 0; /** * Retrieves the name of the emblems representing the state of this device. * The naming follows the freedesktop.org specification. * * @return the emblem names */ virtual QStringList emblems() const = 0; /** * Retrieves the description of device. * * @return the description */ virtual QString description() const = 0; /** * Tests if a property exist. * * @param type the device interface type * @returns true if the device interface is provided by this device, false otherwise */ virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const = 0; /** * Create a specialized interface to interact with the device corresponding to * a particular device interface. * * @param type the device interface type * @returns a pointer to the device interface if supported by the device, 0 otherwise */ virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type) = 0; /** * Register an action for the given device. Each time the same device in another process * broadcast the begin or the end of such action, the corresponding slots will be called * in the current process. * * @param actionName name of the action to register * @param dest the object receiving the messages when the action begins and ends * @param requestSlot the slot processing the message when the action begins * @param doneSlot the slot processing the message when the action ends */ void registerAction(const QString &actionName, QObject *dest, const char *requestSlot, const char *doneSlot) const; /** * Allows to broadcat that an action just got requested on a device to all * the corresponding devices in other processes. * * @param actionName name of the action which just completed */ void broadcastActionRequested(const QString &actionName) const; /** * Allows to broadcast that an action just completed in a device to all * the corresponding devices in other processes. * * @param actionName name of the action which just completed * @param error error code if the action failed * @param errorString message describing a potential error */ void broadcastActionDone(const QString &actionName, int error = Solid::NoError, const QString &errorString = QString()) const; private: QString deviceDBusPath() const; }; } } #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/deviceinterface.cpp000066400000000000000000000017701316350454000240270ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "deviceinterface.h" Solid::Ifaces::DeviceInterface::~DeviceInterface() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/deviceinterface.h000066400000000000000000000031371316350454000234730ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_DEVICEINTERFACE_H #define SOLID_IFACES_DEVICEINTERFACE_H #include #if QT_VERSION < 0x050000 #include #endif namespace Solid { namespace Ifaces { /** * Base interface of all the device interfaces. * * A device interface describes what a device can do. A device generally has * a set of device interfaces. * * @see Solid::Ifaces::AbstractDeviceInterface */ class DeviceInterface { public: /** * Destroys a DeviceInterface object. */ virtual ~DeviceInterface(); }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::DeviceInterface, "org.kde.Solid.Ifaces.DeviceInterface/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/devicemanager.cpp000066400000000000000000000021721316350454000234760ustar00rootroot00000000000000/* Copyright 2005 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "ifaces/devicemanager.h" Solid::Ifaces::DeviceManager::DeviceManager(QObject *parent) : QObject(parent) { } Solid::Ifaces::DeviceManager::~DeviceManager() { } //#include "ifaces/devicemanager.moc" cantata-2.2.0/3rdparty/solid-lite/ifaces/devicemanager.h000066400000000000000000000076351316350454000231540ustar00rootroot00000000000000/* Copyright 2005 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_DEVICEMANAGER_H #define SOLID_IFACES_DEVICEMANAGER_H #include #include #include namespace Solid { namespace Ifaces { /** * This class specifies the interface a backend will have to implement in * order to be used in the system. * * A device manager allows to query the underlying platform to discover the * available devices. It has also the responsibility to notify when a device * appears or disappears. */ class DeviceManager : public QObject { Q_OBJECT public: /** * Constructs a DeviceManager */ DeviceManager(QObject *parent = 0); /** * Destructs a DeviceManager object */ virtual ~DeviceManager(); /** * Retrieves the prefix used for the UDIs off all the devices * reported by the device manager */ virtual QString udiPrefix() const = 0; /** * Retrieves a set of interfaces the backend supports */ virtual QSet supportedInterfaces() const = 0; /** * Retrieves the Universal Device Identifier (UDI) of all the devices * available in the system. This identifier is unique for each device * in the system. * * @returns the UDIs of all the devices in the system */ virtual QStringList allDevices() = 0; /** * Retrieves the Universal Device Identifier (UDI) of all the devices * matching the given constraints (parent and device interface) * * @param parentUdi UDI of the parent of the devices we're searching for, or QString() * if there's no constraint on the parent * @param type DeviceInterface type available on the devices we're looking for, or DeviceInterface::Unknown * if there's no constraint on the device interfaces * @returns the UDIs of all the devices having the given parent and device interface */ virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type = Solid::DeviceInterface::Unknown) = 0; /** * Instantiates a new Device object from this backend given its UDI. * * @param udi the identifier of the device instantiated * @returns a new Device object if there's a device having the given UDI, 0 otherwise */ virtual QObject *createDevice(const QString &udi) = 0; Q_SIGNALS: /** * This signal is emitted when a new device appears in the system. * * @param udi the new device identifier */ void deviceAdded(const QString &udi); /** * This signal is emitted when a device disappears from the system. * * @param udi the old device identifier */ void deviceRemoved(const QString &udi); }; } } #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/genericinterface.cpp000066400000000000000000000017731316350454000242070ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "genericinterface.h" Solid::Ifaces::GenericInterface::~GenericInterface() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/genericinterface.h000066400000000000000000000065721316350454000236560ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_GENERICINTERFACE_H #define SOLID_IFACES_GENERICINTERFACE_H #include #if QT_VERSION < 0x050000 #include #endif #include #include namespace Solid { namespace Ifaces { /** * Generic interface to deal with a device. It exposes a set of properties * and is organized a a key/value set. * * Warning: Using this class could expose some backend specific details * and lead to non portable code. Use it at your own risk, or during * transitional phases when the provided device interfaces don't * provide the necessary methods. */ class GenericInterface { public: /** * Destroys a GenericInterface object. */ virtual ~GenericInterface(); /** * Retrieves the value of a property. * * @param key the property name * @returns the property value or QVariant() if the property doesn't exist */ virtual QVariant property(const QString &key) const = 0; /** * Retrieves all the properties of this device. * * @returns all properties in a map */ virtual QMap allProperties() const = 0; /** * Tests if a property exist. * * @param key the property name * @returns true if the property exists in this device, false otherwise */ virtual bool propertyExists(const QString &key) const = 0; protected: //Q_SIGNALS: /** * This signal is emitted when a property is changed in the device. * * @param changes the map describing the property changes that * occurred in the device, keys are property name and values * describe the kind of change done on the device property * (added/removed/modified), it's one of the type Solid::Device::PropertyChange */ virtual void propertyChanged(const QMap &changes) = 0; /** * This signal is emitted when an event occurred in the device. * For example when a button is pressed. * * @param condition the condition name * @param reason a message explaining why the condition has been raised */ virtual void conditionRaised(const QString &condition, const QString &reason) = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::GenericInterface, "org.kde.Solid.Ifaces.GenericInterface/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/opticaldisc.cpp000066400000000000000000000017541316350454000232070ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "opticaldisc.h" Solid::Ifaces::OpticalDisc::~OpticalDisc() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/opticaldisc.h000066400000000000000000000056161316350454000226550ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_OPTICALDISC_H #define SOLID_IFACES_OPTICALDISC_H #include #include namespace Solid { namespace Ifaces { /** * This device interface is available on optical discs. * * An optical disc is a volume that can be inserted in a cdrom drive. */ class OpticalDisc : virtual public StorageVolume { public: /** * Destroys an OpticalDisc object. */ virtual ~OpticalDisc(); /** * Retrieves the content types this disc contains (audio, video, * data...). * * @return the flag set indicating the available contents */ virtual Solid::OpticalDisc::ContentTypes availableContent() const = 0; /** * Retrieves the disc type (cdr, cdrw...). * * @return the disc type */ virtual Solid::OpticalDisc::DiscType discType() const = 0; /** * Indicates if it's possible to write additional data to the disc. * * @return true if the disc is appendable, false otherwise */ virtual bool isAppendable() const = 0; /** * Indicates if the disc is blank. * * @return true if the disc is blank, false otherwise */ virtual bool isBlank() const = 0; /** * Indicates if the disc is rewritable. * * A disc is rewritable if you can write on it several times. * * @return true if the disc is rewritable, false otherwise */ virtual bool isRewritable() const = 0; /** * Retrieves the disc capacity (that is the maximum size of a * volume could have on this disc). * * @return the capacity of the disc in bytes */ virtual qulonglong capacity() const = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::OpticalDisc, "org.kde.Solid.Ifaces.OpticalDisc/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/opticaldrive.cpp000066400000000000000000000017571316350454000234010ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "opticaldrive.h" Solid::Ifaces::OpticalDrive::~OpticalDrive() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/opticaldrive.h000066400000000000000000000060571316350454000230440ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACE_OPTICALDRIVE_H #define SOLID_IFACE_OPTICALDRIVE_H #include #include #include namespace Solid { namespace Ifaces { /** * This device interface is available on CD-ROM drives. * * A Cdrom is a storage that can handle optical discs. */ class OpticalDrive : virtual public StorageDrive { public: /** * Destroys a Cdrom object. */ virtual ~OpticalDrive(); /** * Retrieves the medium types this drive supports. * * @return the flag set indicating the supported medium types */ virtual Solid::OpticalDrive::MediumTypes supportedMedia() const = 0; /** * Retrieves the maximum read speed of this drive in kilobytes. * * @return the maximum read speed */ virtual int readSpeed() const = 0; /** * Retrieves the maximum write speed of this drive in kilobytes. * * @return the maximum write speed */ virtual int writeSpeed() const = 0; /** * Retrieves the list of supported write speeds of this drive in * kilobytes. * * @return the list of supported write speeds */ virtual QList writeSpeeds() const = 0; /** * Ejects any disc that could be contained in this drive. * If this drive is empty, but has a tray it'll be opened * * @return */ virtual bool eject() = 0; protected: //Q_SIGNALS: /** * This signal is emitted when the eject button is pressed * on the drive. * * Please note that some (broken) drives doesn't report this event. * @param udi the UDI of the drive */ virtual void ejectPressed(const QString &udi) = 0; virtual void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi) = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::OpticalDrive, "org.kde.Solid.Ifaces.OpticalDrive/0.1") #endif // SOLID_IFACE_OPTICALDRIVE_H cantata-2.2.0/3rdparty/solid-lite/ifaces/portablemediaplayer.cpp000066400000000000000000000020211316350454000247220ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "portablemediaplayer.h" Solid::Ifaces::PortableMediaPlayer::~PortableMediaPlayer() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/portablemediaplayer.h000066400000000000000000000076431316350454000244060ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_PORTABLEMEDIAPLAYER_H #define SOLID_IFACES_PORTABLEMEDIAPLAYER_H #include #include namespace Solid { namespace Ifaces { /** * This class implements Portable Media Player device interface and represents * a portable media player attached to the system. * A portable media player is a portable device able to play multimedia files. * Some of them have even recording capabilities. * @author Davide Bettio */ class PortableMediaPlayer : virtual public DeviceInterface { public: /** * Destroys a portable media player object. */ virtual ~PortableMediaPlayer(); /** * Retrieves known protocols this device can speak. This list may be dependent * on installed device driver libraries. * * Possible protocols: * * storage - filesystem-based device: can browse and play media files stored * on its volume. iPod-like devices can have both storage and ipod protocol * set, you should use more specific (ipod) protocol in this case. * * ipod - iPod-like device where media files are stored on filesystem, but these need * an entry in device database in order to be playable. * * mtp - Media Transfer Protocol-compatible devices. * * @return a list of known protocols this device can speak */ virtual QStringList supportedProtocols() const = 0; /** * Retrieves known installed device drivers that claim to handle this device * using the requested protocol. * * Possible drivers: * * usb - device is talked to using USB. This driver alone does not specify which * particular USB service/protocol should be used. * * usbmux - device supports AFC (Apple File Connection) and usbmuxd daemon is ready * on /var/run/usbmuxd socket on UNIX and localhost:27015 port on Windows. * * @param protocol The protocol to get drivers for. Specify empty protocol to get * drivers for all possible protocols. * @return a list of known device drivers that can handle this device */ virtual QStringList supportedDrivers(QString protocol = QString()) const = 0; /** * Retrieves a driver specific string allowing to access the device. * * For example for the "mtp" driver it will return the serial number * of the device and "usbmux" driver will return 40-digit device UUID * * @return the driver specific data */ virtual QVariant driverHandle(const QString &driver) const = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::PortableMediaPlayer, "org.kde.Solid.Ifaces.PortableMediaPlayer/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/storageaccess.cpp000066400000000000000000000017621316350454000235360ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storageaccess.h" Solid::Ifaces::StorageAccess::~StorageAccess() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/storageaccess.h000066400000000000000000000106271316350454000232030ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_STORAGEACCESS_H #define SOLID_IFACES_STORAGEACCESS_H #include #include namespace Solid { namespace Ifaces { /** * This device interface is available on volume devices. * * A volume is anything that can contain data (partition, optical disc, * memory card). It's a particular kind of block device. */ class StorageAccess : virtual public DeviceInterface { public: /** * Destroys a StorageVolume object. */ virtual ~StorageAccess(); /** * Indicates if this volume is mounted. * * @return true if the volume is mounted */ virtual bool isAccessible() const = 0; /** * Retrieves the absolute path of this volume mountpoint. * * @return the absolute path to the mount point if the volume is * mounted, QString() otherwise */ virtual QString filePath() const = 0; /** * Indicates if this volume should be ignored by applications. * * If it should be ignored, it generally means that it should be * invisible to the user. It's useful for firmware partitions or * OS reinstall partitions on some systems. * * @return true if the volume should be ignored */ virtual bool isIgnored() const = 0; /** * Mounts the volume. * * @return the job handling the operation */ virtual bool setup() = 0; /** * Unmounts the volume. * * @return the job handling the operation */ virtual bool teardown() = 0; protected: //Q_SIGNALS: /** * This signal is emitted when the mount state of this device * has changed. * * @param newState true if the volume is mounted, false otherwise * @param udi the UDI of the volume */ virtual void accessibilityChanged(bool accessible, const QString &udi) = 0; /** * This signal is emitted when the mount state of this device * has changed. * * @param newState true if the volume is mounted, false otherwise * @param udi the UDI of the volume */ virtual void setupDone(Solid::ErrorType error, QVariant resultData, const QString &udi) = 0; /** * This signal is emitted when the mount state of this device * has changed. * * @param newState true if the volume is mounted, false otherwise * @param udi the UDI of the volume */ virtual void teardownDone(Solid::ErrorType error, QVariant resultData, const QString &udi) = 0; /** * This signal is emitted when a setup of this device is requested. * The signal might be spontaneous i.e. it can be triggered by * another process. * * @param udi the UDI of the volume */ virtual void setupRequested(const QString &udi) = 0; /** * This signal is emitted when a teardown of this device is requested. * The signal might be spontaneous i.e. it can be triggered by * another process * * @param udi the UDI of the volume */ virtual void teardownRequested(const QString &udi) = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::StorageAccess, "org.kde.Solid.Ifaces.StorageAccess/0.1") #endif cantata-2.2.0/3rdparty/solid-lite/ifaces/storagedrive.cpp000066400000000000000000000017571316350454000234120ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storagedrive.h" Solid::Ifaces::StorageDrive::~StorageDrive() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/storagedrive.h000066400000000000000000000055711316350454000230550ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_STORAGEDRIVE_H #define SOLID_IFACES_STORAGEDRIVE_H #include #include namespace Solid { namespace Ifaces { /** * This device interface is available on storage devices. * * A storage is anything that can contain a set of volumes (card reader, * hard disk, cdrom drive...). It's a particular kind of block device. */ class StorageDrive : virtual public Block { public: /** * Destroys a StorageDrive object. */ virtual ~StorageDrive(); /** * Retrieves the type of physical interface this storage device is * connected to. * * @return the bus type * @see Solid::StorageDrive::Bus */ virtual Solid::StorageDrive::Bus bus() const = 0; /** * Retrieves the type of this storage drive. * * @return the drive type * @see Solid::StorageDrive::DriveType */ virtual Solid::StorageDrive::DriveType driveType() const = 0; /** * Indicates if the media contained by this drive can be removed. * * For example memory card can be removed from the drive by the user, * while partitions can't be removed from hard disks. * * @return true if media can be removed, false otherwise. */ virtual bool isRemovable() const = 0; /** * Indicates if this storage device can be plugged or unplugged while * the computer is running. * * @return true if this storage supports hotplug, false otherwise */ virtual bool isHotpluggable() const = 0; /** * Retrieves this drives size in bytes. * * @return the size of this drive */ virtual qulonglong size() const = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::StorageDrive, "org.kde.Solid.Ifaces.StorageDrive/0.1") #endif // SOLID_IFACES_STORAGEDRIVE_H cantata-2.2.0/3rdparty/solid-lite/ifaces/storagevolume.cpp000066400000000000000000000017621316350454000236040ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storagevolume.h" Solid::Ifaces::StorageVolume::~StorageVolume() { } cantata-2.2.0/3rdparty/solid-lite/ifaces/storagevolume.h000066400000000000000000000071651316350454000232540ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_IFACES_STORAGEVOLUME_H #define SOLID_IFACES_STORAGEVOLUME_H #include #include namespace Solid { namespace Ifaces { /** * This device interface is available on volume devices. * * A volume is anything that can contain data (partition, optical disc, * memory card). It's a particular kind of block device. */ class StorageVolume : virtual public Block { public: /** * Destroys a StorageVolume object. */ virtual ~StorageVolume(); /** * Indicates if this volume should be ignored by applications. * * If it should be ignored, it generally means that it should be * invisible to the user. It's useful for firmware partitions or * OS reinstall partitions on some systems. * * @return true if the volume should be ignored */ virtual bool isIgnored() const = 0; /** * Retrieves the type of use for this volume (for example filesystem). * * @return the usage type * @see Solid::StorageVolume::UsageType */ virtual Solid::StorageVolume::UsageType usage() const = 0; /** * Retrieves the filesystem type of this volume. * * @return the filesystem type if applicable, QString() otherwise */ virtual QString fsType() const = 0; /** * Retrieves this volume label. * * @return the volume lavel if available, QString() otherwise */ virtual QString label() const = 0; /** * Retrieves this volume Universal Unique IDentifier (UUID). * * You can generally assume that this identifier is unique with reasonable * confidence. Except if the volume UUID has been forged to intentionally * provoke a collision, the probability to have two volumes having the same * UUID is low. * * @return the Universal Unique IDentifier if available, QString() otherwise */ virtual QString uuid() const = 0; /** * Retrieves this volume size in bytes. * * @return the size of this volume */ virtual qulonglong size() const = 0; /** * Retrieves the crypto container UDI of this volume. * * @return the encrypted volume UDI containing the current volume if appliable, * an empty string otherwise */ virtual QString encryptedContainerUdi() const = 0; }; } } Q_DECLARE_INTERFACE(Solid::Ifaces::StorageVolume, "org.kde.Solid.Ifaces.StorageVolume/0.1") #endif // SOLID_IFACES_STORAGEVOLUME_H cantata-2.2.0/3rdparty/solid-lite/managerbase.cpp000066400000000000000000000067241316350454000217260ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "managerbase_p.h" #include #if !defined (Q_WS_WIN) && !defined (Q_OS_MAC) #include #endif //#include "backends/fakehw/fakemanager.h" #if defined (Q_OS_MAC) #include "backends/iokit/iokitmanager.h" #elif defined (Q_OS_UNIX) #include "backends/hal/halmanager.h" #if defined (WITH_SOLID_UDISKS2) #include "backends/udisks2/udisksmanager.h" #else #include "backends/udisks/udisksmanager.h" #endif //#include "backends/upower/upowermanager.h" //#if defined (HUPNP_FOUND) //#include "backends/upnp/upnpdevicemanager.h" //#endif #if defined (UDEV_FOUND) #include "backends/udev/udevmanager.h" #endif //#include "backends/fstab/fstabmanager.h" #elif defined (Q_WS_WIN) && defined(HAVE_WBEM) && !defined(_WIN32_WCE) #include "backends/wmi/wmimanager.h" #endif Solid::ManagerBasePrivate::ManagerBasePrivate() { } Solid::ManagerBasePrivate::~ManagerBasePrivate() { qDeleteAll(m_backends); } void Solid::ManagerBasePrivate::loadBackends() { /*QString solidFakeXml(QString::fromLocal8Bit(qgetenv("SOLID_FAKEHW"))); if (!solidFakeXml.isEmpty()) { m_backends << new Solid::Backends::Fake::FakeManager(0, solidFakeXml); } else*/ { # if defined(Q_OS_MAC) m_backends << new Solid::Backends::IOKit::IOKitManager(0); # elif defined(Q_WS_WIN) && defined(HAVE_WBEM) && !defined(_WIN32_WCE) m_backends << new Solid::Backends::Wmi::WmiManager(0); # elif defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) m_backends << new Solid::Backends::Hal::HalManager(0); # elif defined(Q_OS_LINUX) bool solidHalLegacyEnabled = QString::fromLocal8Bit(qgetenv("SOLID_HAL_LEGACY")).toInt()==1; if (solidHalLegacyEnabled) { m_backends << new Solid::Backends::Hal::HalManager(0); } else { # if defined(UDEV_FOUND) m_backends << new Solid::Backends::UDev::UDevManager(0); # endif # if defined(WITH_SOLID_UDISKS2) m_backends << new Solid::Backends::UDisks2::Manager(0) # else m_backends << new Solid::Backends::UDisks::UDisksManager(0) # endif /*<< new Solid::Backends::UPower::UPowerManager(0) << new Solid::Backends::Fstab::FstabManager(0)*/; } # endif //# if defined (HUPNP_FOUND) // m_backends << new Solid::Backends::UPnP::UPnPDeviceManager(0); //# endif } } QList Solid::ManagerBasePrivate::managerBackends() const { return m_backends; } cantata-2.2.0/3rdparty/solid-lite/managerbase_p.h000066400000000000000000000025111316350454000217000ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_MANAGERBASE_P_H #define SOLID_MANAGERBASE_P_H #include #include #include "solid-lite/solid_export.h" namespace Solid { class ManagerBasePrivate { public: ManagerBasePrivate(); virtual ~ManagerBasePrivate(); void loadBackends(); QList managerBackends() const; private: QList m_backends; }; } #endif cantata-2.2.0/3rdparty/solid-lite/opticaldisc.cpp000066400000000000000000000044111316350454000217460ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "opticaldisc.h" #include "opticaldisc_p.h" #include "soliddefs_p.h" #include Solid::OpticalDisc::OpticalDisc(QObject *backendObject) : StorageVolume(*new OpticalDiscPrivate(), backendObject) { } Solid::OpticalDisc::~OpticalDisc() { } Solid::OpticalDisc::ContentTypes Solid::OpticalDisc::availableContent() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), ContentTypes(), availableContent()); } Solid::OpticalDisc::DiscType Solid::OpticalDisc::discType() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), UnknownDiscType, discType()); } bool Solid::OpticalDisc::isAppendable() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), false, isAppendable()); } bool Solid::OpticalDisc::isBlank() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), false, isBlank()); } bool Solid::OpticalDisc::isRewritable() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), false, isRewritable()); } qulonglong Solid::OpticalDisc::capacity() const { Q_D(const OpticalDisc); return_SOLID_CALL(Ifaces::OpticalDisc *, d->backendObject(), 0, capacity()); } //#include "opticaldisc.moc" cantata-2.2.0/3rdparty/solid-lite/opticaldisc.h000066400000000000000000000151331316350454000214160ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_OPTICALDISC_H #define SOLID_OPTICALDISC_H #include #include namespace Solid { class OpticalDiscPrivate; class Device; /** * This device interface is available on optical discs. * * An optical disc is a volume that can be inserted in CD-R*,DVD*,Blu-Ray,HD-DVD drives. */ class SOLID_EXPORT OpticalDisc : public StorageVolume { Q_OBJECT Q_ENUMS(ContentType DiscType) Q_FLAGS(ContentTypes) Q_PROPERTY(ContentTypes availableContent READ availableContent) Q_PROPERTY(DiscType discType READ discType) Q_PROPERTY(bool appendable READ isAppendable) Q_PROPERTY(bool blank READ isBlank) Q_PROPERTY(bool rewritable READ isRewritable) Q_PROPERTY(qulonglong capacity READ capacity) Q_DECLARE_PRIVATE(OpticalDisc) friend class Device; public: /** * This enum type defines the type of content available in an optical disc. * * - Audio : A disc containing audio * - Data : A disc containing data * - VideoCd : A Video Compact Disc (VCD) * - SuperVideoCd : A Super Video Compact Disc (SVCD) * - VideoDvd : A Video Digital Versatile Disc (DVD-Video) */ enum ContentType { NoContent = 0x00, Audio = 0x01, Data = 0x02, VideoCd = 0x04, SuperVideoCd = 0x08, VideoDvd = 0x10, VideoBluRay = 0x20 }; /** * This type stores an OR combination of ContentType values. */ Q_DECLARE_FLAGS(ContentTypes, ContentType) /** * This enum type defines the type of optical disc it can be. * * - UnknownDiscType : An undetermined disc type * - CdRom : A Compact Disc Read-Only Memory (CD-ROM) * - CdRecordable : A Compact Disc Recordable (CD-R) * - CdRewritable : A Compact Disc ReWritable (CD-RW) * - DvdRom : A Digital Versatile Disc Read-Only Memory (DVD-ROM) * - DvdRam : A Digital Versatile Disc Random Access Memory (DVD-RAM) * - DvdRecordable : A Digital Versatile Disc Recordable (DVD-R) * - DvdRewritable : A Digital Versatile Disc ReWritable (DVD-RW) * - DvdPlusRecordable : A Digital Versatile Disc Recordable (DVD+R) * - DvdPlusRewritable : A Digital Versatile Disc ReWritable (DVD+RW) * - DvdPlusRecordableDuallayer : A Digital Versatile Disc Recordable Dual-Layer (DVD+R DL) * - DvdPlusRewritableDuallayer : A Digital Versatile Disc ReWritable Dual-Layer (DVD+RW DL) * - BluRayRom : A Blu-ray Disc (BD) * - BluRayRecordable : A Blu-ray Disc Recordable (BD-R) * - BluRayRewritable : A Blu-ray Disc (BD-RE) * - HdDvdRom: A High Density Digital Versatile Disc (HD DVD) * - HdDvdRecordable : A High Density Digital Versatile Disc Recordable (HD DVD-R) * - HdDvdRewritable : A High Density Digital Versatile Disc ReWritable (HD DVD-RW) */ enum DiscType { UnknownDiscType = -1, CdRom, CdRecordable, CdRewritable, DvdRom, DvdRam, DvdRecordable, DvdRewritable, DvdPlusRecordable, DvdPlusRewritable, DvdPlusRecordableDuallayer, DvdPlusRewritableDuallayer, BluRayRom, BluRayRecordable, BluRayRewritable, HdDvdRom, HdDvdRecordable, HdDvdRewritable }; private: /** * Creates a new OpticalDisc object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit OpticalDisc(QObject *backendObject); public: /** * Destroys an OpticalDisc object. */ virtual ~OpticalDisc(); /** * Get the Solid::DeviceInterface::Type of the OpticalDisc device interface. * * @return the OpticalDisc device interface type * @see Solid::Ifaces::Enums::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::OpticalDisc; } /** * Retrieves the content types this disc contains (audio, video, * data...). * * @return the flag set indicating the available contents * @see Solid::OpticalDisc::ContentType */ ContentTypes availableContent() const; /** * Retrieves the disc type (cdr, cdrw...). * * @return the disc type */ DiscType discType() const; /** * Indicates if it's possible to write additional data to the disc. * * @return true if the disc is appendable, false otherwise */ bool isAppendable() const; /** * Indicates if the disc is blank. * * @return true if the disc is blank, false otherwise */ bool isBlank() const; /** * Indicates if the disc is rewritable. * * A disc is rewritable if you can write on it several times. * * @return true if the disc is rewritable, false otherwise */ bool isRewritable() const; /** * Retrieves the disc capacity (that is the maximum size of a * volume could have on this disc). * * @return the capacity of the disc in bytes */ qulonglong capacity() const; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Solid::OpticalDisc::ContentTypes) #endif cantata-2.2.0/3rdparty/solid-lite/opticaldisc_p.h000066400000000000000000000022701316350454000217330ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_OPTICALDISC_P_H #define SOLID_OPTICALDISC_P_H #include "storagevolume_p.h" namespace Solid { class OpticalDiscPrivate : public StorageVolumePrivate { public: OpticalDiscPrivate() : StorageVolumePrivate() { } }; } #endif cantata-2.2.0/3rdparty/solid-lite/opticaldrive.cpp000066400000000000000000000046741316350454000221500ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "opticaldrive.h" #include "opticaldrive_p.h" #include "soliddefs_p.h" #include Solid::OpticalDrive::OpticalDrive(QObject *backendObject) : StorageDrive(*new OpticalDrivePrivate(), backendObject) { connect(backendObject, SIGNAL(ejectPressed(QString)), this, SIGNAL(ejectPressed(QString))); connect(backendObject, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)), this, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString))); connect(backendObject, SIGNAL(ejectRequested(QString)), this, SIGNAL(ejectRequested(QString))); } Solid::OpticalDrive::~OpticalDrive() { } Solid::OpticalDrive::MediumTypes Solid::OpticalDrive::supportedMedia() const { Q_D(const OpticalDrive); return_SOLID_CALL(Ifaces::OpticalDrive *, d->backendObject(), MediumTypes(), supportedMedia()); } int Solid::OpticalDrive::readSpeed() const { Q_D(const OpticalDrive); return_SOLID_CALL(Ifaces::OpticalDrive *, d->backendObject(), 0, readSpeed()); } int Solid::OpticalDrive::writeSpeed() const { Q_D(const OpticalDrive); return_SOLID_CALL(Ifaces::OpticalDrive *, d->backendObject(), 0, writeSpeed()); } QList Solid::OpticalDrive::writeSpeeds() const { Q_D(const OpticalDrive); return_SOLID_CALL(Ifaces::OpticalDrive *, d->backendObject(), QList(), writeSpeeds()); } bool Solid::OpticalDrive::eject() { Q_D(OpticalDrive); return_SOLID_CALL(Ifaces::OpticalDrive *, d->backendObject(), false, eject()); } //#include "opticaldrive.moc" cantata-2.2.0/3rdparty/solid-lite/opticaldrive.h000066400000000000000000000147361316350454000216150ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_OPTICALDRIVE_H #define SOLID_OPTICALDRIVE_H #include #include #include #include #include namespace Solid { class OpticalDrivePrivate; class Device; /** * This device interface is available on CD-R*,DVD*,Blu-Ray,HD-DVD drives. * * An OpticalDrive is a storage that can handle optical discs. */ class SOLID_EXPORT OpticalDrive : public StorageDrive { Q_OBJECT Q_ENUMS(MediumType) Q_FLAGS(MediumTypes) Q_PROPERTY(MediumTypes supportedMedia READ supportedMedia) Q_PROPERTY(int readSpeed READ readSpeed) Q_PROPERTY(int writeSpeed READ writeSpeed) Q_PROPERTY(QList writeSpeeds READ writeSpeeds) Q_DECLARE_PRIVATE(OpticalDrive) friend class Device; public: /** * This enum type defines the type of medium an optical drive supports. * * - Cdr : A Recordable Compact Disc (CD-R) * - Cdrw : A ReWritable Compact Disc (CD-RW) * - Dvd : A Digital Versatile Disc (DVD) * - Dvdr : A Recordable Digital Versatile Disc (DVD-R) * - Dvdrw : A ReWritable Digital Versatile Disc (DVD-RW) * - Dvdram : A Random Access Memory Digital Versatile Disc (DVD-RAM) * - Dvdplusr : A Recordable Digital Versatile Disc (DVD+R) * - Dvdplusrw : A ReWritable Digital Versatile Disc (DVD+RW) * - Dvdplusdl : A Dual Layer Digital Versatile Disc (DVD+R DL) * - Dvdplusdlrw : A Dual Layer Digital Versatile Disc (DVD+RW DL) * - Bd : A Blu-ray Disc (BD) * - Bdr : A Blu-ray Disc Recordable (BD-R) * - Bdre : A Blu-ray Disc Recordable and Eraseable (BD-RE) * - HdDvd : A High Density Digital Versatile Disc (HD DVD) * - HdDvdr : A High Density Digital Versatile Disc Recordable (HD DVD-R) * - HdDvdrw : A High Density Digital Versatile Disc ReWritable (HD DVD-RW) */ enum MediumType { Cdr=0x00001, Cdrw=0x00002, Dvd=0x00004, Dvdr=0x00008, Dvdrw=0x00010, Dvdram=0x00020, Dvdplusr=0x00040, Dvdplusrw=0x00080, Dvdplusdl=0x00100, Dvdplusdlrw=0x00200, Bd=0x00400, Bdr=0x00800, Bdre=0x01000, HdDvd=0x02000, HdDvdr=0x04000, HdDvdrw=0x08000 }; /** * This type stores an OR combination of MediumType values. */ Q_DECLARE_FLAGS(MediumTypes, MediumType) private: /** * Creates a new OpticalDrive object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit OpticalDrive(QObject *backendObject); public: /** * Destroys an OpticalDrive object. */ virtual ~OpticalDrive(); /** * Get the Solid::DeviceInterface::Type of the OpticalDrive device interface. * * @return the OpticalDrive device interface type * @see Solid::Ifaces::Enums::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::OpticalDrive; } /** * Retrieves the medium types this drive supports. * * @return the flag set indicating the supported medium types */ MediumTypes supportedMedia() const; /** * Retrieves the maximum read speed of this drive in kilobytes per second. * * @return the maximum read speed */ int readSpeed() const; /** * Retrieves the maximum write speed of this drive in kilobytes per second. * * @return the maximum write speed */ int writeSpeed() const; /** * Retrieves the list of supported write speeds of this drive in * kilobytes per second. * * @return the list of supported write speeds */ QList writeSpeeds() const; /** * Ejects any disc that could be contained in this drive. * If this drive is empty, but has a tray it'll be opened. * * @return the status of the eject operation */ bool eject(); Q_SIGNALS: /** * This signal is emitted when the eject button is pressed * on the drive. * * Please note that some (broken) drives doesn't report this event. * @param udi the UDI of the drive */ void ejectPressed(const QString &udi); /** * This signal is emitted when the attempted eject process on this * drive is completed. The signal might be spontaneous, i.e. * it can be triggered by another process. * * @param error type of error that occurred, if any * @param errorData more information about the error, if any * @param udi the UDI of the volume */ void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi); /** * This signal is emitted when eject on this drive is * requested. The signal might be spontaneous, i.e. it * can be triggered by another process. * * @param udi the UDI of the volume */ void ejectRequested(const QString &udi); }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Solid::OpticalDrive::MediumTypes) #endif // SOLID_OPTICALDRIVE_H cantata-2.2.0/3rdparty/solid-lite/opticaldrive_p.h000066400000000000000000000023231316350454000221210ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_OPTICALDRIVE_P_H #define SOLID_OPTICALDRIVE_P_H #include "storagedrive_p.h" namespace Solid { class OpticalDrivePrivate : public StorageDrivePrivate { public: OpticalDrivePrivate() : StorageDrivePrivate() { } }; } #endif // SOLID_OPTICALDRIVE_P_H cantata-2.2.0/3rdparty/solid-lite/portablemediaplayer.cpp000066400000000000000000000041171316350454000235000ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Kevin Ottens Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "portablemediaplayer.h" #include "portablemediaplayer_p.h" #include "soliddefs_p.h" #include Solid::PortableMediaPlayer::PortableMediaPlayer(QObject *backendObject) : DeviceInterface(*new PortableMediaPlayerPrivate(), backendObject) { } Solid::PortableMediaPlayer::~PortableMediaPlayer() { } QStringList Solid::PortableMediaPlayer::supportedProtocols() const { Q_D(const PortableMediaPlayer); return_SOLID_CALL(Ifaces::PortableMediaPlayer *, d->backendObject(), QStringList(), supportedProtocols()); } QStringList Solid::PortableMediaPlayer::supportedDrivers(QString protocol) const { Q_D(const PortableMediaPlayer); return_SOLID_CALL(Ifaces::PortableMediaPlayer *, d->backendObject(), QStringList(), supportedDrivers(protocol)); } QVariant Solid::PortableMediaPlayer::driverHandle(const QString &driver) const { Q_D(const PortableMediaPlayer); return_SOLID_CALL(Ifaces::PortableMediaPlayer *, d->backendObject(), QVariant(), driverHandle(driver)); } //#include "portablemediaplayer.moc" cantata-2.2.0/3rdparty/solid-lite/portablemediaplayer.h000066400000000000000000000075301316350454000231470ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Kevin Ottens Copyright 2007 Jeff Mitchell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_PORTABLEMEDIAPLAYER_H #define SOLID_PORTABLEMEDIAPLAYER_H #include #include #include #include namespace Solid { class PortableMediaPlayerPrivate; class Device; /** * This class implements Portable Media Player device interface and represents * a portable media player attached to the system. * A portable media player is a portable device able to play multimedia files. * Some of them have even recording capabilities. * @author Davide Bettio */ class SOLID_EXPORT PortableMediaPlayer : public DeviceInterface { Q_OBJECT Q_PROPERTY(QStringList supportedProtocols READ supportedProtocols) Q_PROPERTY(QStringList supportedDrivers READ supportedDrivers) Q_DECLARE_PRIVATE(PortableMediaPlayer) friend class Device; public: private: /** * Creates a new PortableMediaPlayer object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit PortableMediaPlayer(QObject *backendObject); public: /** * Destroys a portable media player object. */ virtual ~PortableMediaPlayer(); /** * Get the Solid::DeviceInterface::Type of the PortableMediaPlayer device interface. * * @return the PortableMediaPlayer device interface type * @see Solid::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::PortableMediaPlayer; } /** * Retrieves known protocols this device can speak. This list may be dependent * on installed device driver libraries. * * @return a list of known protocols this device can speak */ QStringList supportedProtocols() const; /** * Retrieves known installed device drivers that claim to handle this device * using the requested protocol. If protocol is blank, returns a list of * all drivers supporting the device. * * @param protocol The protocol to get drivers for. * @return a list of installed drivers meeting the criteria */ QStringList supportedDrivers(QString protocol = QString()) const; /** * Retrieves a driver specific string allowing to access the device. * * For example for the "mtp" driver it will return the serial number * of the device. * * @return the driver specific data */ QVariant driverHandle(const QString &driver) const; }; } #endif cantata-2.2.0/3rdparty/solid-lite/portablemediaplayer_p.h000066400000000000000000000024261316350454000234650ustar00rootroot00000000000000/* Copyright 2006 Davide Bettio Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_PORTABLEMEDIAPLAYER_P_H #define SOLID_PORTABLEMEDIAPLAYER_P_H #include "deviceinterface_p.h" namespace Solid { class PortableMediaPlayerPrivate : public DeviceInterfacePrivate { public: PortableMediaPlayerPrivate() : DeviceInterfacePrivate() { } }; } #endif cantata-2.2.0/3rdparty/solid-lite/predicate.cpp000066400000000000000000000222011316350454000214050ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "predicate.h" #include #include #include #include namespace Solid { class Predicate::Private { public: Private() : isValid(false), type(PropertyCheck), compOperator(Predicate::Equals), operand1(0), operand2(0) {} bool isValid; Type type; DeviceInterface::Type ifaceType; QString property; QVariant value; Predicate::ComparisonOperator compOperator; Predicate *operand1; Predicate *operand2; }; } Solid::Predicate::Predicate() : d(new Private()) { } Solid::Predicate::Predicate(const Predicate &other) : d(new Private()) { *this = other; } Solid::Predicate::Predicate(const DeviceInterface::Type &ifaceType, const QString &property, const QVariant &value, ComparisonOperator compOperator) : d(new Private()) { d->isValid = true; d->ifaceType = ifaceType; d->property = property; d->value = value; d->compOperator = compOperator; } Solid::Predicate::Predicate(const QString &ifaceName, const QString &property, const QVariant &value, ComparisonOperator compOperator) : d(new Private()) { DeviceInterface::Type ifaceType = DeviceInterface::stringToType(ifaceName); if (((int)ifaceType)!=-1) { d->isValid = true; d->ifaceType = ifaceType; d->property = property; d->value = value; d->compOperator = compOperator; } } Solid::Predicate::Predicate(const DeviceInterface::Type &ifaceType) : d(new Private()) { d->isValid = true; d->type = InterfaceCheck; d->ifaceType = ifaceType; } Solid::Predicate::Predicate(const QString &ifaceName) : d(new Private()) { DeviceInterface::Type ifaceType = DeviceInterface::stringToType(ifaceName); if (((int)ifaceType)!=-1) { d->isValid = true; d->type = InterfaceCheck; d->ifaceType = ifaceType; } } Solid::Predicate::~Predicate() { if (d->type!=PropertyCheck && d->type!=InterfaceCheck) { delete d->operand1; delete d->operand2; } delete d; } Solid::Predicate &Solid::Predicate::operator=(const Predicate &other) { d->isValid = other.d->isValid; d->type = other.d->type; if (d->type!=PropertyCheck && d->type!=InterfaceCheck) { Predicate* operand1 = new Predicate(*(other.d->operand1)); delete d->operand1; d->operand1 = operand1; Predicate* operand2 = new Predicate(*(other.d->operand2)); delete d->operand2; d->operand2 = operand2; } else { d->ifaceType = other.d->ifaceType; d->property = other.d->property; d->value = other.d->value; d->compOperator = other.d->compOperator; } return *this; } Solid::Predicate Solid::Predicate::operator &(const Predicate &other) { Predicate result; result.d->isValid = true; result.d->type = Conjunction; result.d->operand1 = new Predicate(*this); result.d->operand2 = new Predicate(other); return result; } Solid::Predicate &Solid::Predicate::operator &=(const Predicate &other) { *this = *this & other; return *this; } Solid::Predicate Solid::Predicate::operator|(const Predicate &other) { Predicate result; result.d->isValid = true; result.d->type = Disjunction; result.d->operand1 = new Predicate(*this); result.d->operand2 = new Predicate(other); return result; } Solid::Predicate &Solid::Predicate::operator |=(const Predicate &other) { *this = *this | other; return *this; } bool Solid::Predicate::isValid() const { return d->isValid; } bool Solid::Predicate::matches(const Device &device) const { if (!d->isValid) return false; switch(d->type) { case Disjunction: return d->operand1->matches(device) || d->operand2->matches(device); case Conjunction: return d->operand1->matches(device) && d->operand2->matches(device); case PropertyCheck: { const DeviceInterface *iface = device.asDeviceInterface(d->ifaceType); if (iface!=0) { const int index = iface->metaObject()->indexOfProperty(d->property.toLatin1()); QMetaProperty metaProp = iface->metaObject()->property(index); QVariant value = metaProp.isReadable() ? metaProp.read(iface) : QVariant(); QVariant expected = d->value; if (metaProp.isEnumType() && expected.type()==QVariant::String) { QMetaEnum metaEnum = metaProp.enumerator(); int value = metaEnum.keysToValue(d->value.toString().toLatin1()); if (value>=0) { // No value found for these keys, resetting expected to invalid expected = value; } else { expected = QVariant(); } } if (d->compOperator==Mask) { bool v_ok; int v = value.toInt(&v_ok); bool e_ok; int e = expected.toInt(&e_ok); return (e_ok && v_ok && (v &e)); } else { return (value == expected); } } break; } case InterfaceCheck: return device.isDeviceInterface(d->ifaceType); } return false; } QSet Solid::Predicate::usedTypes() const { QSet res; if (d->isValid) { switch(d->type) { case Disjunction: case Conjunction: res+= d->operand1->usedTypes(); res+= d->operand2->usedTypes(); break; case PropertyCheck: case InterfaceCheck: res << d->ifaceType; break; } } return res; } QString Solid::Predicate::toString() const { if (!d->isValid) return "False"; if (d->type!=PropertyCheck && d->type!=InterfaceCheck) { QString op = " AND "; if (d->type==Disjunction) op = " OR "; return '['+d->operand1->toString()+op+d->operand2->toString()+']'; } else { QString ifaceName = DeviceInterface::typeToString(d->ifaceType); if (ifaceName.isEmpty()) ifaceName = "Unknown"; if (d->type==InterfaceCheck) { return "IS "+ifaceName; } QString value; switch (d->value.type()) { case QVariant::StringList: { value = '{'; const QStringList list = d->value.toStringList(); QStringList::ConstIterator it = list.begin(); QStringList::ConstIterator end = list.end(); for (; it!=end; ++it) { value+= '\''+ *it+'\''; if (it+1!=end) { value+= ", "; } } value+= '}'; break; } case QVariant::Bool: value = (d->value.toBool()?"true":"false"); break; case QVariant::Int: case QVariant::UInt: case QVariant::LongLong: case QVariant::ULongLong: value = d->value.toString(); break; default: value = '\''+d->value.toString()+'\''; break; } QString str_operator = "=="; if (d->compOperator!=Equals) str_operator = " &"; return ifaceName+'.'+d->property+' '+str_operator+' '+value; } } Solid::Predicate::Type Solid::Predicate::type() const { return d->type; } Solid::DeviceInterface::Type Solid::Predicate::interfaceType() const { return d->ifaceType; } QString Solid::Predicate::propertyName() const { return d->property; } QVariant Solid::Predicate::matchingValue() const { return d->value; } Solid::Predicate::ComparisonOperator Solid::Predicate::comparisonOperator() const { return d->compOperator; } Solid::Predicate Solid::Predicate::firstOperand() const { if( d->operand1 ) { return *d->operand1; } return Predicate(); } Solid::Predicate Solid::Predicate::secondOperand() const { if( d->operand2 ) { return *d->operand2; } return Predicate(); } cantata-2.2.0/3rdparty/solid-lite/predicate.h000066400000000000000000000221151316350454000210560ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_PREDICATE_H #define SOLID_PREDICATE_H #include #include #include #include namespace Solid { class Device; /** * This class implements predicates for devices. * * A predicate is a logical condition that a given device can match or not. * It's a constraint about the value a property must have in a given device * interface, or any combination (conjunction, disjunction) of such * constraints. * * FIXME: Add an example. */ class SOLID_EXPORT Predicate { public: /** * The comparison operator which can be used for matching within the predicate. * * - Equals, the property and the value will match for strict equality * - Mask, the property and the value will match if the bitmasking is not null */ enum ComparisonOperator { Equals, Mask }; /** * The predicate type which controls how the predicate is handled * * - PropertyCheck, the predicate contains a comparison that needs to be matched using a ComparisonOperator * - Conjunction, the two contained predicates need to be true for this predicate to be true * - Disjunction, either of the two contained predicates may be true for this predicate to be true * - InterfaceCheck, the device type is compared */ enum Type { PropertyCheck, Conjunction, Disjunction, InterfaceCheck }; /** * Constructs an invalid predicate. */ Predicate(); /** * Copy constructor. * * @param other the predicate to copy */ Predicate(const Predicate &other); /** * Constructs a predicate matching the value of a property in * a given device interface. * * @param ifaceType the device interface type the device must have * @param property the property name of the device interface * @param value the value the property must have to make the device match * @param compOperator the operator to apply between the property and the value when matching */ Predicate(const DeviceInterface::Type &ifaceType, const QString &property, const QVariant &value, ComparisonOperator compOperator = Equals); /** * Constructs a predicate matching the value of a property in * a given device interface. * * @param ifaceName the name of the device interface the device must have * @param property the property name of the device interface * @param value the value the property must have to make the device match * @param compOperator the operator to apply between the property and the value when matching */ Predicate(const QString &ifaceName, const QString &property, const QVariant &value, ComparisonOperator compOperator = Equals); /** * Constructs a predicate matching devices being of a particular device interface * * @param ifaceType the device interface the device must have */ explicit Predicate(const DeviceInterface::Type &ifaceType); /** * Constructs a predicate matching devices being of a particular device interface * * @param ifaceName the name of the device interface the device must have */ explicit Predicate(const QString &ifaceName); /** * Destroys a Predicate object. */ ~Predicate(); /** * Assignement operator. * * @param other the predicate to assign * @return this predicate after having assigned 'other' to it */ Predicate &operator=(const Predicate &other); /** * 'And' operator. * * @param other the second operand * @return a new 'and' predicate having 'this' and 'other' as operands */ Predicate operator &(const Predicate &other); /** * 'AndEquals' operator. * * @param other the second operand * @return assigns to 'this' a new 'and' predicate having 'this' and 'other' as operands */ Predicate &operator &=(const Predicate &other); /** * 'Or' operator. * * @param other the second operand * @return a new 'or' predicate having 'this' and 'other' as operands */ Predicate operator|(const Predicate &other); /** * 'OrEquals' operator. * * @param other the second operand * @return assigns to 'this' a new 'or' predicate having 'this' and 'other' as operands */ Predicate &operator|=(const Predicate &other); /** * Indicates if the predicate is valid. * * Predicate() is the only invalid predicate. * * @return true if the predicate is valid, false otherwise */ bool isValid() const; /** * Checks if a device matches the predicate. * * @param device the device to match against the predicate * @return true if the given device matches the predicate, false otherwise */ bool matches(const Device &device) const; /** * Retrieves the device interface types used in this predicate. * * @return all the device interface types used in this predicate */ QSet usedTypes() const; /** * Converts the predicate to its string form. * * @return a string representation of the predicate */ QString toString() const; /** * Converts a string to a predicate. * * @param predicate the string to convert * @return a new valid predicate if the given string is syntactically * correct, Predicate() otherwise */ static Predicate fromString(const QString &predicate); /** * Retrieves the predicate type, used to determine how to handle the predicate * * @since 4.4 * @return the predicate type */ Type type() const; /** * Retrieves the interface type. * * @note This is only valid for InterfaceCheck and PropertyCheck types * @since 4.4 * @return a device interface type used by the predicate */ DeviceInterface::Type interfaceType() const; /** * Retrieves the property name used when retrieving the value to compare against * * @note This is only valid for the PropertyCheck type * @since 4.4 * @return a property name */ QString propertyName() const; /** * Retrieves the value used when comparing a devices property to see if it matches the predicate * * @note This is only valid for the PropertyCheck type * @since 4.4 * @return the value used */ QVariant matchingValue() const; /** * Retrieves the comparison operator used to compare a property's value * * @since 4.4 * @note This is only valid for Conjunction and Disjunction types * @return the comparison operator used */ ComparisonOperator comparisonOperator() const; /** * A smaller, inner predicate which is the first to appear and is compared with the second one * * @since 4.4 * @note This is only valid for Conjunction and Disjunction types * @return The predicate used for the first operand */ Predicate firstOperand() const; /** * A smaller, inner predicate which is the second to appear and is compared with the first one * * @since 4.4 * @note This is only valid for Conjunction and Disjunction types * @return The predicate used for the second operand */ Predicate secondOperand() const; private: class Private; Private * const d; }; } #endif cantata-2.2.0/3rdparty/solid-lite/predicate_lexer.c000066400000000000000000001630441316350454000222570ustar00rootroot00000000000000#line 2 "predicate_lexer.c" #line 4 "predicate_lexer.c" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 5 #define YY_FLEX_SUBMINOR_VERSION 35 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ #ifdef __cplusplus /* The "const" storage-class-modifier is valid. */ #define YY_USE_CONST #else /* ! __cplusplus */ /* C99 requires __STDC__ to be defined as 1. */ #if defined (__STDC__) #define YY_USE_CONST #endif /* defined (__STDC__) */ #endif /* ! __cplusplus */ #ifdef YY_USE_CONST #define yyconst const #else #define yyconst #endif /* Returned upon end-of-file. */ #define YY_NULL 0 /* Promotes a possibly negative, possibly signed char to an unsigned * integer for use as an array index. If the signed char is negative, * we want to instead treat it as an 8-bit unsigned char, hence the * double cast. */ #define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) /* An opaque pointer. */ #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void* yyscan_t; #endif /* For convenience, these vars (plus the bison vars far below) are macros in the reentrant scanner. */ #define yyin yyg->yyin_r #define yyout yyg->yyout_r #define yyextra yyg->yyextra_r #define yyleng yyg->yyleng_r #define yytext yyg->yytext_r #define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) #define yy_flex_debug yyg->yy_flex_debug_r /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN yyg->yy_start = 1 + 2 * /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START ((yyg->yy_start - 1) / 2) #define YYSTATE YY_START /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) /* Special action meaning "start processing a new file". */ #define YY_NEW_FILE Solidrestart(yyin ,yyscanner ) #define YY_END_OF_BUFFER_CHAR 0 /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k. * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. * Ditto for the __ia64__ case accordingly. */ #define YY_BUF_SIZE 32768 #else #define YY_BUF_SIZE 16384 #endif /* __ia64__ */ #endif /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 #define YY_LESS_LINENO(n) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ *yy_cp = yyg->yy_hold_char; \ YY_RESTORE_YY_MORE_OFFSET \ yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) #define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ yy_size_t yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; #define YY_BUFFER_NEW 0 #define YY_BUFFER_NORMAL 1 /* When an EOF's been seen but there's still some text to process * then we mark the buffer as YY_EOF_PENDING, to indicate that we * shouldn't try reading from the input source any more. We might * still have a bunch of tokens to match, though, because of * possible backing-up. * * When we actually see the EOF, we change the status to "new" * (via Solidrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". * * Returns the top of the stack, or NULL. */ #define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ : NULL) /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] void Solidrestart (FILE *input_file ,yyscan_t yyscanner ); void Solid_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); YY_BUFFER_STATE Solid_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); void Solid_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); void Solid_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); void Solidpush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); void Solidpop_buffer_state (yyscan_t yyscanner ); static void Solidensure_buffer_stack (yyscan_t yyscanner ); static void Solid_load_buffer_state (yyscan_t yyscanner ); static void Solid_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); #define YY_FLUSH_BUFFER Solid_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) YY_BUFFER_STATE Solid_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); YY_BUFFER_STATE Solid_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); YY_BUFFER_STATE Solid_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); void *Solidalloc (yy_size_t ,yyscan_t yyscanner ); void *Solidrealloc (void *,yy_size_t ,yyscan_t yyscanner ); void Solidfree (void * ,yyscan_t yyscanner ); #define yy_new_buffer Solid_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ Solidensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ Solid_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ Solidensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ Solid_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) /* Begin user sect3 */ typedef unsigned char YY_CHAR; typedef int yy_state_type; #define yytext_ptr yytext_r static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); static int yy_get_next_buffer (yyscan_t yyscanner ); static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ yyg->yytext_ptr = yy_bp; \ yyleng = (size_t) (yy_cp - yy_bp); \ yyg->yy_hold_char = *yy_cp; \ *yy_cp = '\0'; \ yyg->yy_c_buf_p = yy_cp; #define YY_NUM_RULES 16 #define YY_END_OF_BUFFER 17 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info { flex_int32_t yy_verify; flex_int32_t yy_nxt; }; static yyconst flex_int16_t yy_accept[41] = { 0, 0, 0, 17, 15, 14, 14, 2, 15, 13, 15, 13, 10, 15, 12, 12, 12, 12, 12, 12, 14, 0, 8, 9, 11, 0, 10, 1, 12, 12, 12, 5, 4, 12, 3, 12, 12, 12, 6, 7, 0 } ; static yyconst flex_int32_t yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 4, 5, 1, 1, 1, 1, 6, 7, 8, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 10, 1, 1, 1, 11, 12, 12, 13, 14, 15, 12, 12, 16, 12, 12, 17, 12, 18, 19, 12, 12, 20, 21, 22, 23, 12, 12, 12, 12, 12, 24, 1, 25, 1, 1, 1, 11, 12, 12, 13, 14, 15, 12, 12, 16, 12, 12, 17, 12, 18, 19, 12, 12, 20, 21, 22, 23, 12, 12, 12, 12, 12, 26, 1, 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } ; static yyconst flex_int32_t yy_meta[28] = { 0, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1 } ; static yyconst flex_int16_t yy_base[43] = { 0, 0, 0, 60, 61, 26, 28, 61, 54, 61, 49, 48, 24, 46, 37, 0, 43, 32, 32, 31, 32, 45, 61, 40, 39, 38, 28, 61, 0, 33, 28, 0, 0, 21, 0, 22, 28, 27, 0, 0, 61, 37, 38 } ; static yyconst flex_int16_t yy_def[43] = { 0, 40, 1, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 42, 42, 42, 42, 42, 42, 40, 41, 40, 40, 40, 40, 40, 40, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 0, 40, 40 } ; static yyconst flex_int16_t yy_nxt[89] = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 16, 17, 15, 15, 18, 15, 15, 19, 15, 9, 9, 9, 9, 20, 20, 20, 20, 25, 26, 20, 20, 25, 26, 21, 21, 28, 39, 38, 37, 36, 35, 34, 24, 24, 23, 22, 33, 32, 31, 30, 29, 27, 24, 23, 22, 40, 3, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40 } ; static yyconst flex_int16_t yy_chk[89] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 6, 6, 12, 12, 20, 20, 26, 26, 41, 41, 42, 37, 36, 35, 33, 30, 29, 25, 24, 23, 21, 19, 18, 17, 16, 14, 13, 11, 10, 8, 3, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40 } ; /* The intent behind this definition is that it'll catch * any uses of REJECT which flex missed. */ #define REJECT reject_used_but_not_detected #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET #line 1 "predicate_lexer.l" #line 2 "predicate_lexer.l" #include "predicate_parser.h" #include "predicateparse.h" #include #include #define YY_NO_UNPUT int Solidwrap( yyscan_t _scanner ); void PredicateParse_initLexer( const char *_code, yyscan_t _scanner ); char *PredicateParse_putSymbol( char *_name ); char *PredicateParse_putString( char *_str ); void PredicateParse_initFlex( const char *_code, yyscan_t _scanner ); #line 480 "predicate_lexer.c" #define INITIAL 0 #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif /* Holds the entire state of the reentrant scanner. */ struct yyguts_t { /* User-defined. Not touched by flex. */ YY_EXTRA_TYPE yyextra_r; /* The rest are the same as the globals declared in the non-reentrant scanner. */ FILE *yyin_r, *yyout_r; size_t yy_buffer_stack_top; /**< index of top of stack. */ size_t yy_buffer_stack_max; /**< capacity of stack. */ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ char yy_hold_char; int yy_n_chars; int yyleng_r; char *yy_c_buf_p; int yy_init; int yy_start; int yy_did_buffer_switch_on_eof; int yy_start_stack_ptr; int yy_start_stack_depth; int *yy_start_stack; yy_state_type yy_last_accepting_state; char* yy_last_accepting_cpos; int yylineno_r; int yy_flex_debug_r; char *yytext_r; int yy_more_flag; int yy_more_len; YYSTYPE * yylval_r; }; /* end struct yyguts_t */ static int yy_init_globals (yyscan_t yyscanner ); /* This must go here because YYSTYPE and YYLTYPE are included * from bison output in section 1.*/ # define yylval yyg->yylval_r int Solidlex_init (yyscan_t* scanner); int Solidlex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int Solidlex_destroy (yyscan_t yyscanner ); int Solidget_debug (yyscan_t yyscanner ); void Solidset_debug (int debug_flag ,yyscan_t yyscanner ); YY_EXTRA_TYPE Solidget_extra (yyscan_t yyscanner ); void Solidset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); FILE *Solidget_in (yyscan_t yyscanner ); void Solidset_in (FILE * in_str ,yyscan_t yyscanner ); FILE *Solidget_out (yyscan_t yyscanner ); void Solidset_out (FILE * out_str ,yyscan_t yyscanner ); int Solidget_leng (yyscan_t yyscanner ); char *Solidget_text (yyscan_t yyscanner ); int Solidget_lineno (yyscan_t yyscanner ); void Solidset_lineno (int line_number ,yyscan_t yyscanner ); YYSTYPE * Solidget_lval (yyscan_t yyscanner ); void Solidset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int Solidwrap (yyscan_t yyscanner ); #else extern int Solidwrap (yyscan_t yyscanner ); #endif #endif /*static void yyunput (int c,char *buf_ptr ,yyscan_t yyscanner); UNUSED */ #ifndef yytext_ptr static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); #endif #ifndef YY_NO_INPUT #if 0 #ifdef __cplusplus static int yyinput (yyscan_t yyscanner ); #else static int input (yyscan_t yyscanner ); #endif #endif /* UNUSED */ #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k */ #define YY_READ_BUF_SIZE 16384 #else #define YY_READ_BUF_SIZE 8192 #endif /* __ia64__ */ #endif /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ #define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ #ifndef YY_INPUT #define YY_INPUT(buf,result,max_size) \ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ size_t n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ if ( c == '\n' ) \ buf[n++] = (char) c; \ if ( c == EOF && ferror( yyin ) ) \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ result = n; \ } \ else \ { \ errno=0; \ while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ break; \ } \ errno=0; \ clearerr(yyin); \ } \ }\ \ #endif /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. */ #ifndef yyterminate #define yyterminate() return YY_NULL #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) #endif /* end tables serialization structures and prototypes */ /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int Solidlex \ (YYSTYPE * yylval_param ,yyscan_t yyscanner); #define YY_DECL int Solidlex \ (YYSTYPE * yylval_param , yyscan_t yyscanner) #endif /* !YY_DECL */ /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ #ifndef YY_USER_ACTION #define YY_USER_ACTION #endif /* Code executed at the end of each rule. */ #ifndef YY_BREAK #define YY_BREAK break; #endif #define YY_RULE_SETUP \ YY_USER_ACTION /** The main scanner function which does all the work. */ YY_DECL { register yy_state_type yy_current_state; register char *yy_cp, *yy_bp; register int yy_act; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; #line 25 "predicate_lexer.l" #line 721 "predicate_lexer.c" yylval = yylval_param; if ( !yyg->yy_init ) { yyg->yy_init = 1; #ifdef YY_USER_INIT YY_USER_INIT; #endif if ( ! yyg->yy_start ) yyg->yy_start = 1; /* first start state */ if ( ! yyin ) yyin = stdin; if ( ! yyout ) yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { Solidensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = Solid_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); } Solid_load_buffer_state(yyscanner ); } while ( 1 ) /* loops until end-of-file is reached */ { yy_cp = yyg->yy_c_buf_p; /* Support of yytext. */ *yy_cp = yyg->yy_hold_char; /* yy_bp points to the position in yy_ch_buf of the start of * the current run. */ yy_bp = yy_cp; yy_current_state = yyg->yy_start; yy_match: do { register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 41 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; ++yy_cp; } while ( yy_current_state != 40 ); yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; yy_find_action: yy_act = yy_accept[yy_current_state]; YY_DO_BEFORE_ACTION; do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ /* undo the effects of YY_DO_BEFORE_ACTION */ *yy_cp = yyg->yy_hold_char; yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; goto yy_find_action; case 1: YY_RULE_SETUP #line 27 "predicate_lexer.l" { return EQ; } YY_BREAK case 2: YY_RULE_SETUP #line 28 "predicate_lexer.l" { return MASK; } YY_BREAK case 3: YY_RULE_SETUP #line 30 "predicate_lexer.l" { return AND; } YY_BREAK case 4: YY_RULE_SETUP #line 31 "predicate_lexer.l" { return OR; } YY_BREAK case 5: YY_RULE_SETUP #line 32 "predicate_lexer.l" { return IS; } YY_BREAK case 6: YY_RULE_SETUP #line 34 "predicate_lexer.l" { yylval->valb = 1; return VAL_BOOL; } YY_BREAK case 7: YY_RULE_SETUP #line 35 "predicate_lexer.l" { yylval->valb = 0; return VAL_BOOL; } YY_BREAK case 8: /* rule 8 can match eol */ YY_RULE_SETUP #line 37 "predicate_lexer.l" { yylval->name = PredicateParse_putString( yytext ); return VAL_STRING; } YY_BREAK case 9: YY_RULE_SETUP #line 39 "predicate_lexer.l" { yylval->vali = atoi( yytext ); return VAL_NUM; } YY_BREAK case 10: YY_RULE_SETUP #line 40 "predicate_lexer.l" { yylval->vali = atoi( yytext ); return VAL_NUM; } YY_BREAK case 11: YY_RULE_SETUP #line 42 "predicate_lexer.l" { yylval->vald = atof( yytext ); return VAL_FLOAT; } YY_BREAK case 12: YY_RULE_SETUP #line 44 "predicate_lexer.l" { yylval->name = PredicateParse_putSymbol( yytext ); return VAL_ID; } YY_BREAK case 13: YY_RULE_SETUP #line 46 "predicate_lexer.l" { yylval->name = 0; return (int)(*yytext); } YY_BREAK case 14: /* rule 14 can match eol */ YY_RULE_SETUP #line 48 "predicate_lexer.l" /* eat up whitespace */ YY_BREAK case 15: YY_RULE_SETUP #line 50 "predicate_lexer.l" { PredicateLexer_unknownToken(yytext); } YY_BREAK case 16: YY_RULE_SETUP #line 52 "predicate_lexer.l" ECHO; YY_BREAK #line 884 "predicate_lexer.c" case YY_STATE_EOF(INITIAL): yyterminate(); case YY_END_OF_BUFFER: { /* Amount of text matched not including the EOB char. */ int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; /* Undo the effects of YY_DO_BEFORE_ACTION. */ *yy_cp = yyg->yy_hold_char; YY_RESTORE_YY_MORE_OFFSET if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) { /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called * Solidlex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a * back-up) that will match for the new input source. */ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; } /* Note that here we test for yy_c_buf_p "<=" to the position * of the first EOB in the buffer, since yy_c_buf_p will * already have been incremented past the NUL character * (since all states make transitions on EOB to the * end-of-buffer state). Contrast this with the test * in input(). */ if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) { /* This was really a NUL. */ yy_state_type yy_next_state; yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); /* Okay, we're now positioned to make the NUL * transition. We couldn't have * yy_get_previous_state() go ahead and do it * for us because it doesn't know how to deal * with the possibility of jamming (and we don't * want to build jamming into it because then it * will run more slowly). */ yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; if ( yy_next_state ) { /* Consume the NUL. */ yy_cp = ++yyg->yy_c_buf_p; yy_current_state = yy_next_state; goto yy_match; } else { yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; goto yy_find_action; } } else switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_END_OF_FILE: { yyg->yy_did_buffer_switch_on_eof = 0; if ( Solidwrap(yyscanner ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up * yytext, we can now set up * yy_c_buf_p so that if some total * hoser (like flex itself) wants to * call the scanner after we return the * YY_NULL, it'll still work - another * YY_NULL will get returned. */ yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; yy_act = YY_STATE_EOF(YY_START); goto do_action; } else { if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; } break; } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_match; case EOB_ACT_LAST_MATCH: yyg->yy_c_buf_p = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_find_action; } break; } default: YY_FATAL_ERROR( "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ } /* end of Solidlex */ /* yy_get_next_buffer - try to read in a new buffer * * Returns a code representing an action: * EOB_ACT_LAST_MATCH - * EOB_ACT_CONTINUE_SCAN - continue scanning from current position * EOB_ACT_END_OF_FILE - end of file */ static int yy_get_next_buffer (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; register char *source = yyg->yytext_ptr; register int number_to_move, i; int ret_val; if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) YY_FATAL_ERROR( "fatal flex scanner internal error--end of buffer missed" ); if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) { /* Don't try to fill the buffer, so this is an EOF. */ if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) { /* We matched a single character, the EOB, so * treat this as a final EOF. */ return EOB_ACT_END_OF_FILE; } else { /* We matched some text prior to the EOB, first * process it. */ return EOB_ACT_LAST_MATCH; } } /* Try to read more data. */ /* First move last chars to start of buffer. */ number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; else { int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER; int yy_c_buf_p_offset = (int) (yyg->yy_c_buf_p - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; else b->yy_buf_size *= 2; b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ Solidrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); } else /* Can't grow it, we don't own it. */ b->yy_ch_buf = 0; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "fatal error - scanner input buffer overflow" ); yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; } if ( num_to_read > YY_READ_BUF_SIZE ) num_to_read = YY_READ_BUF_SIZE; /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), yyg->yy_n_chars, (size_t) num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } if ( yyg->yy_n_chars == 0 ) { if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; Solidrestart(yyin ,yyscanner); } else { ret_val = EOB_ACT_LAST_MATCH; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_EOF_PENDING; } } else ret_val = EOB_ACT_CONTINUE_SCAN; if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) Solidrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); } yyg->yy_n_chars += number_to_move; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; return ret_val; } /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (yyscan_t yyscanner) { register yy_state_type yy_current_state; register char *yy_cp; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_current_state = yyg->yy_start; for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) { register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 41 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; } return yy_current_state; } /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis * next_state = yy_try_NUL_trans( current_state ); */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) { register int yy_is_jam; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ register char *yy_cp = yyg->yy_c_buf_p; register YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 41 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; yy_is_jam = (yy_current_state == 40); return yy_is_jam ? 0 : yy_current_state; } #if 0 /* UNUSED */ static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner) { register char *yy_cp; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_cp = yyg->yy_c_buf_p; /* undo effects of setting up yytext */ *yy_cp = yyg->yy_hold_char; if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) { /* need to shift things up to make room */ /* +2 for EOB chars. */ register int number_to_move = yyg->yy_n_chars + 2; register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; register char *source = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) *--dest = *--source; yy_cp += (int) (dest - source); yy_bp += (int) (dest - source); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) YY_FATAL_ERROR( "flex scanner push-back overflow" ); } *--yy_cp = (char) c; yyg->yytext_ptr = yy_bp; yyg->yy_hold_char = *yy_cp; yyg->yy_c_buf_p = yy_cp; } #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (yyscan_t yyscanner) #else static int input (yyscan_t yyscanner) #endif { int c; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; *yyg->yy_c_buf_p = yyg->yy_hold_char; if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) { /* yy_c_buf_p now points to the character we want to return. * If this occurs *before* the EOB characters, then it's a * valid NUL; if not, then we've hit the end of the buffer. */ if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) /* This was really a NUL. */ *yyg->yy_c_buf_p = '\0'; else { /* need more input */ int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; ++yyg->yy_c_buf_p; switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_LAST_MATCH: /* This happens because yy_g_n_b() * sees that we've accumulated a * token and flags that we need to * try matching the token before * proceeding. But for input(), * there's no matching to consider. * So convert the EOB_ACT_LAST_MATCH * to EOB_ACT_END_OF_FILE. */ /* Reset buffer status. */ Solidrestart(yyin ,yyscanner); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { if ( Solidwrap(yyscanner ) ) return EOF; if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; #ifdef __cplusplus return yyinput(yyscanner); #else return input(yyscanner); #endif } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + offset; break; } } } c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ yyg->yy_hold_char = *++yyg->yy_c_buf_p; return c; } #endif /* ifndef YY_NO_INPUT */ #endif /* UNUSED */ /** Immediately switch to a different input stream. * @param input_file A readable stream. * @param yyscanner The scanner object. * @note This function does not reset the start condition to @c INITIAL . */ void Solidrestart (FILE * input_file , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! YY_CURRENT_BUFFER ){ Solidensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = Solid_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); } Solid_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); Solid_load_buffer_state(yyscanner ); } /** Switch to a different input buffer. * @param new_buffer The new input buffer. * @param yyscanner The scanner object. */ void Solid_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* TODO. We should be able to replace this entire function body * with * Solidpop_buffer_state(); * Solidpush_buffer_state(new_buffer); */ Solidensure_buffer_stack (yyscanner); if ( YY_CURRENT_BUFFER == new_buffer ) return; if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } YY_CURRENT_BUFFER_LVALUE = new_buffer; Solid_load_buffer_state(yyscanner ); /* We don't actually know whether we did this switch during * EOF (Solidwrap()) processing, but the only time this flag * is looked at is after Solidwrap() is called, so it's safe * to go ahead and always set it. */ yyg->yy_did_buffer_switch_on_eof = 1; } static void Solid_load_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; yyg->yy_hold_char = *yyg->yy_c_buf_p; } /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. * @param yyscanner The scanner object. * @return the allocated buffer state. */ YY_BUFFER_STATE Solid_create_buffer (FILE * file, int size , yyscan_t yyscanner) { YY_BUFFER_STATE b; b = (YY_BUFFER_STATE) Solidalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in Solid_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) Solidalloc(b->yy_buf_size + 2 ,yyscanner ); if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in Solid_create_buffer()" ); b->yy_is_our_buffer = 1; Solid_init_buffer(b,file ,yyscanner); return b; } /** Destroy the buffer. * @param b a buffer created with Solid_create_buffer() * @param yyscanner The scanner object. */ void Solid_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) Solidfree((void *) b->yy_ch_buf ,yyscanner ); Solidfree((void *) b ,yyscanner ); } /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a Solidrestart() or at EOF. */ static void Solid_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) { int oerrno = errno; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; Solid_flush_buffer(b ,yyscanner); b->yy_input_file = file; b->yy_fill_buffer = 1; /* If b is the current buffer, then Solid_init_buffer was _probably_ * called from Solidrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ b->yy_bs_lineno = 1; b->yy_bs_column = 0; } b->yy_is_interactive = 0; errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * @param yyscanner The scanner object. */ void Solid_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; b->yy_n_chars = 0; /* We always need two end-of-buffer characters. The first causes * a transition to the end-of-buffer state. The second causes * a jam in that state. */ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; b->yy_buf_pos = &b->yy_ch_buf[0]; b->yy_at_bol = 1; b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) Solid_load_buffer_state(yyscanner ); } /** Pushes the new state onto the stack. The new state becomes * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. * @param yyscanner The scanner object. */ void Solidpush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (new_buffer == NULL) return; Solidensure_buffer_stack(yyscanner); /* This block is copied from Solid_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } /* Only push if top exists. Otherwise, replace top. */ if (YY_CURRENT_BUFFER) yyg->yy_buffer_stack_top++; YY_CURRENT_BUFFER_LVALUE = new_buffer; /* copied from Solid_switch_to_buffer. */ Solid_load_buffer_state(yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * @param yyscanner The scanner object. */ void Solidpop_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!YY_CURRENT_BUFFER) return; Solid_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); YY_CURRENT_BUFFER_LVALUE = NULL; if (yyg->yy_buffer_stack_top > 0) --yyg->yy_buffer_stack_top; if (YY_CURRENT_BUFFER) { Solid_load_buffer_state(yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } } /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ static void Solidensure_buffer_stack (yyscan_t yyscanner) { int num_to_alloc; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!yyg->yy_buffer_stack) { /* First allocation is just for 2 elements, since we don't know if this * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ num_to_alloc = 1; yyg->yy_buffer_stack = (struct yy_buffer_state**)Solidalloc (num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in Solidensure_buffer_stack()" ); memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; yyg->yy_buffer_stack_top = 0; return; } if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ /* Increase the buffer to prepare for a possible push. */ int grow_size = 8 /* arbitrary grow size */; num_to_alloc = yyg->yy_buffer_stack_max + grow_size; yyg->yy_buffer_stack = (struct yy_buffer_state**)Solidrealloc (yyg->yy_buffer_stack, num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in Solidensure_buffer_stack()" ); /* zero only the new slots.*/ memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; } } /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE Solid_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) { YY_BUFFER_STATE b; if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ return 0; b = (YY_BUFFER_STATE) Solidalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in Solid_scan_buffer()" ); b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; b->yy_input_file = 0; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; Solid_switch_to_buffer(b ,yyscanner ); return b; } /** Setup the input buffer state to scan a string. The next call to Solidlex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * @param yyscanner The scanner object. * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * Solid_scan_bytes() instead. */ YY_BUFFER_STATE Solid_scan_string (yyconst char * yystr , yyscan_t yyscanner) { return Solid_scan_bytes(yystr,strlen(yystr) ,yyscanner); } /** Setup the input buffer state to scan the given bytes. The next call to Solidlex() will * scan from a @e copy of @a bytes. * @param yybytes the byte buffer to scan * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE Solid_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) { YY_BUFFER_STATE b; char *buf; yy_size_t n; int i; /* Get memory for full buffer, including space for trailing EOB's. */ n = _yybytes_len + 2; buf = (char *) Solidalloc(n ,yyscanner ); if ( ! buf ) YY_FATAL_ERROR( "out of dynamic memory in Solid_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; b = Solid_scan_buffer(buf,n ,yyscanner); if ( ! b ) YY_FATAL_ERROR( "bad buffer in Solid_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. */ b->yy_is_our_buffer = 1; return b; } #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) { (void)yyscanner; /* UNUSED */ (void) fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } /* Redefine yyless() so it works in section 3 code. */ #undef yyless #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = yyg->yy_hold_char; \ yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ yyg->yy_hold_char = *yyg->yy_c_buf_p; \ *yyg->yy_c_buf_p = '\0'; \ yyleng = yyless_macro_arg; \ } \ while ( 0 ) /* Accessor methods (get/set functions) to struct members. */ /** Get the user-defined data for this scanner. * @param yyscanner The scanner object. */ YY_EXTRA_TYPE Solidget_extra (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyextra; } /** Get the current line number. * @param yyscanner The scanner object. */ int Solidget_lineno (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yylineno; } /** Get the current column number. * @param yyscanner The scanner object. */ int Solidget_column (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yycolumn; } /** Get the input stream. * @param yyscanner The scanner object. */ FILE *Solidget_in (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyin; } /** Get the output stream. * @param yyscanner The scanner object. */ FILE *Solidget_out (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyout; } /** Get the length of the current token. * @param yyscanner The scanner object. */ int Solidget_leng (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyleng; } /** Get the current token. * @param yyscanner The scanner object. */ char *Solidget_text (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yytext; } /** Set the user-defined data. This data is never touched by the scanner. * @param user_defined The data to be associated with this scanner. * @param yyscanner The scanner object. */ void Solidset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyextra = user_defined ; } /** Set the current line number. * @param line_number * @param yyscanner The scanner object. */ void Solidset_lineno (int line_number , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* lineno is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) yy_fatal_error( "Solidset_lineno called with no buffer" , yyscanner); yylineno = line_number; } /** Set the current column. * @param line_number * @param yyscanner The scanner object. */ void Solidset_column (int column_no , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* column is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) yy_fatal_error( "Solidset_column called with no buffer" , yyscanner); yycolumn = column_no; } /** Set the input stream. This does not discard the current * input buffer. * @param in_str A readable stream. * @param yyscanner The scanner object. * @see Solid_switch_to_buffer */ void Solidset_in (FILE * in_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyin = in_str ; } void Solidset_out (FILE * out_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyout = out_str ; } int Solidget_debug (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yy_flex_debug; } void Solidset_debug (int bdebug , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_flex_debug = bdebug ; } /* Accessor methods for yylval and yylloc */ YYSTYPE * Solidget_lval (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yylval; } void Solidset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylval = yylval_param; } /* User-visible API */ /* Solidlex_init is special because it creates the scanner itself, so it is * the ONLY reentrant function that doesn't take the scanner as the last argument. * That's why we explicitly handle the declaration, instead of using our macros. */ int Solidlex_init(yyscan_t* ptr_yy_globals) { if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) Solidalloc ( sizeof( struct yyguts_t ), NULL ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); return yy_init_globals ( *ptr_yy_globals ); } /* Solidlex_init_extra has the same functionality as Solidlex_init, but follows the * convention of taking the scanner as the last argument. Note however, that * this is a *pointer* to a scanner, as it will be allocated by this call (and * is the reason, too, why this function also must handle its own declaration). * The user defined value in the first argument will be available to Solidalloc in * the yyextra field. */ int Solidlex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals ) { struct yyguts_t dummy_yyguts; Solidset_extra (yy_user_defined, &dummy_yyguts); if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) Solidalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); Solidset_extra (yy_user_defined, *ptr_yy_globals); return yy_init_globals ( *ptr_yy_globals ); } static int yy_init_globals (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Initialization is the same as for the non-reentrant scanner. * This function is called from Solidlex_destroy(), so don't allocate here. */ yyg->yy_buffer_stack = 0; yyg->yy_buffer_stack_top = 0; yyg->yy_buffer_stack_max = 0; yyg->yy_c_buf_p = (char *) 0; yyg->yy_init = 0; yyg->yy_start = 0; yyg->yy_start_stack_ptr = 0; yyg->yy_start_stack_depth = 0; yyg->yy_start_stack = NULL; /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else yyin = (FILE *) 0; yyout = (FILE *) 0; #endif /* For future reference: Set errno on error, since we are called by * Solidlex_init() */ return 0; } /* Solidlex_destroy is for both reentrant and non-reentrant scanners. */ int Solidlex_destroy (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ Solid_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); YY_CURRENT_BUFFER_LVALUE = NULL; Solidpop_buffer_state(yyscanner); } /* Destroy the stack itself. */ Solidfree(yyg->yy_buffer_stack ,yyscanner); yyg->yy_buffer_stack = NULL; /* Destroy the start condition stack. */ Solidfree(yyg->yy_start_stack ,yyscanner ); yyg->yy_start_stack = NULL; /* Reset the globals. This is important in a non-reentrant scanner so the next time * Solidlex() is called, initialization will occur. */ yy_init_globals( yyscanner); /* Destroy the main struct (reentrant only). */ Solidfree ( yyscanner , yyscanner ); yyscanner = NULL; return 0; } /* * Internal utility routines. */ #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) { register int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) { register int n; for ( n = 0; s[n]; ++n ) ; return n; } #endif void *Solidalloc (yy_size_t size , yyscan_t yyscanner) { (void)yyscanner; /* UNUSED */ return (void *) malloc( size ); } void *Solidrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) { (void)yyscanner; /* UNUSED */ /* The cast to (char *) in the following accommodates both * implementations that use char* generic pointers, and those * that use void* generic pointers. It works with the latter * because both ANSI C and C++ allow castless assignment from * any pointer type to void*, and deal with argument conversions * as though doing an assignment. */ return (void *) realloc( (char *) ptr, size ); } void Solidfree (void * ptr , yyscan_t yyscanner) { (void)yyscanner; /* UNUSED */ free( (char *) ptr ); /* see Solidrealloc() for (char *) cast */ } #define YYTABLES_NAME "yytables" #line 52 "predicate_lexer.l" char *PredicateParse_putSymbol( char *_name ) { char *p = (char*)malloc( strlen( _name ) + 1 ); if (p != NULL) { strcpy( p, _name ); } return p; } char *PredicateParse_putString( char *_str ) { int l = strlen( _str ); char *p = (char*)malloc( l ); char *s = _str + 1; char *d = p; if (p == NULL) return NULL; while ( s != _str + l - 1 ) { if ( *s != '\\' ) *d++ = *s++; else { s++; if ( s != _str + l - 1 ) { if ( *s == '\\' ) *d++ = '\\'; else if ( *s == 'n' ) *d++ = '\n'; else if ( *s == 'r' ) *d++ = '\r'; else if ( *s == 't' ) *d++ = '\t'; s++; } } } *d = 0; return p; } void PredicateParse_initLexer( const char *_code, yyscan_t _scanner ) { Solid_switch_to_buffer( Solid_scan_string( _code, _scanner ), _scanner ); } int Solidwrap( yyscan_t _scanner ) { struct yyguts_t *yyg = (struct yyguts_t*)_scanner; Solid_delete_buffer( YY_CURRENT_BUFFER, _scanner ); return 1; } cantata-2.2.0/3rdparty/solid-lite/predicate_lexer.l000066400000000000000000000047511316350454000222670ustar00rootroot00000000000000%{ #include "predicate_parser.h" #include "predicateparse.h" #include #include #define YY_NO_UNPUT int Solidwrap( yyscan_t _scanner ); void PredicateParse_initLexer( const char *_code, yyscan_t _scanner ); char *PredicateParse_putSymbol( char *_name ); char *PredicateParse_putString( char *_str ); void PredicateParse_initFlex( const char *_code, yyscan_t _scanner ); %} %option nomain %option never-interactive %option noalways-interactive %option nostack %option reentrant %option bison-bridge DIGIT [0-9] %% "==" { return EQ; } "&" { return MASK; } [aA][nN][dD] { return AND; } [oO][rR] { return OR; } [iI][sS] { return IS; } [tT][rR][uU][eE] { yylval->valb = 1; return VAL_BOOL; } [fF][aA][lL][sS][eE] { yylval->valb = 0; return VAL_BOOL; } "'"[^']*"'" { yylval->name = PredicateParse_putString( yytext ); return VAL_STRING; } "-"{DIGIT}+ { yylval->vali = atoi( yytext ); return VAL_NUM; } {DIGIT}+ { yylval->vali = atoi( yytext ); return VAL_NUM; } {DIGIT}*"\."{DIGIT}+ { yylval->vald = atof( yytext ); return VAL_FLOAT; } [a-zA-Z][a-zA-Z0-9\-]* { yylval->name = PredicateParse_putSymbol( yytext ); return VAL_ID; } "{"|"}"|"["|"]"|","|"\." { yylval->name = 0; return (int)(*yytext); } [ \t\n]+ /* eat up whitespace */ . { PredicateLexer_unknownToken(yytext); } %% char *PredicateParse_putSymbol( char *_name ) { char *p = (char*)malloc( strlen( _name ) + 1 ); if (p != NULL) { strcpy( p, _name ); } return p; } char *PredicateParse_putString( char *_str ) { int l = strlen( _str ); char *p = (char*)malloc( l ); char *s = _str + 1; char *d = p; if (p == NULL) return NULL; while ( s != _str + l - 1 ) { if ( *s != '\\' ) *d++ = *s++; else { s++; if ( s != _str + l - 1 ) { if ( *s == '\\' ) *d++ = '\\'; else if ( *s == 'n' ) *d++ = '\n'; else if ( *s == 'r' ) *d++ = '\r'; else if ( *s == 't' ) *d++ = '\t'; s++; } } } *d = 0; return p; } void PredicateParse_initLexer( const char *_code, yyscan_t _scanner ) { Solid_switch_to_buffer( Solid_scan_string( _code, _scanner ), _scanner ); } int Solidwrap( yyscan_t _scanner ) { struct yyguts_t *yyg = (struct yyguts_t*)_scanner; Solid_delete_buffer( YY_CURRENT_BUFFER, _scanner ); return 1; } cantata-2.2.0/3rdparty/solid-lite/predicate_parser.c000066400000000000000000001344331316350454000224340ustar00rootroot00000000000000 /* A Bison parser, made by GNU Bison 2.4.1. */ /* Skeleton implementation for Bison's Yacc-like parsers in C Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ #define YYBISON_VERSION "2.4.1" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 1 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Using locations. */ #define YYLSP_NEEDED 0 /* Substitute the variable and function names. */ #define yyparse Solidparse #define yylex Solidlex #define yyerror Soliderror #define yylval Solidlval #define yychar Solidchar #define yydebug Soliddebug #define yynerrs Solidnerrs /* Copy the first part of user declarations. */ /* Line 189 of yacc.c */ #line 1 "predicate_parser.y" #include #include #include "predicate_parser.h" #include "predicateparse.h" #define YYLTYPE_IS_TRIVIAL 0 #define YYENABLE_NLS 0 #define YYLEX_PARAM scanner #define YYPARSE_PARAM scanner typedef void* yyscan_t; void Soliderror(const char *s); int Solidlex( YYSTYPE *yylval, yyscan_t scanner ); int Solidlex_init( yyscan_t *scanner ); int Solidlex_destroy( yyscan_t *scanner ); void PredicateParse_initLexer( const char *s, yyscan_t scanner ); void PredicateParse_mainParse( const char *_code ); /* Line 189 of yacc.c */ #line 102 "predicate_parser.tab.c" /* Enabling traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE # undef YYERROR_VERBOSE # define YYERROR_VERBOSE 1 #else # define YYERROR_VERBOSE 0 #endif /* Enabling the token table. */ #ifndef YYTOKEN_TABLE # define YYTOKEN_TABLE 0 #endif /* Tokens. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE /* Put the tokens into the symbol table, so that GDB and other debuggers know about them. */ enum yytokentype { EQ = 258, MASK = 259, AND = 260, OR = 261, IS = 262, VAL_BOOL = 263, VAL_STRING = 264, VAL_ID = 265, VAL_NUM = 266, VAL_FLOAT = 267 }; #endif #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef union YYSTYPE { /* Line 214 of yacc.c */ #line 22 "predicate_parser.y" char valb; int vali; double vald; char *name; void *ptr; /* Line 214 of yacc.c */ #line 160 "predicate_parser.tab.c" } YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 #endif /* Copy the second part of user declarations. */ /* Line 264 of yacc.c */ #line 172 "predicate_parser.tab.c" #ifdef short # undef short #endif #ifdef YYTYPE_UINT8 typedef YYTYPE_UINT8 yytype_uint8; #else typedef unsigned char yytype_uint8; #endif #ifdef YYTYPE_INT8 typedef YYTYPE_INT8 yytype_int8; #elif (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) typedef signed char yytype_int8; #else typedef short int yytype_int8; #endif #ifdef YYTYPE_UINT16 typedef YYTYPE_UINT16 yytype_uint16; #else typedef unsigned short int yytype_uint16; #endif #ifdef YYTYPE_INT16 typedef YYTYPE_INT16 yytype_int16; #else typedef short int yytype_int16; #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned int # endif #endif #define YYSIZE_MAXIMUM ((YYSIZE_T) -1) #ifndef YY_ # if YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(msgid) dgettext ("bison-runtime", msgid) # endif # endif # ifndef YY_ # define YY_(msgid) msgid # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YYUSE(e) ((void) (e)) #else # define YYUSE(e) /* empty */ #endif /* Identity function, used to suppress warnings about constant conditions. */ #ifndef lint # define YYID(n) (n) #else #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static int YYID (int yyi) #else static int YYID (yyi) int yyi; #endif { return yyi; } #endif #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) # include /* INFRINGES ON USER NAME SPACE */ # ifndef _STDLIB_H # define _STDLIB_H 1 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's `empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined _STDLIB_H \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef _STDLIB_H # define _STDLIB_H 1 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* ! defined yyoverflow || YYERROR_VERBOSE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yytype_int16 yyss_alloc; YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) /* Copy COUNT objects from FROM to TO. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(To, From, Count) \ __builtin_memcpy (To, From, (Count) * sizeof (*(From))) # else # define YYCOPY(To, From, Count) \ do \ { \ YYSIZE_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (To)[yyi] = (From)[yyi]; \ } \ while (YYID (0)) # endif # endif /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYSIZE_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / sizeof (*yyptr); \ } \ while (YYID (0)) #endif /* YYFINAL -- State number of the termination state. */ #define YYFINAL 11 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 29 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 19 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 8 /* YYNRULES -- Number of rules. */ #define YYNRULES 18 /* YYNRULES -- Number of states. */ #define YYNSTATES 34 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ #define YYUNDEFTOK 2 #define YYMAXUTOK 267 #define YYTRANSLATE(YYX) \ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 18, 2, 15, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 13, 2, 14, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 16, 2, 17, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; #if YYDEBUG /* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in YYRHS. */ static const yytype_uint8 yyprhs[] = { 0, 0, 3, 5, 9, 13, 19, 25, 28, 32, 36, 38, 40, 42, 44, 46, 50, 51, 53 }; /* YYRHS -- A `-1'-separated list of the rules' RHS. */ static const yytype_int8 yyrhs[] = { 20, 0, -1, 21, -1, 13, 22, 14, -1, 13, 23, 14, -1, 10, 15, 10, 3, 24, -1, 10, 15, 10, 4, 24, -1, 7, 10, -1, 20, 6, 20, -1, 20, 5, 20, -1, 9, -1, 8, -1, 11, -1, 12, -1, 25, -1, 16, 26, 17, -1, -1, 9, -1, 9, 18, 26, -1 }; /* YYRLINE[YYN] -- source line where rule number YYN was defined. */ static const yytype_uint8 yyrline[] = { 0, 60, 60, 61, 62, 64, 65, 66, 68, 70, 72, 73, 74, 75, 76, 78, 80, 81, 82 }; #endif #if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "$end", "error", "$undefined", "EQ", "MASK", "AND", "OR", "IS", "VAL_BOOL", "VAL_STRING", "VAL_ID", "VAL_NUM", "VAL_FLOAT", "'['", "']'", "'.'", "'{'", "'}'", "','", "$accept", "predicate", "predicate_atom", "predicate_or", "predicate_and", "value", "string_list", "string_list_rec", 0 }; #endif # ifdef YYPRINT /* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to token YYLEX-NUM. */ static const yytype_uint16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 91, 93, 46, 123, 125, 44 }; # endif /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_uint8 yyr1[] = { 0, 19, 20, 20, 20, 21, 21, 21, 22, 23, 24, 24, 24, 24, 24, 25, 26, 26, 26 }; /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ static const yytype_uint8 yyr2[] = { 0, 2, 1, 3, 3, 5, 5, 2, 3, 3, 1, 1, 1, 1, 1, 3, 0, 1, 3 }; /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state STATE-NUM when YYTABLE doesn't specify something else to do. Zero means the default is an error. */ static const yytype_uint8 yydefact[] = { 0, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 1, 0, 0, 0, 3, 4, 0, 0, 9, 8, 11, 10, 12, 13, 16, 5, 14, 6, 17, 0, 16, 15, 18 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int8 yydefgoto[] = { -1, 4, 5, 9, 10, 26, 27, 30 }; /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ #define YYPACT_NINF -13 static const yytype_int8 yypact[] = { 5, -2, -12, 5, 16, -13, -13, 7, 1, 6, 8, -13, 10, 5, 5, -13, -13, -7, -7, -13, -13, -13, -13, -13, -13, 12, -13, -13, -13, 9, 2, 12, -13, -13 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int8 yypgoto[] = { -13, -3, -13, -13, -13, 11, -13, -8 }; /* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule which number is the opposite. If zero, do what YYDEFACT says. If YYTABLE_NINF, syntax error. */ #define YYTABLE_NINF -1 static const yytype_uint8 yytable[] = { 8, 21, 22, 7, 23, 24, 13, 14, 6, 25, 19, 20, 1, 17, 18, 2, 11, 12, 3, 32, 15, 29, 16, 33, 0, 0, 0, 31, 0, 28 }; static const yytype_int8 yycheck[] = { 3, 8, 9, 15, 11, 12, 5, 6, 10, 16, 13, 14, 7, 3, 4, 10, 0, 10, 13, 17, 14, 9, 14, 31, -1, -1, -1, 18, -1, 18 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_uint8 yystos[] = { 0, 7, 10, 13, 20, 21, 10, 15, 20, 22, 23, 0, 10, 5, 6, 14, 14, 3, 4, 20, 20, 8, 9, 11, 12, 16, 24, 25, 24, 9, 26, 18, 17, 26 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY (-2) #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab /* Like YYERROR except do call yyerror. This remains here temporarily to ease the transition to the new meaning of YYERROR, for GCC. Once GCC version 2 has supplanted version 1, this can go. */ #define YYFAIL goto yyerrlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY && yylen == 1) \ { \ yychar = (Token); \ yylval = (Value); \ yytoken = YYTRANSLATE (yychar); \ YYPOPSTACK (1); \ goto yybackup; \ } \ else \ { \ yyerror (YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (YYID (0)) #define YYTERROR 1 #define YYERRCODE 256 /* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. If N is 0, then set CURRENT to the empty location which ends the previous symbol: RHS[0] (always defined). */ #define YYRHSLOC(Rhs, K) ((Rhs)[K]) #ifndef YYLLOC_DEFAULT # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (YYID (N)) \ { \ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ } \ else \ { \ (Current).first_line = (Current).last_line = \ YYRHSLOC (Rhs, 0).last_line; \ (Current).first_column = (Current).last_column = \ YYRHSLOC (Rhs, 0).last_column; \ } \ while (YYID (0)) #endif /* YY_LOCATION_PRINT -- Print the location on the stream. This macro was not mandated originally: define only if we know we won't break user code: when these are the locations we know. */ #ifndef YY_LOCATION_PRINT # if YYLTYPE_IS_TRIVIAL # define YY_LOCATION_PRINT(File, Loc) \ fprintf (File, "%d.%d-%d.%d", \ (Loc).first_line, (Loc).first_column, \ (Loc).last_line, (Loc).last_column) # else # define YY_LOCATION_PRINT(File, Loc) ((void) 0) # endif #endif /* YYLEX -- calling `yylex' with the right arguments. */ #ifdef YYLEX_PARAM # define YYLEX yylex (&yylval, YYLEX_PARAM) #else # define YYLEX yylex (&yylval) #endif /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (YYID (0)) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Type, Value); \ YYFPRINTF (stderr, "\n"); \ } \ } while (YYID (0)) /*--------------------------------. | Print this symbol on YYOUTPUT. | `--------------------------------*/ /*ARGSUSED*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) #else static void yy_symbol_value_print (yyoutput, yytype, yyvaluep) FILE *yyoutput; int yytype; YYSTYPE const * const yyvaluep; #endif { if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); # else YYUSE (yyoutput); # endif switch (yytype) { default: break; } } /*--------------------------------. | Print this symbol on YYOUTPUT. | `--------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) #else static void yy_symbol_print (yyoutput, yytype, yyvaluep) FILE *yyoutput; int yytype; YYSTYPE const * const yyvaluep; #endif { if (yytype < YYNTOKENS) YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); else YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); yy_symbol_value_print (yyoutput, yytype, yyvaluep); YYFPRINTF (yyoutput, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) #else static void yy_stack_print (yybottom, yytop) yytype_int16 *yybottom; yytype_int16 *yytop; #endif { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (YYID (0)) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_reduce_print (YYSTYPE *yyvsp, int yyrule) #else static void yy_reduce_print (yyvsp, yyrule) YYSTYPE *yyvsp; int yyrule; #endif { int yynrhs = yyr2[yyrule]; int yyi; unsigned long int yylno = yyrline[yyrule]; YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], &(yyvsp[(yyi + 1) - (yynrhs)]) ); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyvsp, Rule); \ } while (YYID (0)) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen strlen # else /* Return the length of YYSTR. */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static YYSIZE_T yystrlen (const char *yystr) #else static YYSIZE_T yystrlen (yystr) const char *yystr; #endif { YYSIZE_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif # endif # ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static char * yystpcpy (char *yydest, const char *yysrc) #else static char * yystpcpy (yydest, yysrc) char *yydest; const char *yysrc; #endif { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif # endif # ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYSIZE_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYSIZE_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; /* Fall through. */ default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (! yyres) return yystrlen (yystr); return yystpcpy (yyres, yystr) - yyres; } # endif /* Copy into YYRESULT an error message about the unexpected token YYCHAR while in state YYSTATE. Return the number of bytes copied, including the terminating null byte. If YYRESULT is null, do not copy anything; just return the number of bytes that would be copied. As a special case, return 0 if an ordinary "syntax error" message will do. Return YYSIZE_MAXIMUM if overflow occurs during size calculation. */ static YYSIZE_T yysyntax_error (char *yyresult, int yystate, int yychar) { int yyn = yypact[yystate]; if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) return 0; else { int yytype = YYTRANSLATE (yychar); YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); YYSIZE_T yysize = yysize0; YYSIZE_T yysize1; int yysize_overflow = 0; enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; int yyx; # if 0 /* This is so xgettext sees the translatable formats that are constructed on the fly. */ YY_("syntax error, unexpected %s"); YY_("syntax error, unexpected %s, expecting %s"); YY_("syntax error, unexpected %s, expecting %s or %s"); YY_("syntax error, unexpected %s, expecting %s or %s or %s"); YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); # endif char *yyfmt; char const *yyf; static char const yyunexpected[] = "syntax error, unexpected %s"; static char const yyexpecting[] = ", expecting %s"; static char const yyor[] = " or %s"; char yyformat[sizeof yyunexpected + sizeof yyexpecting - 1 + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) * (sizeof yyor - 1))]; char const *yyprefix = yyexpecting; /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yycount = 1; yyarg[0] = yytname[yytype]; yyfmt = yystpcpy (yyformat, yyunexpected); for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) { if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) { yycount = 1; yysize = yysize0; yyformat[sizeof yyunexpected - 1] = '\0'; break; } yyarg[yycount++] = yytname[yyx]; yysize1 = yysize + yytnamerr (0, yytname[yyx]); yysize_overflow |= (yysize1 < yysize); yysize = yysize1; yyfmt = yystpcpy (yyfmt, yyprefix); yyprefix = yyor; } yyf = YY_(yyformat); yysize1 = yysize + yystrlen (yyf); yysize_overflow |= (yysize1 < yysize); yysize = yysize1; if (yysize_overflow) return YYSIZE_MAXIMUM; if (yyresult) { /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ char *yyp = yyresult; int yyi = 0; while ((*yyp = *yyf) != '\0') { if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yyarg[yyi++]); yyf += 2; } else { yyp++; yyf++; } } } return yysize; } } #endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ /*ARGSUSED*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) #else static void yydestruct (yymsg, yytype, yyvaluep) const char *yymsg; int yytype; YYSTYPE *yyvaluep; #endif { YYUSE (yyvaluep); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); switch (yytype) { case 20: /* "predicate" */ /* Line 1000 of yacc.c */ #line 51 "predicate_parser.y" { PredicateParse_destroy( (yyvaluep->ptr) ); }; /* Line 1000 of yacc.c */ #line 1080 "predicate_parser.tab.c" break; case 21: /* "predicate_atom" */ /* Line 1000 of yacc.c */ #line 52 "predicate_parser.y" { PredicateParse_destroy( (yyvaluep->ptr) ); }; /* Line 1000 of yacc.c */ #line 1089 "predicate_parser.tab.c" break; case 22: /* "predicate_or" */ /* Line 1000 of yacc.c */ #line 53 "predicate_parser.y" { PredicateParse_destroy( (yyvaluep->ptr) ); }; /* Line 1000 of yacc.c */ #line 1098 "predicate_parser.tab.c" break; case 23: /* "predicate_and" */ /* Line 1000 of yacc.c */ #line 54 "predicate_parser.y" { PredicateParse_destroy( (yyvaluep->ptr) ); }; /* Line 1000 of yacc.c */ #line 1107 "predicate_parser.tab.c" break; default: break; } } /* Prevent warnings from -Wmissing-prototypes. */ #ifdef YYPARSE_PARAM #if defined __STDC__ || defined __cplusplus int yyparse (void *YYPARSE_PARAM); #else int yyparse (); #endif #else /* ! YYPARSE_PARAM */ #if defined __STDC__ || defined __cplusplus int yyparse (void); #else int yyparse (); #endif #endif /* ! YYPARSE_PARAM */ /*-------------------------. | yyparse or yypush_parse. | `-------------------------*/ #ifdef YYPARSE_PARAM #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) int yyparse (void *YYPARSE_PARAM) #else int yyparse (YYPARSE_PARAM) void *YYPARSE_PARAM; #endif #else /* ! YYPARSE_PARAM */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) int yyparse (void) #else int yyparse () #endif #endif { /* The lookahead symbol. */ int yychar; /* The semantic value of the lookahead symbol. */ YYSTYPE yylval; /* Number of syntax errors so far. */ int yynerrs; int yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: `yyss': related to states. `yyvs': related to semantic values. Refer to the stacks thru separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ yytype_int16 yyssa[YYINITDEPTH]; yytype_int16 *yyss; yytype_int16 *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs; YYSTYPE *yyvsp; YYSIZE_T yystacksize; int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ int yytoken; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYSIZE_T yymsg_alloc = sizeof yymsgbuf; #endif #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; yytoken = 0; yyss = yyssa; yyvs = yyvsa; yystacksize = YYINITDEPTH; YYDPRINTF ((stderr, "Starting parse\n")); yystate = 0; yyerrstatus = 0; yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ /* Initialize stack pointers. Waste one element of value and location stack so that they stay on the same level as the state stack. The wasted elements are never initialized. */ yyssp = yyss; yyvsp = yyvs; goto yysetstate; /*------------------------------------------------------------. | yynewstate -- Push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; yysetstate: *yyssp = yystate; if (yyss + yystacksize - 1 <= yyssp) { /* Get the current used size of the three stacks, in elements. */ YYSIZE_T yysize = yyssp - yyss + 1; #ifdef yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ YYSTYPE *yyvs1 = yyvs; yytype_int16 *yyss1 = yyss; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * sizeof (*yyssp), &yyvs1, yysize * sizeof (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } #else /* no yyoverflow */ # ifndef YYSTACK_RELOCATE goto yyexhaustedlab; # else /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yytype_int16 *yyss1 = yyss; union yyalloc *yyptr = (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif #endif /* no yyoverflow */ yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YYDPRINTF ((stderr, "Stack size increased to %lu\n", (unsigned long int) yystacksize)); if (yyss + yystacksize - 1 <= yyssp) YYABORT; } YYDPRINTF ((stderr, "Entering state %d\n", yystate)); if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yyn == YYPACT_NINF) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); yychar = YYLEX; } if (yychar <= YYEOF) { yychar = yytoken = YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yyn == 0 || yyn == YYTABLE_NINF) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); /* Discard the shifted token. */ yychar = YYEMPTY; yystate = yyn; *++yyvsp = yylval; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- Do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: `$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { case 2: /* Line 1455 of yacc.c */ #line 60 "predicate_parser.y" { PredicateParse_setResult( (yyvsp[(1) - (1)].ptr) ); (yyval.ptr) = (yyvsp[(1) - (1)].ptr); ;} break; case 3: /* Line 1455 of yacc.c */ #line 61 "predicate_parser.y" { PredicateParse_setResult( (yyvsp[(2) - (3)].ptr) ); (yyval.ptr) = (yyvsp[(2) - (3)].ptr); ;} break; case 4: /* Line 1455 of yacc.c */ #line 62 "predicate_parser.y" { PredicateParse_setResult( (yyvsp[(2) - (3)].ptr) ); (yyval.ptr) = (yyvsp[(2) - (3)].ptr); ;} break; case 5: /* Line 1455 of yacc.c */ #line 64 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newAtom( (yyvsp[(1) - (5)].name), (yyvsp[(3) - (5)].name), (yyvsp[(5) - (5)].ptr) ); ;} break; case 6: /* Line 1455 of yacc.c */ #line 65 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newMaskAtom( (yyvsp[(1) - (5)].name), (yyvsp[(3) - (5)].name), (yyvsp[(5) - (5)].ptr) ); ;} break; case 7: /* Line 1455 of yacc.c */ #line 66 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newIsAtom( (yyvsp[(2) - (2)].name) ); ;} break; case 8: /* Line 1455 of yacc.c */ #line 68 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newOr( (yyvsp[(1) - (3)].ptr), (yyvsp[(3) - (3)].ptr) ); ;} break; case 9: /* Line 1455 of yacc.c */ #line 70 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newAnd( (yyvsp[(1) - (3)].ptr), (yyvsp[(3) - (3)].ptr) ); ;} break; case 10: /* Line 1455 of yacc.c */ #line 72 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newStringValue( (yyvsp[(1) - (1)].name) ); ;} break; case 11: /* Line 1455 of yacc.c */ #line 73 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newBoolValue( (yyvsp[(1) - (1)].valb) ); ;} break; case 12: /* Line 1455 of yacc.c */ #line 74 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newNumValue( (yyvsp[(1) - (1)].vali) ); ;} break; case 13: /* Line 1455 of yacc.c */ #line 75 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newDoubleValue( (yyvsp[(1) - (1)].vald) ); ;} break; case 14: /* Line 1455 of yacc.c */ #line 76 "predicate_parser.y" { (yyval.ptr) = (yyvsp[(1) - (1)].ptr); ;} break; case 15: /* Line 1455 of yacc.c */ #line 78 "predicate_parser.y" { (yyval.ptr) = (yyvsp[(1) - (3)].ptr); ;} break; case 16: /* Line 1455 of yacc.c */ #line 80 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newEmptyStringListValue(); ;} break; case 17: /* Line 1455 of yacc.c */ #line 81 "predicate_parser.y" { (yyval.ptr) = PredicateParse_newStringListValue( (yyvsp[(1) - (1)].ptr) ); ;} break; case 18: /* Line 1455 of yacc.c */ #line 82 "predicate_parser.y" { (yyval.ptr) = PredicateParse_appendStringListValue( (yyvsp[(1) - (3)].name), (yyvsp[(3) - (3)].ptr) ); ;} break; /* Line 1455 of yacc.c */ #line 1530 "predicate_parser.tab.c" default: break; } YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; /* Now `shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ yyn = yyr1[yyn]; yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) yystate = yytable[yystate]; else yystate = yydefgoto[yyn - YYNTOKENS]; goto yynewstate; /*------------------------------------. | yyerrlab -- here on detecting error | `------------------------------------*/ yyerrlab: /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; #if ! YYERROR_VERBOSE yyerror (YY_("syntax error")); #else { YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) { YYSIZE_T yyalloc = 2 * yysize; if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) yyalloc = YYSTACK_ALLOC_MAXIMUM; if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = (char *) YYSTACK_ALLOC (yyalloc); if (yymsg) yymsg_alloc = yyalloc; else { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; } } if (0 < yysize && yysize <= yymsg_alloc) { (void) yysyntax_error (yymsg, yystate, yychar); yyerror (yymsg); } else { yyerror (YY_("syntax error")); if (yysize != 0) goto yyexhaustedlab; } } #endif } if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers like GCC when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (/*CONSTCOND*/ 0) goto yyerrorlab; /* Do not reclaim the symbols of the rule which action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; if (yyn != YYPACT_NINF) { yyn += YYTERROR; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", yystos[yystate], yyvsp); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } *++yyvsp = yylval; /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #if !defined(yyoverflow) || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif yyreturn: if (yychar != YYEMPTY) yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval); /* Do not reclaim the symbols of the rule which action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", yystos[*yyssp], yyvsp); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif #if YYERROR_VERBOSE if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif /* Make sure YYID is used. */ return YYID (yyresult); } /* Line 1675 of yacc.c */ #line 84 "predicate_parser.y" void Soliderror ( const char *s ) /* Called by Solidparse on error */ { PredicateParse_errorDetected(s); } void PredicateParse_mainParse( const char *_code ) { yyscan_t scanner; Solidlex_init( &scanner ); PredicateParse_initLexer( _code, scanner ); Solidparse( scanner ); Solidlex_destroy( scanner ); } cantata-2.2.0/3rdparty/solid-lite/predicate_parser.h000066400000000000000000000046201316350454000224330ustar00rootroot00000000000000 /* A Bison parser, made by GNU Bison 2.4.1. */ /* Skeleton interface for Bison's Yacc-like parsers in C Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* Tokens. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE /* Put the tokens into the symbol table, so that GDB and other debuggers know about them. */ enum yytokentype { EQ = 258, MASK = 259, AND = 260, OR = 261, IS = 262, VAL_BOOL = 263, VAL_STRING = 264, VAL_ID = 265, VAL_NUM = 266, VAL_FLOAT = 267 }; #endif #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef union YYSTYPE { /* Line 1676 of yacc.c */ #line 22 "predicate_parser.y" char valb; int vali; double vald; char *name; void *ptr; /* Line 1676 of yacc.c */ #line 74 "predicate_parser.tab.h" } YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 #endif cantata-2.2.0/3rdparty/solid-lite/predicate_parser.y000066400000000000000000000055541316350454000224630ustar00rootroot00000000000000%{ #include #include #include "predicate_parser.h" #include "predicateparse.h" #define YYLTYPE_IS_TRIVIAL 0 #define YYENABLE_NLS 0 #define YYLEX_PARAM scanner #define YYPARSE_PARAM scanner typedef void* yyscan_t; void Soliderror(const char *s); int Solidlex( YYSTYPE *yylval, yyscan_t scanner ); int Solidlex_init( yyscan_t *scanner ); int Solidlex_destroy( yyscan_t *scanner ); void PredicateParse_initLexer( const char *s, yyscan_t scanner ); void PredicateParse_mainParse( const char *_code ); %} %union { char valb; int vali; double vald; char *name; void *ptr; } %token EQ %token MASK %token AND %token OR %token IS %token VAL_BOOL %token VAL_STRING %token VAL_ID %token VAL_NUM %token VAL_FLOAT %type predicate %type predicate_atom %type predicate_or %type predicate_and %type string_list %type string_list_rec %type value %destructor { PredicateParse_destroy( $$ ); } predicate %destructor { PredicateParse_destroy( $$ ); } predicate_atom %destructor { PredicateParse_destroy( $$ ); } predicate_or %destructor { PredicateParse_destroy( $$ ); } predicate_and %pure-parser %% predicate: predicate_atom { PredicateParse_setResult( $1 ); $$ = $1; } | '[' predicate_or ']' { PredicateParse_setResult( $2 ); $$ = $2; } | '[' predicate_and ']' { PredicateParse_setResult( $2 ); $$ = $2; } predicate_atom: VAL_ID '.' VAL_ID EQ value { $$ = PredicateParse_newAtom( $1, $3, $5 ); } | VAL_ID '.' VAL_ID MASK value { $$ = PredicateParse_newMaskAtom( $1, $3, $5 ); } | IS VAL_ID { $$ = PredicateParse_newIsAtom( $2 ); } predicate_or: predicate OR predicate { $$ = PredicateParse_newOr( $1, $3 ); } predicate_and: predicate AND predicate { $$ = PredicateParse_newAnd( $1, $3 ); } value: VAL_STRING { $$ = PredicateParse_newStringValue( $1 ); } | VAL_BOOL { $$ = PredicateParse_newBoolValue( $1 ); } | VAL_NUM { $$ = PredicateParse_newNumValue( $1 ); } | VAL_FLOAT { $$ = PredicateParse_newDoubleValue( $1 ); } | string_list { $$ = $1; } string_list: '{' string_list_rec '}' { $$ = $1; } string_list_rec: /* empty */ { $$ = PredicateParse_newEmptyStringListValue(); } | VAL_STRING { $$ = PredicateParse_newStringListValue( $1 ); } | VAL_STRING ',' string_list_rec { $$ = PredicateParse_appendStringListValue( $1, $3 ); } %% void Soliderror ( const char *s ) /* Called by Solidparse on error */ { PredicateParse_errorDetected(s); } void PredicateParse_mainParse( const char *_code ) { yyscan_t scanner; Solidlex_init( &scanner ); PredicateParse_initLexer( _code, scanner ); Solidparse( scanner ); Solidlex_destroy( scanner ); } cantata-2.2.0/3rdparty/solid-lite/predicateparse.cpp000066400000000000000000000125071316350454000224500ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ extern "C" { #include "predicateparse.h" void PredicateParse_mainParse(const char *_code); } #include "predicate.h" #include "soliddefs_p.h" #include #include #include namespace Solid { namespace PredicateParse { struct ParsingData { ParsingData() : result(0) {} Solid::Predicate *result; QByteArray buffer; }; } } SOLID_GLOBAL_STATIC(QThreadStorage, s_parsingData) Solid::Predicate Solid::Predicate::fromString(const QString &predicate) { Solid::PredicateParse::ParsingData *data = new Solid::PredicateParse::ParsingData(); s_parsingData->setLocalData(data); #if QT_VERSION < 0x050000 data->buffer = predicate.toAscii(); #else data->buffer = predicate.toLatin1(); #endif PredicateParse_mainParse(data->buffer.constData()); Predicate result; if (data->result) { result = Predicate(*data->result); delete data->result; } s_parsingData->setLocalData(0); return result; } void PredicateParse_setResult(void *result) { Solid::PredicateParse::ParsingData *data = s_parsingData->localData(); data->result = (Solid::Predicate *) result; } void PredicateParse_errorDetected(const char* s) { qWarning("ERROR from solid predicate parser: %s", s); s_parsingData->localData()->result = 0; } void PredicateParse_destroy(void *pred) { Solid::PredicateParse::ParsingData *data = s_parsingData->localData(); Solid::Predicate *p = (Solid::Predicate *) pred; if (p != data->result) { delete p; } } void *PredicateParse_newAtom(char *interface, char *property, void *value) { QString iface(interface); QString prop(property); QVariant *val = (QVariant *)value; Solid::Predicate *result = new Solid::Predicate(iface, prop, *val); delete val; free(interface); free(property); return result; } void *PredicateParse_newMaskAtom(char *interface, char *property, void *value) { QString iface(interface); QString prop(property); QVariant *val = (QVariant *)value; Solid::Predicate *result = new Solid::Predicate(iface, prop, *val, Solid::Predicate::Mask); delete val; free(interface); free(property); return result; } void *PredicateParse_newIsAtom(char *interface) { QString iface(interface); Solid::Predicate *result = new Solid::Predicate(iface); free(interface); return result; } void *PredicateParse_newAnd(void *pred1, void *pred2) { Solid::Predicate *result = new Solid::Predicate(); Solid::PredicateParse::ParsingData *data = s_parsingData->localData(); Solid::Predicate *p1 = (Solid::Predicate *)pred1; Solid::Predicate *p2 = (Solid::Predicate *)pred2; if (p1==data->result || p2==data->result) { data->result = 0; } *result = *p1 & *p2; delete p1; delete p2; return result; } void *PredicateParse_newOr(void *pred1, void *pred2) { Solid::Predicate *result = new Solid::Predicate(); Solid::PredicateParse::ParsingData *data = s_parsingData->localData(); Solid::Predicate *p1 = (Solid::Predicate *)pred1; Solid::Predicate *p2 = (Solid::Predicate *)pred2; if (p1==data->result || p2==data->result) { data->result = 0; } *result = *p1 | *p2; delete p1; delete p2; return result; } void *PredicateParse_newStringValue(char *val) { QString s(val); free(val); return new QVariant(s); } void *PredicateParse_newBoolValue(int val) { bool b = (val != 0); return new QVariant(b); } void *PredicateParse_newNumValue(int val) { return new QVariant(val); } void *PredicateParse_newDoubleValue(double val) { return new QVariant(val); } void *PredicateParse_newEmptyStringListValue() { return new QVariant(QStringList()); } void *PredicateParse_newStringListValue(char *name) { QStringList list; list << QString(name); free(name); return new QVariant(list); } void *PredicateParse_appendStringListValue(char *name, void *list) { QVariant *variant = (QVariant *)list; QStringList new_list = variant->toStringList(); new_list << QString(name); delete variant; free(name); return new QVariant(new_list); } void PredicateLexer_unknownToken(const char* text) { qWarning("ERROR from solid predicate parser: unrecognized token '%s' in predicate '%s'\n", text, s_parsingData->localData()->buffer.constData()); } cantata-2.2.0/3rdparty/solid-lite/predicateparse.h000066400000000000000000000034721316350454000221160ustar00rootroot00000000000000/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef PREDICATEPARSE_H #define PREDICATEPARSE_H void PredicateLexer_unknownToken(const char* text); void PredicateParse_setResult(void *result); void PredicateParse_errorDetected(const char* error); void PredicateParse_destroy(void *pred); void *PredicateParse_newAtom(char *interface, char *property, void *value); void *PredicateParse_newMaskAtom(char *interface, char *property, void *value); void *PredicateParse_newIsAtom(char *interface); void *PredicateParse_newAnd(void *pred1, void *pred2); void *PredicateParse_newOr(void *pred1, void *pred2); void *PredicateParse_newStringValue(char *val); void *PredicateParse_newBoolValue(int val); void *PredicateParse_newNumValue(int val); void *PredicateParse_newDoubleValue(double val); void *PredicateParse_newEmptyStringListValue(); void *PredicateParse_newStringListValue(char *name); void *PredicateParse_appendStringListValue(char *name, void *list); #endif cantata-2.2.0/3rdparty/solid-lite/solid_export.h000066400000000000000000000000611316350454000216250ustar00rootroot00000000000000#ifndef SOLID_EXPORT #define SOLID_EXPORT #endif cantata-2.2.0/3rdparty/solid-lite/soliddefs_p.h000066400000000000000000000212421316350454000214110ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_SOLIDDEFS_P_H #define SOLID_SOLIDDEFS_P_H #include #define return_SOLID_CALL(Type, Object, Default, Method) \ Type t = qobject_cast(Object); \ if (t!=0) \ { \ return t->Method; \ } \ else \ { \ return Default; \ } #define SOLID_CALL(Type, Object, Method) \ Type t = qobject_cast(Object); \ if (t!=0) \ { \ t->Method; \ } // // WARNING!! // This code uses undocumented Qt API // Do not copy it to your application! Use only the functions that are here! // Otherwise, it could break when a new version of Qt ships. // /* * Lame copy of K_GLOBAL_STATIC below, but that's the price for * being completely kdecore independent. */ namespace Solid { typedef void (*CleanUpFunction)(); class CleanUpGlobalStatic { public: Solid::CleanUpFunction func; inline ~CleanUpGlobalStatic() { func(); } }; } #ifdef Q_CC_MSVC # define SOLID_GLOBAL_STATIC_STRUCT_NAME(NAME) _solid_##NAME##__LINE__ #else # define SOLID_GLOBAL_STATIC_STRUCT_NAME(NAME) #endif #define SOLID_GLOBAL_STATIC(TYPE, NAME) SOLID_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ()) #if QT_VERSION < 0x050000 #define SOLID_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \ static QBasicAtomicPointer _solid_static_##NAME = Q_BASIC_ATOMIC_INITIALIZER(0);\ static bool _solid_static_##NAME##_destroyed; \ static struct SOLID_GLOBAL_STATIC_STRUCT_NAME(NAME) \ { \ bool isDestroyed() \ { \ return _solid_static_##NAME##_destroyed; \ } \ inline operator TYPE*() \ { \ return operator->(); \ } \ inline TYPE *operator->() \ { \ if (!_solid_static_##NAME) { \ if (isDestroyed()) { \ qFatal("Fatal Error: Accessed global static '%s *%s()' after destruction. " \ "Defined at %s:%d", #TYPE, #NAME, __FILE__, __LINE__); \ } \ TYPE *x = new TYPE ARGS; \ if (!_solid_static_##NAME.testAndSetOrdered(0, x) \ && _solid_static_##NAME != x ) { \ delete x; \ } else { \ static Solid::CleanUpGlobalStatic cleanUpObject = { destroy }; \ } \ } \ return _solid_static_##NAME; \ } \ inline TYPE &operator*() \ { \ return *operator->(); \ } \ static void destroy() \ { \ _solid_static_##NAME##_destroyed = true; \ TYPE *x = _solid_static_##NAME; \ _solid_static_##NAME = 0; \ delete x; \ } \ } NAME; #else #define SOLID_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \ static QBasicAtomicPointer _solid_static_##NAME = Q_BASIC_ATOMIC_INITIALIZER(0);\ static bool _solid_static_##NAME##_destroyed; \ static struct SOLID_GLOBAL_STATIC_STRUCT_NAME(NAME) \ { \ bool isDestroyed() \ { \ return _solid_static_##NAME##_destroyed; \ } \ inline operator TYPE*() \ { \ return operator->(); \ } \ inline TYPE *operator->() \ { \ if (!_solid_static_##NAME.load()) { \ if (isDestroyed()) { \ qFatal("Fatal Error: Accessed global static '%s *%s()' after destruction. " \ "Defined at %s:%d", #TYPE, #NAME, __FILE__, __LINE__); \ } \ TYPE *x = new TYPE ARGS; \ if (!_solid_static_##NAME.testAndSetOrdered(0, x) \ && _solid_static_##NAME.load() != x ) { \ delete x; \ } else { \ static Solid::CleanUpGlobalStatic cleanUpObject = { destroy }; \ } \ } \ return _solid_static_##NAME.load(); \ } \ inline TYPE &operator*() \ { \ return *operator->(); \ } \ static void destroy() \ { \ _solid_static_##NAME##_destroyed = true; \ TYPE *x = _solid_static_##NAME.load(); \ _solid_static_##NAME.store(0); \ delete x; \ } \ } NAME; #endif #endif cantata-2.2.0/3rdparty/solid-lite/solidnamespace.cpp000066400000000000000000000023171316350454000224420ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "solidnamespace.h" static int registerSolidMetaTypes() { qRegisterMetaType(); return 0; // something } #ifdef Q_CONSTRUCTOR_FUNCTION Q_CONSTRUCTOR_FUNCTION(registerSolidMetaTypes) #else static const int _Solid_registerMetaTypes = registerSolidMetaTypes(); #endif cantata-2.2.0/3rdparty/solid-lite/solidnamespace.h000066400000000000000000000024541316350454000221110ustar00rootroot00000000000000/* Copyright 2007 Kevin Ottens Copyright 2011 Lukas Tinkl This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_SOLIDNAMESPACE_H #define SOLID_SOLIDNAMESPACE_H namespace Solid { enum ErrorType { NoError = 0, UnauthorizedOperation, DeviceBusy, OperationFailed, UserCanceled, InvalidOption, MissingDriver }; } #include Q_DECLARE_METATYPE(Solid::ErrorType) #endif cantata-2.2.0/3rdparty/solid-lite/storageaccess.cpp000066400000000000000000000071031316350454000222770ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storageaccess.h" #include "storageaccess_p.h" #include "soliddefs_p.h" #include Solid::StorageAccess::StorageAccess(QObject *backendObject) : DeviceInterface(*new StorageAccessPrivate(), backendObject) { connect(backendObject, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), this, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString))); connect(backendObject, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)), this, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString))); connect(backendObject, SIGNAL(setupRequested(QString)), this, SIGNAL(setupRequested(QString))); connect(backendObject, SIGNAL(teardownRequested(QString)), this, SIGNAL(teardownRequested(QString))); connect(backendObject, SIGNAL(accessibilityChanged(bool,QString)), this, SIGNAL(accessibilityChanged(bool,QString))); } Solid::StorageAccess::StorageAccess(StorageAccessPrivate &dd, QObject *backendObject) : DeviceInterface(dd, backendObject) { connect(backendObject, SIGNAL(setupDone(Solid::StorageAccess::SetupResult,QVariant,QString)), this, SIGNAL(setupDone(Solid::StorageAccess::SetupResult,QVariant,QString))); connect(backendObject, SIGNAL(teardownDone(Solid::StorageAccess::TeardownResult,QVariant,QString)), this, SIGNAL(teardownDone(Solid::StorageAccess::TeardownResult,QVariant,QString))); connect(backendObject, SIGNAL(setupRequested(QString)), this, SIGNAL(setupRequested(QString))); connect(backendObject, SIGNAL(teardownRequested(QString)), this, SIGNAL(teardownRequested(QString))); connect(backendObject, SIGNAL(accessibilityChanged(bool,QString)), this, SIGNAL(accessibilityChanged(bool,QString))); } Solid::StorageAccess::~StorageAccess() { } bool Solid::StorageAccess::isAccessible() const { Q_D(const StorageAccess); return_SOLID_CALL(Ifaces::StorageAccess *, d->backendObject(), false, isAccessible()); } QString Solid::StorageAccess::filePath() const { Q_D(const StorageAccess); return_SOLID_CALL(Ifaces::StorageAccess *, d->backendObject(), QString(), filePath()); } bool Solid::StorageAccess::setup() { Q_D(StorageAccess); return_SOLID_CALL(Ifaces::StorageAccess *, d->backendObject(), false, setup()); } bool Solid::StorageAccess::teardown() { Q_D(StorageAccess); return_SOLID_CALL(Ifaces::StorageAccess *, d->backendObject(), false, teardown()); } bool Solid::StorageAccess::isIgnored() const { Q_D(const StorageAccess); return_SOLID_CALL(Ifaces::StorageAccess *, d->backendObject(), true, isIgnored()); } //#include "storageaccess.moc" cantata-2.2.0/3rdparty/solid-lite/storageaccess.h000066400000000000000000000134161316350454000217500ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEACCESS_H #define SOLID_STORAGEACCESS_H #include #include #include #include namespace Solid { class StorageAccessPrivate; class Device; /** * This device interface is available on volume devices to access them * (i.e. mount or unmount them). * * A volume is anything that can contain data (partition, optical disc, * memory card). It's a particular kind of block device. */ class SOLID_EXPORT StorageAccess : public DeviceInterface { Q_OBJECT Q_PROPERTY(bool accessible READ isAccessible) Q_PROPERTY(QString filePath READ filePath) Q_PROPERTY(bool ignored READ isIgnored) Q_DECLARE_PRIVATE(StorageAccess) friend class Device; private: /** * Creates a new StorageAccess object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit StorageAccess(QObject *backendObject); public: /** * Destroys a StorageAccess object. */ virtual ~StorageAccess(); /** * Get the Solid::DeviceInterface::Type of the StorageAccess device interface. * * @return the StorageVolume device interface type * @see Solid::Ifaces::Enums::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::StorageAccess; } /** * Indicates if this volume is mounted. * * @return true if the volume is mounted */ bool isAccessible() const; /** * Retrieves the absolute path of this volume mountpoint. * * @return the absolute path to the mount point if the volume is * mounted, QString() otherwise */ QString filePath() const; /** * Indicates if this volume should be ignored by applications. * * If it should be ignored, it generally means that it should be * invisible to the user. It's useful for firmware partitions or * OS reinstall partitions on some systems. * * @return true if the volume should be ignored */ bool isIgnored() const; /** * Mounts the volume. * * @return false if the operation is not supported, true if the * operation is attempted */ bool setup(); /** * Unmounts the volume. * * @return false if the operation is not supported, true if the * operation is attempted */ bool teardown(); Q_SIGNALS: /** * This signal is emitted when the accessiblity of this device * has changed. * * @param accessible true if the volume is accessible, false otherwise * @param udi the UDI of the volume */ void accessibilityChanged(bool accessible, const QString &udi); /** * This signal is emitted when the attempted setting up of this * device is completed. The signal might be spontaneous i.e. * it can be triggered by another process. * * @param error type of error that occurred, if any * @param errorData more information about the error, if any * @param udi the UDI of the volume */ void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); /** * This signal is emitted when the attempted tearing down of this * device is completed. The signal might be spontaneous i.e. * it can be triggered by another process. * * @param error type of error that occurred, if any * @param errorData more information about the error, if any * @param udi the UDI of the volume */ void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); /** * This signal is emitted when a setup of this device is requested. * The signal might be spontaneous i.e. it can be triggered by * another process. * * @param udi the UDI of the volume */ void setupRequested(const QString &udi); /** * This signal is emitted when a teardown of this device is requested. * The signal might be spontaneous i.e. it can be triggered by * another process * * @param udi the UDI of the volume */ void teardownRequested(const QString &udi); protected: /** * @internal */ StorageAccess(StorageAccessPrivate &dd, QObject *backendObject); }; } #endif cantata-2.2.0/3rdparty/solid-lite/storageaccess_p.h000066400000000000000000000023061316350454000222630ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEACCESS_P_H #define SOLID_STORAGEACCESS_P_H #include "deviceinterface_p.h" namespace Solid { class StorageAccessPrivate : public DeviceInterfacePrivate { public: StorageAccessPrivate() : DeviceInterfacePrivate() { } }; } #endif cantata-2.2.0/3rdparty/solid-lite/storagedrive.cpp000066400000000000000000000054311316350454000221510ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storagedrive.h" #include "storagedrive_p.h" #include "soliddefs_p.h" #include #include "predicate.h" #include "storageaccess.h" #include "device.h" #include "device_p.h" Solid::StorageDrive::StorageDrive(QObject *backendObject) : DeviceInterface(*new StorageDrivePrivate(), backendObject) { } Solid::StorageDrive::StorageDrive(StorageDrivePrivate &dd, QObject *backendObject) : DeviceInterface(dd, backendObject) { } Solid::StorageDrive::~StorageDrive() { } Solid::StorageDrive::Bus Solid::StorageDrive::bus() const { Q_D(const StorageDrive); return_SOLID_CALL(Ifaces::StorageDrive *, d->backendObject(), Platform, bus()); } Solid::StorageDrive::DriveType Solid::StorageDrive::driveType() const { Q_D(const StorageDrive); return_SOLID_CALL(Ifaces::StorageDrive *, d->backendObject(), HardDisk, driveType()); } bool Solid::StorageDrive::isRemovable() const { Q_D(const StorageDrive); return_SOLID_CALL(Ifaces::StorageDrive *, d->backendObject(), false, isRemovable()); } bool Solid::StorageDrive::isHotpluggable() const { Q_D(const StorageDrive); return_SOLID_CALL(Ifaces::StorageDrive *, d->backendObject(), false, isHotpluggable()); } qulonglong Solid::StorageDrive::size() const { Q_D(const StorageDrive); return_SOLID_CALL(Ifaces::StorageDrive *, d->backendObject(), false, size()); } bool Solid::StorageDrive::isInUse() const { Q_D(const StorageDrive); Predicate p(DeviceInterface::StorageAccess); QList devices = Device::listFromQuery(p, d->devicePrivate()->udi()); bool inUse = false; foreach (const Device &dev, devices) { if (dev.is()) { const Solid::StorageAccess* access = dev.as(); inUse |= (access->isAccessible()); } } return inUse; } //#include "storagedrive.moc" cantata-2.2.0/3rdparty/solid-lite/storagedrive.h000066400000000000000000000126451316350454000216230ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEDRIVE_H #define SOLID_STORAGEDRIVE_H #include #include namespace Solid { class StorageDrivePrivate; class Device; /** * This device interface is available on storage devices. * * A storage is anything that can contain a set of volumes (card reader, * hard disk, cdrom drive...). It's a particular kind of block device. */ class SOLID_EXPORT StorageDrive : public DeviceInterface { Q_OBJECT Q_ENUMS(Bus DriveType) Q_PROPERTY(Bus bus READ bus) Q_PROPERTY(DriveType driveType READ driveType) Q_PROPERTY(bool removable READ isRemovable) Q_PROPERTY(bool hotpluggable READ isHotpluggable) Q_PROPERTY(bool inUse READ isInUse) Q_PROPERTY(qulonglong size READ size) Q_DECLARE_PRIVATE(StorageDrive) friend class Device; public: /** * This enum type defines the type of bus a storage device is attached to. * * - Ide : An Integrated Drive Electronics (IDE) bus, also known as ATA * - Usb : An Universal Serial Bus (USB) * - Ieee1394 : An Ieee1394 bus, also known as Firewire * - Scsi : A Small Computer System Interface bus * - Sata : A Serial Advanced Technology Attachment (SATA) bus * - Platform : A legacy bus that is part of the underlying platform */ enum Bus { Ide, Usb, Ieee1394, Scsi, Sata, Platform }; /** * This enum type defines the type of drive a storage device can be. * * - HardDisk : A hard disk * - CdromDrive : An optical drive * - Floppy : A floppy disk drive * - Tape : A tape drive * - CompactFlash : A Compact Flash card reader * - MemoryStick : A Memory Stick card reader * - SmartMedia : A Smart Media card reader * - SdMmc : A SecureDigital/MultiMediaCard card reader * - Xd : A xD card reader */ enum DriveType { HardDisk, CdromDrive, Floppy, Tape, CompactFlash, MemoryStick, SmartMedia, SdMmc, Xd }; private: /** * Creates a new StorageDrive object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit StorageDrive(QObject *backendObject); public: /** * Destroys a StorageDrive object. */ virtual ~StorageDrive(); /** * Get the Solid::DeviceInterface::Type of the StorageDrive device interface. * * @return the StorageDrive device interface type * @see Solid::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::StorageDrive; } /** * Retrieves the type of physical interface this storage device is * connected to. * * @return the bus type * @see Solid::StorageDrive::Bus */ Bus bus() const; /** * Retrieves the type of this storage drive. * * @return the drive type * @see Solid::StorageDrive::DriveType */ DriveType driveType() const; /** * Indicates if the media contained by this drive can be removed. * * For example memory card can be removed from the drive by the user, * while partitions can't be removed from hard disks. * * @return true if media can be removed, false otherwise. */ bool isRemovable() const; /** * Indicates if this storage device can be plugged or unplugged while * the computer is running. * * @return true if this storage supports hotplug, false otherwise */ bool isHotpluggable() const; /** * Retrieves this drives size in bytes. * * @return the size of this drive */ qulonglong size() const; /** * Indicates if the storage device is currently in use * i.e. if at least one child storage access is * mounted * * @return true if at least one child storage access is mounted */ bool isInUse() const; protected: /** * @internal */ StorageDrive(StorageDrivePrivate &dd, QObject *backendObject); }; } #endif // SOLID_STORAGEDRIVE_H cantata-2.2.0/3rdparty/solid-lite/storagedrive_p.h000066400000000000000000000023341316350454000221340ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEDRIVE_P_H #define SOLID_STORAGEDRIVE_P_H #include "deviceinterface_p.h" namespace Solid { class StorageDrivePrivate : public DeviceInterfacePrivate { public: StorageDrivePrivate() : DeviceInterfacePrivate() { } }; } #endif // SOLID_STORAGEDRIVE_P_H cantata-2.2.0/3rdparty/solid-lite/storagevolume.cpp000066400000000000000000000053331316350454000223500ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "storagevolume.h" #include "storagevolume_p.h" #include "soliddefs_p.h" #include #include Solid::StorageVolume::StorageVolume(QObject *backendObject) : DeviceInterface(*new StorageVolumePrivate(), backendObject) { } Solid::StorageVolume::StorageVolume(StorageVolumePrivate &dd, QObject *backendObject) : DeviceInterface(dd, backendObject) { } Solid::StorageVolume::~StorageVolume() { } bool Solid::StorageVolume::isIgnored() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), true, isIgnored()); } Solid::StorageVolume::UsageType Solid::StorageVolume::usage() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), Unused, usage()); } QString Solid::StorageVolume::fsType() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), QString(), fsType()); } QString Solid::StorageVolume::label() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), QString(), label()); } QString Solid::StorageVolume::uuid() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), QString(), uuid().toLower()); } qulonglong Solid::StorageVolume::size() const { Q_D(const StorageVolume); return_SOLID_CALL(Ifaces::StorageVolume *, d->backendObject(), 0, size()); } Solid::Device Solid::StorageVolume::encryptedContainer() const { Q_D(const StorageVolume); Ifaces::StorageVolume *iface = qobject_cast(d->backendObject()); if (iface!=0) { return Device(iface->encryptedContainerUdi()); } else { return Device(); } } //#include "storagevolume.moc" cantata-2.2.0/3rdparty/solid-lite/storagevolume.h000066400000000000000000000123711316350454000220150ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEVOLUME_H #define SOLID_STORAGEVOLUME_H #include #include namespace Solid { class StorageVolumePrivate; class Device; /** * This device interface is available on volume devices. * * A volume is anything that can contain data (partition, optical disc, * memory card). It's a particular kind of block device. */ class SOLID_EXPORT StorageVolume : public DeviceInterface { Q_OBJECT Q_ENUMS(UsageType) Q_PROPERTY(bool ignored READ isIgnored) Q_PROPERTY(UsageType usage READ usage) Q_PROPERTY(QString fsType READ fsType) Q_PROPERTY(QString label READ label) Q_PROPERTY(QString uuid READ uuid) Q_PROPERTY(qulonglong size READ size) Q_DECLARE_PRIVATE(StorageVolume) friend class Device; public: /** * This enum type defines the how a volume is used. * * - FileSystem : A mountable filesystem volume * - PartitionTable : A volume containing a partition table * - Raid : A volume member of a raid set (not mountable) * - Other : A not mountable volume (like a swap partition) * - Unused : An unused or free volume */ enum UsageType { Other = 0, Unused = 1, FileSystem = 2, PartitionTable = 3, Raid = 4, Encrypted = 5 }; private: /** * Creates a new StorageVolume object. * You generally won't need this. It's created when necessary using * Device::as(). * * @param backendObject the device interface object provided by the backend * @see Solid::Device::as() */ explicit StorageVolume(QObject *backendObject); public: /** * Destroys a StorageVolume object. */ virtual ~StorageVolume(); /** * Get the Solid::DeviceInterface::Type of the StorageVolume device interface. * * @return the StorageVolume device interface type * @see Solid::DeviceInterface::Type */ static Type deviceInterfaceType() { return DeviceInterface::StorageVolume; } /** * Indicates if this volume should be ignored by applications. * * If it should be ignored, it generally means that it should be * invisible to the user. It's useful for firmware partitions or * OS reinstall partitions on some systems. * * @return true if the volume should be ignored */ bool isIgnored() const; /** * Retrieves the type of use for this volume (for example filesystem). * * @return the usage type * @see Solid::StorageVolume::UsageType */ UsageType usage() const; /** * Retrieves the filesystem type of this volume. * * FIXME: It's a platform dependent string, maybe we should switch to * an enum? * * @return the filesystem type if applicable, QString() otherwise */ QString fsType() const; /** * Retrieves this volume label. * * @return the volume label if available, QString() otherwise */ QString label() const; /** * Retrieves this volume Universal Unique IDentifier (UUID). * * You can generally assume that this identifier is unique with reasonable * confidence. Except if the volume UUID has been forged to intentionally * provoke a collision, the probability to have two volumes having the same * UUID is low. * * @return the Universal Unique IDentifier if available, QString() otherwise */ QString uuid() const; /** * Retrieves this volume size in bytes. * * @return the size of this volume */ qulonglong size() const; /** * Retrieves the crypto container of this volume. * * @return the encrypted volume containing the current volume if appliable, * an invalid device otherwise */ Device encryptedContainer() const; protected: /** * @internal */ StorageVolume(StorageVolumePrivate &dd, QObject *backendObject); }; } #endif // SOLID_STORAGEVOLUME_H cantata-2.2.0/3rdparty/solid-lite/storagevolume_p.h000066400000000000000000000023411316350454000223300ustar00rootroot00000000000000/* Copyright 2006-2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef SOLID_STORAGEVOLUME_P_H #define SOLID_STORAGEVOLUME_P_H #include "deviceinterface_p.h" namespace Solid { class StorageVolumePrivate : public DeviceInterfacePrivate { public: StorageVolumePrivate() : DeviceInterfacePrivate() { } }; } #endif // SOLID_STORAGEVOLUME_P_H cantata-2.2.0/3rdparty/solid-lite/xdgbasedirs.cpp000066400000000000000000000111001316350454000217400ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2007 by Kevin Krammer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ /* * This file was copied to solid from [akonadi]/libs/xdgbasedirs.cpp and slimmed down. * See xdgbasedirs_p.h for notes about slimming down. */ #include "xdgbasedirs_p.h" #include #include static QStringList splitPathList( const QString &pathList ) { return pathList.split( QLatin1Char( ':' ) ); } class XdgBaseDirsSingleton { public: QString homePath( const char *variable, const char *defaultSubDir ); QStringList systemPathList( const char *variable, const char *defaultDirList ); public: QString mConfigHome; QString mDataHome; QStringList mConfigDirs; QStringList mDataDirs; }; Q_GLOBAL_STATIC( XdgBaseDirsSingleton, instance ) QString Solid::XdgBaseDirs::homePath( const char *resource ) { if ( qstrncmp( "data", resource, 4 ) == 0 ) { if ( instance()->mDataHome.isEmpty() ) { instance()->mDataHome = instance()->homePath( "XDG_DATA_HOME", ".local/share" ); } return instance()->mDataHome; } else if ( qstrncmp( "config", resource, 6 ) == 0 ) { if ( instance()->mConfigHome.isEmpty() ) { instance()->mConfigHome = instance()->homePath( "XDG_CONFIG_HOME", ".config" ); } return instance()->mConfigHome; } return QString(); } QStringList Solid::XdgBaseDirs::systemPathList( const char *resource ) { if ( qstrncmp( "data", resource, 4 ) == 0 ) { if ( instance()->mDataDirs.isEmpty() ) { instance()->mDataDirs = instance()->systemPathList( "XDG_DATA_DIRS", "/usr/local/share:/usr/share" ); } return instance()->mDataDirs; } else if ( qstrncmp( "config", resource, 6 ) == 0 ) { if ( instance()->mConfigDirs.isEmpty() ) { instance()->mConfigDirs = instance()->systemPathList( "XDG_CONFIG_DIRS", "/etc/xdg" ); } return instance()->mConfigDirs; } return QStringList(); } QString Solid::XdgBaseDirs::findResourceFile( const char *resource, const QString &relPath ) { const QString fullPath = homePath( resource ) + QLatin1Char( '/' ) + relPath; QFileInfo fileInfo( fullPath ); if ( fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable() ) { return fullPath; } const QStringList pathList = systemPathList( resource ); foreach ( const QString &path, pathList ) { fileInfo = QFileInfo( path + QLatin1Char('/' ) + relPath ); if ( fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable() ) { return fileInfo.absoluteFilePath(); } } return QString(); } QString XdgBaseDirsSingleton::homePath( const char *variable, const char *defaultSubDir ) { const QByteArray env = qgetenv( variable ); QString xdgPath; if ( env.isEmpty() ) { xdgPath = QDir::homePath() + QLatin1Char( '/' ) + QLatin1String( defaultSubDir ); } else if ( env.startsWith( '/' ) ) { xdgPath = QString::fromLocal8Bit( env ); } else { xdgPath = QDir::homePath() + QLatin1Char( '/' ) + QString::fromLocal8Bit( env ); } return xdgPath; } QStringList XdgBaseDirsSingleton::systemPathList( const char *variable, const char *defaultDirList ) { const QByteArray env = qgetenv( variable ); QString xdgDirList; if ( env.isEmpty() ) { xdgDirList = QLatin1String( defaultDirList ); } else { xdgDirList = QString::fromLocal8Bit( env ); } return splitPathList( xdgDirList ); } cantata-2.2.0/3rdparty/solid-lite/xdgbasedirs_p.h000066400000000000000000000110671316350454000217400ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2007 by Kevin Krammer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ /* * This file was copied to solid from [akonadi]/libs/xdgbasedirs_p.h and slimmed down. * UDev::PortableMediaPlayer uses findResourceFile() which in turn uses homePath() and * systemPathList() so these are exported here too */ #ifndef SOLID_XDGBASEDIRS_H #define SOLID_XDGBASEDIRS_H class QString; class QStringList; namespace Solid { /** @brief Resource type based handling of standard directories Developers of several Free Software desktop projects have created a specification for handling so-called "base directories", i.e. lists of system wide directories and directories within each user's home directory for installing and later finding certain files. This class handles the respective behaviour, i.e. environment variables and their defaults, for the following type of resources: - "config" - "data" @author Kevin Krammer, @see http://www.freedesktop.org/wiki/Specifications/basedir-spec */ namespace XdgBaseDirs { /** @brief Returns the user specific directory for the given resource type Unless the user's environment has a specific path set as an override this will be the default as defined in the freedesktop.org base-dir-spec @note Caches the value of the first call @param resource a named resource type, e.g. "config" @return a directory path @see systemPathList() */ QString homePath( const char *resource ); /** @brief Returns the list of system wide directories for a given resource type The returned list can contain one or more directory paths. If there are more than one, the list is sorted by falling priority, i.e. if an entry is valid for the respective use case (e.g. contains a file the application looks for) the list should not be processed further. @note The user's resource path should, to be compliant with the spec, always be treated as having higher priority than any path in the list of system wide paths @note Caches the value of the first call @param resource a named resource type, e.g. "config" @return a priority sorted list of directory paths @see homePath() */ QStringList systemPathList( const char *resource ); /** @brief Searches the resource specific directories for a given file Convenience method for finding a given file (with optional relative path) in any of the configured base directories for a given resource type. Will check the user local directory first and then process the system wide path list according to the inherent priority. @param resource a named resource type, e.g. "config" @param relPath relative path of a file to look for, e.g."akonadi/akonadiserverrc" @returns the file path of the first match, or @c QString() if no such relative path exists in any of the base directories or if a match is not a file */ QString findResourceFile( const char *resource, const QString &relPath ); } } #endif cantata-2.2.0/AUTHORS000066400000000000000000000002151316350454000141750ustar00rootroot00000000000000Craig Drummond ---------- QtMPC Authors ---------- Sander Knopper Roeland Douma Daniel Selinger Armin Walland cantata-2.2.0/CMakeLists.txt000066400000000000000000001076241316350454000157010ustar00rootroot00000000000000PROJECT(cantata) # NOTE: If PROJECT_URL, or PROJECT_REV_URL, are changed, then cantata-dynamic, and README will need updating. # dbus/mpd.cantata.xml and devices/mounter/mpd.cantata.mounter.xml will also need renaming/updating. set(PROJECT_URL "cantata.mpd") set(PROJECT_REV_URL "mpd.cantata") if (CMAKE_SYSTEM_NAME MATCHES "kOpenBSD.*|OpenBSD.*") set(OPENBSD TRUE) endif () if (WIN32) # For some reason I chose mpd as the organization name for Windows builds. So, for config compatability, this is # left as mpd for now... set(ORGANIZATION_NAME "mpd") else () # Otherwise its set to the same as the project name. Not sure what else to set it to!!! set(ORGANIZATION_NAME ${CMAKE_PROJECT_NAME}) endif () cmake_minimum_required(VERSION 2.6) cmake_policy(SET CMP0020 OLD) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") set(CPACK_GENERATOR "DEB;RPM;TBZ2") set(DEBIAN_PACKAGE_DESCRIPTION "Qt Client for MPD") set(CPACK_SOURCE_GENERATOR "TBZ2") set(CPACK_PACKAGE_VERSION_MAJOR "2") set(CPACK_PACKAGE_VERSION_MINOR "2") set(CPACK_PACKAGE_VERSION_PATCH "0") set(CPACK_PACKAGE_VERSION_SPIN "") # Use ".$number" - e.g. ".1" set(CPACK_PACKAGE_CONTACT "Craig Drummond ") set(CANTATA_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") set(CANTATA_VERSION_FULL "${CANTATA_VERSION}.${CPACK_PACKAGE_VERSION_PATCH}") set(CANTATA_VERSION_WITH_SPIN "${CANTATA_VERSION_FULL}${CPACK_PACKAGE_VERSION_SPIN}") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CANTATA_VERSION_WITH_SPIN}") set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CANTATA_VERSION_WITH_SPIN}") set(CPACK_SOURCE_IGNORE_FILES "/.svn/;/.git/;.gitignore;.project;CMakeLists.txt.user;README.md") include(CPack) include(MacroLogFeature) option(ENABLE_TAGLIB "Enable TagLib library (required for tag editing, replaygain calculation, device support, etc)" ON) option(ENABLE_TAGLIB_EXTRAS "Enable TagLib-Extras library (used by tag editing, replaygain calculation, device support, etc)" ON) option(ENABLE_HTTP_STREAM_PLAYBACK "Enable playback of MPD HTTP streams (LibVLC or QtMultimedia)" ON) option(ENABLE_FFMPEG "Enable ffmpeg/libav libraries (required for replaygain calculation)" ON) option(ENABLE_MPG123 "Enable mpg123 libraries (required for replaygain calculation)" ON) option(ENABLE_PROXY_CONFIG "Enable proxy config in settings dialog" OFF) option(ENABLE_HTTP_SERVER "Enable internal HTTP server to play non-MPD files" ON) if (WIN32 OR APPLE) option(ENABLE_DEVICES_SUPPORT "Enable suport for external devices" OFF) option(ENABLE_LIBVLC "Use libVLC for MPD HTTP stream playback (if ENABLE_HTTP_STREAM_PLAYBACK=ON)" OFF) else () option(ENABLE_DEVICES_SUPPORT "Enable suport for external devices" ON) option(ENABLE_SIMPLE_MPD_SUPPORT "Enable support for simple (Cantata controlled) MPD" ON) option(ENABLE_LIBVLC "Use libVLC for MPD HTTP stream playback (if ENABLE_HTTP_STREAM_PLAYBACK=ON)" ON) endif () option(ENABLE_CDPARANOIA "Enable CDParanoia libraries (this or libCDIO_paranoia required for AudioCD support)" ON) option(ENABLE_CDIOPARANOIA "Enable libCDIO_paranoia libraries (this or CDParanoia required for AudioCD support)" ON) option(ENABLE_CDDB "Enable CDDB libraries (either this or MusicBrianz required for AudioCD support)" ON) option(ENABLE_MUSICBRAINZ "Enable MusicBrianz libraries (either this or CDDB required for AudioCD support)" ON) option(ENABLE_UDISKS2 "Build UDisks2 backend, and NOT UDisks, for Qt builds" ON) option(ENABLE_REMOTE_DEVICES "Enable support for remote (sshfs, samba) devices (EXPERIMENTAL)" OFF) option(ENABLE_MTP "Enable MTP library (required to support MTP devices)" ON) if (NOT APPLE AND NOT WIN32) set(SHARE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Define install directory for read-only architecture-independent data") endif () if (WIN32) set(CANTATA_ICON_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/icons/cantata) elseif (APPLE) set(MACOSX_BUNDLE_BUNDLE_NAME Cantata) set(MACOSX_BUNDLE_EXECUTABLE cantata) set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CANTATA_VERSION}) set(MACOSX_BUNDLE_VERSION ${CANTATA_VERSION}) set(MACOSX_BUNDLE_LONG_VERSION_STRING ${CANTATA_VERSION_WITH_SPIN}) set(MACOSX_BUNDLE_ICON_FILE cantata.icns) set(CANTATA_APP_CONTENTS_DIR ${CMAKE_INSTALL_PREFIX}/Cantata.app/Contents) set(MACOSX_BUNDLE_RESOURCES ${CANTATA_APP_CONTENTS_DIR}/Resources) set(MACOSX_BUNDLE_APP_DIR ${CANTATA_APP_CONTENTS_DIR}/MacOS) set(CANTATA_ICON_INSTALL_PREFIX ${MACOSX_BUNDLE_RESOURCES}/icons/cantata) else () set(CANTATA_ICON_INSTALL_PREFIX ${SHARE_INSTALL_PREFIX}/icons/hicolor) endif () if (NOT WIN32 AND NOT APPLE AND ENABLE_DEVICES_SUPPORT AND ENABLE_UDISKS2) set(WITH_SOLID_UDISKS2 ON) endif () if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") message("-- Set build type to ${CMAKE_BUILD_TYPE}") endif () if (NOT APPLE AND NOT WIN32) if (CANTATA_HELPERS_LIB_DIR AND CMAKE_SIZEOF_VOID_P EQUAL 8) set(LINUX_LIB_DIR ${CANTATA_HELPERS_LIB_DIR}) else () set(LINUX_LIB_DIR lib) endif () endif () if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND NOT APPLE AND NOT WIN32) set(CMAKE_INSTALL_PREFIX "/usr") message("-- Set install prefix to ${CMAKE_INSTALL_PREFIX}") endif () if (ENABLE_TAGLIB) if (NOT TAGLIB_FOUND) set(TAGLIB_MIN_VERSION "1.6") find_package(Taglib) endif() macro_log_feature(TAGLIB_FOUND "TagLib" "Tag editor, file organiser, etc." "http://taglib.github.io/") endif () if (TAGLIB_FOUND) if (ENABLE_DEVICES_SUPPORT AND (ENABLE_CDIOPARANOIA OR ENABLE_CDPARANOIA)) # Issue 1021 - prefer libcdio_paranoia over plain cdparanoia if (ENABLE_CDIOPARANOIA) find_package(Cdioparanoia) macro_log_feature(CDIOPARANOIA_FOUND "libcdio_paranoia" "CD ripping." "https://www.gnu.org/software/libcdio/") endif() if (NOT CDIOPARANOIA_FOUND) find_package(Cdparanoia) macro_log_feature(CDPARANOIA_FOUND "libcdda_paranoia" "CD ripping." "http://www.xiph.org/paranoia") endif() if (CDIOPARANOIA_FOUND OR CDPARANOIA_FOUND) if (ENABLE_CDDB) find_package(CDDB) macro_log_feature(CDDB_FOUND "libcddb" "CD info retrieval via CDDB." "http://libcddb.sourceforge.net") endif () if (ENABLE_MUSICBRAINZ) find_package(MusicBrainz5) macro_log_feature(MUSICBRAINZ5_FOUND "libmusicbrainz5" "CD info retrieval via MusicBrainz." "http://musicbrainz.org/doc/libmusicbrainz") endif () endif () endif () set(ENABLE_TAGEDITOR_SUPPORT 1) set(ENABLE_TRACKORGANIZER_SUPPORT 1) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDES}") set(CMAKE_REQUIRED_LIBRARIES "${TAGLIB_LIBRARIES}") check_cxx_source_compiles("#include int main() { TagLib::ASF::Tag tag; return 0;}" TAGLIB_ASF_FOUND) if (NOT TAGLIB_ASF_FOUND) message("TagLib does not have ASF support compiled in.") endif () check_cxx_source_compiles("#include int main() { TagLib::MP4::Tag tag(0, 0); return 0;}" TAGLIB_MP4_FOUND) if (NOT TAGLIB_MP4_FOUND) message("TagLib does not have MP4 support compiled in.") endif () check_cxx_source_compiles("#include int main() { TagLib::Ogg::Opus::File *f=0; return 0;}" TAGLIB_OPUS_FOUND) if (NOT TAGLIB_OPUS_FOUND) message("TagLib does not have OPUS support compiled in.") endif () set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_LIBRARIES) set(TAGLIB-EXTRAS_MIN_VERSION "1.0") if (ENABLE_TAGLIB_EXTRAS) find_package(Taglib-Extras) macro_log_feature(TAGLIB-EXTRAS_FOUND "TagLib-Extras" "Tag editor, file organiser, etc." "http://taglib.github.io/") endif () set(TAGLIB_EXTRAS_FOUND ${TAGLIB-EXTRAS_FOUND}) # we need a c-compatible name for the include file #include(CheckTagLibFileName) #check_taglib_filename(COMPLEX_TAGLIB_FILENAME) add_definitions(${TAGLIB_CFLAGS}) if (TAGLIB-EXTRAS_FOUND) include_directories(${TAGLIB-EXTRAS_INCLUDES}) add_definitions(${TAGLIB-EXTRAS_CFLAGS}) endif () if (ENABLE_FFMPEG) find_package(FFMPEG) macro_log_feature(FFMPEG_FOUND "libavcodec/libavutil/libavformat" "ReplayGain calculation." "http://ffmpeg.org") endif () if (ENABLE_MPG123) find_package(MPG123) macro_log_feature(MPG123_FOUND "libmpg123" "ReplayGain calculation." "http://www.mpg123.de") endif () if (ENABLE_MTP AND ENABLE_DEVICES_SUPPORT AND NOT WIN32 AND NOT APPLE) find_package(Mtp) macro_log_feature(MTP_FOUND "libmtp" "MTP Device Support." "http://libmtp.sourceforge.net") endif () else (TAGLIB_FOUND) set(ENABLE_DEVICES_SUPPORT OFF) endif () if (NOT MTP_FOUND) set(ENABLE_UNCACHED_MTP OFF) endif () find_package(Qt5Widgets REQUIRED) find_package(Qt5Xml REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Concurrent REQUIRED) find_package(Qt5Svg REQUIRED) find_package(Qt5Sql REQUIRED) set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_STANDARD 11) set(QTCORELIBS ${Qt5Core_LIBRARIES}) set(QTNETWORKLIBS ${Qt5Network_LIBRARIES}) set(QTGUILIBS ${Qt5Gui_LIBRARIES}) set(QTLIBS ${QTCORELIBS} ${Qt5Widgets_LIBRARIES} ${QTNETWORKLIBS} ${QTGUILIBS} ${Qt5Xml_LIBRARIES} ${Qt5Concurrent_LIBRARIES} ${Qt5Svg_LIBRARIES} ${Qt5Sql_LIBRARIES}) set(QTINCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Xml_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS} ${Qt5Svg_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS}) add_definitions(${Qt5Widgets_DEFINITIONS} ${Qt5Network_DEFINITIONS} ${Qt5Xml_DEFINITIONS} ${Qt5Concurrent_DEFINITIONS} ${Qt5Svg_DEFINITIONS} ${Qt5Sql_DEFINITIONS}) if (Qt5_POSITION_INDEPENDENT_CODE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") endif () if (APPLE OR WIN32) get_target_property(QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) exec_program(${QT_QMAKE_EXECUTABLE} ARGS -query QT_INSTALL_TRANSLATIONS OUTPUT_VARIABLE QT_TRANSLATIONS_DIR) else () find_package(Qt5DBus REQUIRED) set(QT_QTDBUS_FOUND 1) # required for config.h !!! set(QTLIBS ${QTLIBS} ${Qt5DBus_LIBRARIES}) set(QTINCLUDES ${QTINCLUDES} ${Qt5DBus_INCLUDE_DIRS}) add_definitions(${Qt5DBus_DEFINITIONS}) endif () if (ENABLE_HTTP_STREAM_PLAYBACK) if (ENABLE_LIBVLC) find_package(LIBVLC REQUIRED) macro_log_feature(LIBVLC_FOUND "libVLC" "MPD HTTP stream playback." "http://videolan.org") add_definitions(-DLIBVLC_FOUND) include_directories(${LIBVLC_INCLUDE_DIR}) set(CANTATA_LIBS ${CANTATA_LIBS} ${LIBVLC_LIBRARY}) else () find_package(Qt5Multimedia REQUIRED) macro_log_feature(Qt5Multimedia_FOUND "Qt5Multimedia" "MPD HTTP stream playback." "http://qtproject.org") include_directories(${Qt5Multimedia_INCLUDE_DIRS}) add_definitions(${Qt5Multimedia_DEFINITIONS}) set(CANTATA_LIBS ${CANTATA_LIBS} ${Qt5Multimedia_LIBRARIES}) endif () set(CANTATA_SRCS ${CANTATA_SRCS} mpd-interface/httpstream.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} mpd-interface/httpstream.h) endif () find_package(ZLIB REQUIRED) if (APPLE) set(CANTATA_SRCS ${CANTATA_SRCS} mac/dockmenu.cpp mac/macnotify.mm) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} mac/dockmenu.h) find_package(IOKit) if (IOKIT_FOUND) set(CANTATA_LIBS ${CANTATA_LIBS} ${IOKIT_LIBRARY}) set(CANTATA_SRCS ${CANTATA_SRCS} mac/powermanagement.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} mac/powermanagement.h) endif () find_package(Foundation REQUIRED) set(CANTATA_LIBS ${CANTATA_LIBS} ${FOUNDATION_LIBRARY}) find_package(Qt5MacExtras) if (Qt5MacExtras_FOUND) set(QTINCLUDES ${QTINCLUDES} ${Qt5MacExtras_INCLUDE_DIRS}) set(QTLIBS ${QTLIBS} ${Qt5MacExtras_LIBRARIES}) add_definitions(${Qt5MacExtras_DEFINITIONS}) set(QT_MAC_EXTRAS_FOUND 1) endif () elseif (WIN32) find_package(Qt5WinExtras REQUIRED) set(QTINCLUDES ${QTINCLUDES} ${Qt5WinExtras_INCLUDE_DIRS}) set(QTLIBS ${QTLIBS} ${Qt5WinExtras_LIBRARIES}) add_definitions(${Qt5WinExtras_DEFINITIONS}) set(CANTATA_SRCS ${CANTATA_SRCS} windows/thumbnailtoolbar.cpp) endif () # MusicBrainz requires exceptions to be enabled! if (MUSICBRAINZ5_FOUND) message("-- Enabling exceptions") if (CMAKE_COMPILER_IS_GNUCXX) add_definitions("-fexceptions -UQT_NO_EXCEPTIONS") endif (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_C_COMPILER MATCHES "icc") add_definitions(-fexceptions) endif () if (MSVC) add_definitions(-EHsc) endif () endif () include_directories(${CMAKE_SOURCE_DIR}/3rdparty ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QTINCLUDES} ${ZLIB_INCLUDE_DIRS}) set(CANTATA_SRCS ${CANTATA_SRCS} gui/settings.cpp gui/application.cpp gui/initialsettingswizard.cpp gui/mainwindow.cpp gui/preferencesdialog.cpp gui/customactionssettings.cpp gui/filesettings.cpp gui/interfacesettings.cpp gui/playbacksettings.cpp gui/serversettings.cpp gui/librarypage.cpp gui/customactions.cpp gui/folderpage.cpp gui/trayitem.cpp gui/cachesettings.cpp gui/coverdialog.cpp gui/searchpage.cpp gui/stdactions.cpp gui/main.cpp gui/covers.cpp gui/currentcover.cpp devices/deviceoptions.cpp db/librarydb.cpp db/mpdlibrarydb.cpp widgets/treeview.cpp widgets/listview.cpp widgets/itemview.cpp widgets/autohidingsplitter.cpp widgets/nowplayingwidget.cpp widgets/actionlabel.cpp widgets/playqueueview.cpp widgets/groupedview.cpp widgets/actionitemdelegate.cpp widgets/textbrowser.cpp widgets/volumeslider.cpp widgets/menubutton.cpp widgets/icons.cpp widgets/toolbutton.cpp widgets/wizardpage.cpp widgets/searchwidget.cpp widgets/messageoverlay.cpp widgets/basicitemdelegate.cpp widgets/sizegrip.cpp widgets/sizewidget.cpp widgets/servicestatuslabel.cpp widgets/spacerwidget.cpp widgets/songdialog.cpp widgets/stretchheaderview.cpp widgets/tableview.cpp widgets/thinsplitterhandle.cpp widgets/coverwidget.cpp widgets/ratingwidget.cpp widgets/notelabel.cpp widgets/selectorlabel.cpp widgets/titlewidget.cpp widgets/multipagewidget.cpp widgets/singlepagewidget.cpp widgets/stackedpagewidget.cpp widgets/mirrormenu.cpp widgets/genrecombo.cpp context/lyricsettings.cpp context/ultimatelyricsprovider.cpp context/ultimatelyrics.cpp context/lyricsdialog.cpp context/contextwidget.cpp context/view.cpp context/artistview.cpp context/albumview.cpp context/songview.cpp context/contextengine.cpp context/wikipediaengine.cpp context/wikipediasettings.cpp context/othersettings.cpp context/contextsettings.cpp context/togglelist.cpp context/lastfmengine.cpp context/metaengine.cpp context/onlineview.cpp streams/streamspage.cpp streams/digitallyimportedsettings.cpp streams/streamssettings.cpp streams/streamdialog.cpp streams/tar.cpp streams/streamproviderlistdialog.cpp streams/streamfetcher.cpp models/streamsproxymodel.cpp models/streamsearchmodel.cpp models/digitallyimported.cpp models/musiclibraryitemroot.cpp models/musiclibraryitemartist.cpp models/musiclibraryitemalbum.cpp models/musiclibraryproxymodel.cpp models/playlistsmodel.cpp models/playlistsproxymodel.cpp models/playqueuemodel.cpp models/proxymodel.cpp models/actionmodel.cpp models/musiclibraryitem.cpp models/browsemodel.cpp models/searchmodel.cpp models/streamsmodel.cpp models/searchproxymodel.cpp models/sqllibrarymodel.cpp models/mpdlibrarymodel.cpp models/mpdsearchmodel.cpp models/playqueueproxymodel.cpp mpd-interface/mpdconnection.cpp mpd-interface/mpdparseutils.cpp mpd-interface/mpdstats.cpp mpd-interface/mpdstatus.cpp mpd-interface/song.cpp mpd-interface/cuefile.cpp network/networkaccessmanager.cpp network/networkproxyfactory.cpp playlists/dynamicplaylists.cpp playlists/playlistproxymodel.cpp playlists/dynamicplaylistspage.cpp playlists/playlistruledialog.cpp playlists/playlistrulesdialog.cpp playlists/playlistspage.cpp playlists/storedplaylistspage.cpp playlists/rulesplaylists.cpp playlists/smartplaylists.cpp playlists/smartplaylistspage.cpp online/onlineservicespage.cpp online/onlinedbservice.cpp online/jamendoservice.cpp online/onlinedbwidget.cpp online/onlineservice.cpp online/jamendosettingsdialog.cpp online/magnatuneservice.cpp online/magnatunesettingsdialog.cpp online/soundcloudservice.cpp online/onlinesearchwidget.cpp online/podcastservice.cpp online/rssparser.cpp online/opmlparser.cpp online/podcastsearchdialog.cpp online/podcastsettingsdialog.cpp online/podcastwidget.cpp online/onlinesearchservice.cpp db/onlinedb.cpp scrobbling/scrobbler.cpp scrobbling/pausabletimer.cpp scrobbling/scrobblingsettings.cpp scrobbling/scrobblingstatus.cpp scrobbling/scrobblinglove.cpp http/httpserver.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} gui/initialsettingswizard.h gui/mainwindow.h gui/folderpage.h gui/librarypage.h gui/playbacksettings.h gui/serversettings.h gui/preferencesdialog.h gui/interfacesettings.h gui/cachesettings.h gui/trayitem.h gui/coverdialog.h gui/searchpage.h gui/customactions.h gui/customactionssettings.h gui/covers.h gui/currentcover.h models/musiclibraryproxymodel.h models/playlistsmodel.h models/playlistsproxymodel.h models/playqueuemodel.h models/playqueueproxymodel.h models/actionmodel.h models/browsemodel.h models/searchmodel.h models/sqllibrarymodel.h models/mpdlibrarymodel.h models/mpdsearchmodel.h models/digitallyimported.h models/streamsearchmodel.h mpd-interface/mpdconnection.h mpd-interface/mpdstats.h mpd-interface/mpdstatus.h db/librarydb.h db/mpdlibrarydb.h network/networkaccessmanager.h streams/streamfetcher.h widgets/treeview.h widgets/listview.h widgets/itemview.h widgets/autohidingsplitter.h widgets/nowplayingwidget.h widgets/actionlabel.h widgets/playqueueview.h widgets/groupedview.h widgets/actionitemdelegate.h widgets/volumeslider.h widgets/singlepagewidget.h widgets/searchwidget.h widgets/messageoverlay.h widgets/servicestatuslabel.h widgets/stretchheaderview.h widgets/tableview.h widgets/coverwidget.h widgets/ratingwidget.h widgets/selectorlabel.h widgets/titlewidget.h widgets/multipagewidget.h widgets/stackedpagewidget.h widgets/mirrormenu.h widgets/genrecombo.h widgets/menubutton.h widgets/songdialog.h widgets/notelabel.h context/togglelist.h context/ultimatelyrics.h context/ultimatelyricsprovider.h context/lyricsdialog.h context/contextsettings.h context/contextwidget.h context/artistview.h context/albumview.h context/songview.h context/view.h context/contextengine.h context/wikipediaengine.h context/wikipediasettings.h context/othersettings.h context/lastfmengine.h context/metaengine.h context/lyricsettings.h context/onlineview.h streams/streamspage.h streams/digitallyimportedsettings.h streams/streamssettings.h streams/streamdialog.h models/streamsmodel.h streams/streamproviderlistdialog.h online/onlineservicespage.h online/onlinedbservice.h online/onlinedbwidget.h online/magnatunesettingsdialog.h online/soundcloudservice.h online/onlinesearchwidget.h online/podcastservice.h online/podcastsearchdialog.h online/podcastsettingsdialog.h online/podcastwidget.h online/jamendoservice.h online/jamendosettingsdialog.h online/magnatuneservice.h online/onlinesearchservice.h db/onlinedb.h playlists/dynamicplaylists.h playlists/playlistruledialog.h playlists/dynamicplaylistspage.h playlists/playlistrulesdialog.h playlists/playlistspage.h playlists/storedplaylistspage.h playlists/rulesplaylists.h playlists/smartplaylists.h playlists/smartplaylistspage.h scrobbling/scrobbler.h scrobbling/scrobblingsettings.h scrobbling/scrobblingstatus.h scrobbling/scrobblinglove.h) set(CANTATA_UIS ${CANTATA_UIS} gui/initialsettingswizard.ui gui/mainwindow.ui gui/filesettings.ui gui/interfacesettings.ui gui/playbacksettings.ui gui/serversettings.ui gui/coverdialog.ui context/togglelist.ui context/othersettings.ui streams/digitallyimportedsettings.ui streams/streamssettings.ui playlists/playlistrule.ui playlists/playlistrules.ui widgets/itemview.ui scrobbling/scrobblingsettings.ui) if (ENABLE_SIMPLE_MPD_SUPPORT) set(CANTATA_SRCS ${CANTATA_SRCS} mpd-interface/mpduser.cpp) endif () set(CANTATA_LIBS ${CANTATA_LIBS} support) if (WIN32) # Not installed for windows - script uses sym-links... elseif (APPLE) install(PROGRAMS playlists/cantata-dynamic DESTINATION ${MACOSX_BUNDLE_RESOURCES}/scripts/) else () install(PROGRAMS playlists/cantata-dynamic DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/scripts) endif () if (ENABLE_HTTP_SERVER) set(CANTATA_SRCS ${CANTATA_SRCS} http/httpsocket.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} http/httpserver.h http/httpsocket.h) endif () if (QT_QTDBUS_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} dbus/gnomemediakeys.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} dbus/gnomemediakeys.h) set_SOURCE_FILES_PROPERTIES(dbus/org.gnome.SettingsDaemon.xml dbus/org.gnome.SettingsDaemon.MediaKeys.xml PROPERTIES NO_NAMESPACE TRUE) set(CANTATA_SRCS ${CANTATA_SRCS} dbus/mpris.cpp dbus/powermanagement.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} dbus/mpris.h dbus/powermanagement.h) set(CANTATA_SRCS ${CANTATA_SRCS} dbus/notify.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} dbus/notify.h) qt5_add_dbus_adaptor(CANTATA_SRCS dbus/org.mpris.MediaPlayer2.Player.xml dbus/mpris.h Mpris) qt5_add_dbus_adaptor(CANTATA_SRCS dbus/org.mpris.MediaPlayer2.root.xml dbus/mpris.h Mpris) qt5_add_dbus_adaptor(CANTATA_SRCS dbus/${PROJECT_REV_URL}.xml gui/mainwindow.h MainWindow) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.freedesktop.Notifications.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.freedesktop.UPower.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.freedesktop.login1.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.gnome.SettingsDaemon.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.gnome.SettingsDaemon.MediaKeys.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.kde.Solid.PowerManagement.PolicyAgent.xml) qt5_add_dbus_interfaces(CANTATA_SRCS dbus/org.freedesktop.PowerManagement.Inhibit.xml) endif () if (NOT WIN32 AND NOT APPLE) set(CANTATA_SRCS ${CANTATA_SRCS} devices/mountpoints.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/mountpoints.h) endif () set(CANTATA_RCS cantata.qrc cantata_media.qrc) if (TAGLIB_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} tags/tageditor.cpp tags/trackorganiser.cpp devices/filenameschemedialog.cpp widgets/tagspinbox.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} tags/tageditor.h tags/trackorganiser.h devices/filenameschemedialog.h devices/device.h widgets/tagspinbox.h) set(CANTATA_UIS ${CANTATA_UIS} tags/tageditor.ui tags/trackorganiser.ui devices/filenameschemedialog.ui) # Cantata still links to taglib, even if external tag reader/writer is used, because JamendoService uses taglib for ID3 genres. set(CANTATA_LIBS ${CANTATA_LIBS} ${TAGLIB_LIBRARIES}) include_directories(${TAGLIB_INCLUDES}) if (APPLE OR OPENBSD) # for some reason under osx we get full path no include/taglib? include_directories(${TAGLIB_INCLUDES}/..) endif () if (ENABLE_DEVICES_SUPPORT) set(CANTATA_LIBS ${CANTATA_LIBS} solidlite) endif () if (ENABLE_DEVICES_SUPPORT AND ENABLE_REMOTE_DEVICES AND NOT WIN32 AND NOT APPLE) set(CANTATA_LIBS ${CANTATA_LIBS} avahi) endif () if (FFMPEG_FOUND OR MPG123_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} replaygain/albumscanner.cpp replaygain/rgdialog.cpp replaygain/tagreader.cpp replaygain/jobcontroller.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} replaygain/albumscanner.h replaygain/rgdialog.h replaygain/tagreader.h replaygain/jobcontroller.h) set(ENABLE_REPLAYGAIN_SUPPORT 1) add_subdirectory(replaygain) endif () set(CANTATA_SRCS ${CANTATA_SRCS} tags/taghelperiface.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} tags/taghelperiface.h) add_subdirectory(tags) if (ENABLE_DEVICES_SUPPORT) add_subdirectory(3rdparty/solid-lite) if (MTP_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} devices/mtpdevice.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/mtpdevice.h) include_directories(${MTP_INCLUDE_DIR}) set(CANTATA_LIBS ${CANTATA_LIBS} ${MTP_LIBRARIES}) endif () if (CDDB_FOUND OR MUSICBRAINZ5_FOUND) if (CDDB_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} devices/cddbinterface.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/cddbinterface.h) include_directories(${CDDB_INCLUDE_DIR}) set(CANTATA_LIBS ${CANTATA_LIBS} ${CDDB_LIBS}) endif () if (MUSICBRAINZ5_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} devices/musicbrainz.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/musicbrainz.h) include_directories(${MUSICBRAINZ5_INCLUDE_DIRS}) set(CANTATA_LIBS ${CANTATA_LIBS} ${MUSICBRAINZ5_LIBRARIES}) endif () set(CANTATA_SRCS ${CANTATA_SRCS} devices/audiocddevice.cpp devices/cddbselectiondialog.cpp devices/cdparanoia.cpp devices/audiocdsettings.cpp devices/extractjob.cpp devices/albumdetailsdialog.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/audiocddevice.h devices/extractjob.h devices/albumdetailsdialog.h devices/cddbselectiondialog.h devices/audiocdsettings.h) set(CANTATA_UIS ${CANTATA_UIS} devices/albumdetails.ui devices/audiocdsettings.ui) # If CDDB/MusicBrainz5 found - then CDParanoia must have been! if (CDIOPARANOIA_FOUND) set(CANTATA_LIBS ${CANTATA_LIBS} ${CDIOPARANOIA_LIBRARIES}) include_directories(${CDIOPARANOIA_INCLUDE_DIRS}) else() set(CANTATA_LIBS ${CANTATA_LIBS} ${CDPARANOIA_LIBRARIES}) include_directories(${CDPARANOIA_INCLUDE_DIR}) endif() endif () set(CANTATA_SRCS ${CANTATA_SRCS} devices/devicespage.cpp devices/filejob.cpp devices/device.cpp devices/fsdevice.cpp devices/umsdevice.cpp devices/splitlabelwidget.cpp models/devicesmodel.cpp devices/actiondialog.cpp devices/devicepropertieswidget.cpp devices/devicepropertiesdialog.cpp devices/encoders.cpp devices/freespaceinfo.cpp devices/transcodingjob.cpp devices/valueslider.cpp devices/syncdialog.cpp devices/synccollectionwidget.cpp online/onlinedevice.cpp models/musiclibrarymodel.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/devicespage.h devices/filejob.h devices/fsdevice.h devices/umsdevice.h models/devicesmodel.h devices/actiondialog.h devices/devicepropertieswidget.h devices/devicepropertiesdialog.h devices/transcodingjob.h devices/valueslider.h devices/syncdialog.h devices/synccollectionwidget.h online/onlinedevice.h models/musiclibrarymodel.h) set(CANTATA_UIS ${CANTATA_UIS} devices/actiondialog.ui devices/devicepropertieswidget.ui devices/synccollectionwidget.ui) if (ENABLE_REMOTE_DEVICES) set(CANTATA_SRCS ${CANTATA_SRCS} devices/remotefsdevice.cpp devices/remotedevicepropertiesdialog.cpp devices/remotedevicepropertieswidget.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} devices/remotefsdevice.h devices/remotedevicepropertiesdialog.h devices/remotedevicepropertieswidget.h) set(CANTATA_UIS ${CANTATA_UIS} devices/remotedevicepropertieswidget.ui) qt5_add_dbus_interfaces(CANTATA_SRCS devices/mounter/${PROJECT_REV_URL}.mounter.xml) add_subdirectory(devices/mounter) add_subdirectory(devices/avahi) endif () else (ENABLE_DEVICES_SUPPORT) set(CANTATA_SRCS ${CANTATA_SRCS} devices/device.cpp) endif () else (TAGLIB_FOUND) set(ENABLE_DEVICES_SUPPORT OFF) set(ENABLE_REMOTE_DEVICES OFF) endif () if (ENABLE_PROXY_CONFIG) set(CANTATA_SRCS ${CANTATA_SRCS} network/proxysettings.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} network/proxysettings.h) set(CANTATA_UIS ${CANTATA_UIS} network/proxysettings.ui) endif () if (WIN32 OR APPLE) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} gui/singleapplication.h) set(CANTATA_SRCS ${CANTATA_SRCS} gui/singleapplication.cpp) endif () if (WIN32) set(CANTATA_SRCS ${CANTATA_SRCS} gui/application_win.cpp) elseif (APPLE) set(CANTATA_SRCS ${CANTATA_SRCS} gui/application_mac.cpp) else () set(CANTATA_SRCS ${CANTATA_SRCS} gui/application_qt.cpp) endif () if (WIN32) add_definitions(-DWIN32) add_subdirectory(windows) if (NOT CANTATA_WINDOWS_INSTALLER_DEST) set(CANTATA_WINDOWS_INSTALLER_DEST z:\ ) endif () if (MINGW) # resource compilation for MinGW ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cantataico.o COMMAND windres.exe -I${CMAKE_CURRENT_SOURCE_DIR} -i${CMAKE_SOURCE_DIR}/windows/cantataico.rc -o ${CMAKE_CURRENT_BINARY_DIR}/cantataico.o) set(CANTATA_SRCS ${CANTATA_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/cantataico.o) else () set(CANTATA_SRCS ${CANTATA_SRCS} windows/cantataico.rc) endif () elseif (APPLE) add_definitions(-D__APPLE__) endif () set(CANTATA_SRCS ${CANTATA_SRCS} gui/shortcutssettingspage.cpp gui/mediakeys.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} gui/multimediakeysinterface.h) QT5_ADD_RESOURCES(CANTATA_RC_SRCS ${CANTATA_RCS}) QT5_WRAP_UI(CANTATA_UI_HDRS ${CANTATA_UIS}) QT5_WRAP_CPP(CANTATA_MOC_SRCS ${CANTATA_MOC_HDRS}) if (WIN32) add_definitions(-DQXT_STATIC) add_subdirectory(3rdparty/qxt) set(CANTATA_SRCS ${CANTATA_SRCS} gui/qxtmediakeys.cpp) set(CMAKE_BUILD_TYPE "Release") ADD_EXECUTABLE(cantata WIN32 ${CANTATA_SRCS} ${CANTATA_MOC_SRCS} ${CANTATA_RC_SRCS} ${CANTATA_UI_HDRS} ${CANTATA_PO}) install(TARGETS cantata DESTINATION ${CMAKE_INSTALL_PREFIX}) elseif (APPLE) ADD_EXECUTABLE(cantata MACOSX_BUNDLE ${CANTATA_SRCS} ${CANTATA_MOC_SRCS} ${CANTATA_RC_SRCS} ${CANTATA_UI_HDRS} ${CANTATA_PO}) set(BREW_OPENSSL_PATH /usr/local/opt/openssl/lib) if (EXISTS ${BREW_OPENSSL_PATH}/libcrypto.1.0.0.dylib AND EXISTS ${BREW_OPENSSL_PATH}/libssl.1.0.0.dylib) install(FILES ${BREW_OPENSSL_PATH}/libcrypto.1.0.0.dylib DESTINATION ${MACOSX_BUNDLE_APP_DIR} RENAME libcrypto.dylib) install(FILES ${BREW_OPENSSL_PATH}/libssl.1.0.0.dylib DESTINATION ${MACOSX_BUNDLE_APP_DIR} RENAME libssl.dylib) endif() add_subdirectory(mac) include(DeployQt5) install(PROGRAMS ${CMAKE_BINARY_DIR}/cantata.app/Contents/MacOS/cantata DESTINATION ${MACOSX_BUNDLE_APP_DIR}) # Create our own plist file to enable HighDPI support configure_file(mac/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/mac/Info.plist) configure_file(mac/dmg/create-dmg.sh.in ${CMAKE_CURRENT_BINARY_DIR}/mac/create-dmg.sh) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mac/Info.plist DESTINATION ${CANTATA_APP_CONTENTS_DIR}) if (TAGLIB_FOUND) # *NEED* to install cantata-replagain/cantata-tags *before* install_qt5_executable fix'es the bundle # If we install from their own CMakeLists.txt then they are installed *after* the fix-up, and so link # to the build libs not the packaged ones! if (FFMPEG_FOUND OR MPG123_FOUND) install(PROGRAMS ${CMAKE_BINARY_DIR}/replaygain/cantata-replaygain DESTINATION ${MACOSX_BUNDLE_APP_DIR}) endif (FFMPEG_FOUND OR MPG123_FOUND) install(PROGRAMS ${CMAKE_BINARY_DIR}/tags/cantata-tags DESTINATION ${MACOSX_BUNDLE_APP_DIR}) endif (TAGLIB_FOUND) if (ENABLE_HTTP_STREAM_PLAYBACK AND NOT ENABLE_LIBVLC) install_qt5_executable(cantata.app "qjpeg;qsvg;qsvgicon;qcocoa;qsqlite;qavfmediaplayer") else (ENABLE_HTTP_STREAM_PLAYBACK AND NOT ENABLE_LIBVLC) install_qt5_executable(cantata.app "qjpeg;qsvg;qsvgicon;qcocoa;qsqlite") endif (ENABLE_HTTP_STREAM_PLAYBACK AND NOT ENABLE_LIBVLC) else () ADD_EXECUTABLE(cantata ${CANTATA_SRCS} ${CANTATA_MOC_SRCS} ${CANTATA_RC_SRCS} ${CANTATA_UI_HDRS}) install(TARGETS cantata RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) endif () if (WIN32 OR APPLE) add_subdirectory(3rdparty/qtsingleapplication) target_link_libraries(cantata qtsingleapplication) else () set(XDG_APPS_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/applications") endif () if (WIN32) target_link_libraries(cantata qxt) install(FILES tags/tag_fixes.xml context/lyrics_providers.xml context/weblinks.xml online/podcast_directories.xml scrobbling/scrobblers.xml DESTINATION ${CMAKE_INSTALL_PREFIX}/config/) elseif (APPLE) install(FILES tags/tag_fixes.xml context/lyrics_providers.xml context/weblinks.xml online/podcast_directories.xml scrobbling/scrobblers.xml DESTINATION ${MACOSX_BUNDLE_RESOURCES}/config/) else () install(FILES tags/tag_fixes.xml context/lyrics_providers.xml context/weblinks.xml online/podcast_directories.xml scrobbling/scrobblers.xml DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/config/) install(FILES mpd-interface/mpd.conf.template DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/mpd) endif (WIN32) add_subdirectory(icons) add_subdirectory(translations) add_subdirectory(support) add_subdirectory(3rdparty/qtiocompressor) add_subdirectory(streams/icons) add_subdirectory(online/icons) target_link_libraries(cantata support-core qtiocompressor ${CANTATA_LIBS} ${QTLIBS} ${ZLIB_LIBRARIES}) # enable warnings add_definitions(-DQT_NO_DEBUG_OUTPUT) if (UNIX AND NOT APPLE) configure_file(cantata-remote.cmake ${CMAKE_BINARY_DIR}/cantata-remote) configure_file(cantata.desktop.cmake ${CMAKE_BINARY_DIR}/cantata.desktop) install(PROGRAMS ${CMAKE_BINARY_DIR}/cantata-remote DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/scripts) install(FILES ${CMAKE_BINARY_DIR}/cantata.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) target_link_libraries(cantata -lpthread) endif () configure_file(config.h.cmake ${CMAKE_BINARY_DIR}/config.h) if (NOT WIN32 AND NOT APPLE) # uninstall target configure_file("${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) configure_file(install_dirs.cmake ${CMAKE_BINARY_DIR}/install_dirs) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake) endif () macro_display_feature_log() if ((NOT ENABLE_DEVICES_SUPPORT AND NOT WIN32 AND NOT APPLE) OR (NOT ENABLE_HTTP_STREAM_PLAYBACK)) message("-----------------------------------------------------------------------------") message("-- The following STANDARD options have been DISABLED.") message("-----------------------------------------------------------------------------") if (NOT ENABLE_DEVICES_SUPPORT) message(" * Devices Tab - Media device support (UMS, MTP, AudioCD).") endif () if (NOT ENABLE_HTTP_STREAM_PLAYBACK) message(" * MPD HTTP Streams - Playback MPD HTTP output stream via libVLC.") endif () message("") endif () if ((ENABLE_DEVICES_SUPPORT AND ENABLE_REMOTE_DEVICES) OR ENABLE_PROXY_CONFIG OR ENABLE_SIMPLE_MPD_SUPPORT) message("-----------------------------------------------------------------------------") message("-- The following NON STANDARD options have been ENABLED.") message("-----------------------------------------------------------------------------") if (ENABLE_PROXY_CONFIG) message(" * Proxy Configuration") endif () if (ENABLE_REMOTE_DEVICES) message(" * Remote Devices - Access sshfs, Samba, and local folders as devices. (EXPERIMENTAL)") endif () if (ENABLE_SIMPLE_MPD_SUPPORT) message(" * Simple/Personal (Cantata controlled) MPD instance support") endif() endif () cantata-2.2.0/ChangeLog000066400000000000000000003714621316350454000147160ustar00rootroot000000000000002.2.0 ----- 1. Add option to specify number of play queue tracks for dynamic playlists. 2. Add option to set application style. 3. Fix potential issue with priority menu items being disabled. 4. When adding items with a custom priority, or updating a custom priority, add option to have this priority decrease with each item. 5. Remove unity menu icon work-around. 6. To support older GNOME settings daemon installations, if fail to use the new MediaKeys DBUS interface then use the previous. 7. Fix desktop detection via XDG_CURRENT_DESKTOP - check for colon separated values. 8. If an error is to be shown, ensure Cantata is not minimised to system tray. 9. If the initial start-up connection fails, try again every .5 second for a few seconds. 10. In playlists page, internet, etc, allow back navigation to go fully back. 11. Don't try to seek if no song loaded. 12. Only use menubar for macOS builds. 13. Smart playlists - like dynamic, but do not auto update. 14. Use em-dash to as separator. 15. Add device option to only transcode if source is FLAC or WAV (detection is solely extension based). 16. Fix extraction of album names from DB - for use in tag editor and playlist rules dialogs. 17. Fix some potential security issues - thanks to Jonas Wielicki for the patches. 18. Only set Qt::AA_EnableHighDpiScaling for Windows builds. 19. Fix sidebar highlight for windows (at least for Windows 10). 20. Only enable system tray for Linux if org.kde.StatusNotifierWatcher DBUS service is registered. 21. Fix MPRIS track path. 22. Fix MPRIS can go next/previous state changes. 23. When playing MPD's HTTP output, stop backend when MPD is paused. 2.1.0 ----- 1. Re-add all album sorts from Cantata 1.x 2. Try to detect DLNA streams (e.g. when using upmpdcli), and show as regular albums in grouped view. 3. Add filename / path to list of dynamic rule properties. 4. Flat current track highlight. 5. When adding tracks from folders view, only add playlists if these have been explicitly selected. 6. Allow to set keyboard shortcuts for ratings actions. Default to Alt+0 (No rating), Alt+1 (1 star), etc. 7. Re-add genre combo to library view. Only visible if grouping by artist or album. 8. When adding a podcast (or other track from an internet service), remove any new-lines from metadata. 9. When configuring streams, clear list of providers before re-populating. 10. If a 'Basic' mode connection fails, re-start spawned MPD instance (and remove any previous pid file). 11. Fix Jamendo and Magnatune covers. 12. Fix various issues with 'Personal' MPD instance. 13. Fix saving, and restoring, of podcast 'played' status. 14. When adding streams to play queue via add dialog, always allow setting of name. 15. Use Pulse Audio for 'Personal' MPD instance. 16. Always return true for MPRIS CanPlay, CanPause, etc. 17. Work-around KDE 5.7 MPRIS issues. 18. If can't load SQLite db, then show error. 19. Don't show custom actions menu entry if there are no actions. 20. Fix add/set priority menus. 21. Match view mono icons to text colour. 22. Use FontAwesome icons for all action icons. 23. Send a message at least once every 5 seconds to MPD, to ensure connection is still valid. 24. Fix updating of playlists if these contain duplicates and are modified by another client. 25. Cache up to 4 genres in SQL db. 26. Fix crash when changinh playqueue view type. 27. Use same sidebar inactive tab mouse-over for all styles. 28. Fix colouring issues with some Kvantum styles. 29. Abort network connections before closing. 30. When listing albums where composer is used for artist grouping, place album artist name after album name if different from composer. 31. If file has embedded cover, save this to the cache folder - so that this file path can be used with MPRIS. 32. Fix scrobbling when Album is empty. 33. Fix duration of last track for split CUE files. 34. Move stream listings to github. 35. Fix local file playback on remote MPD when MPD's curl is using IPv6. 36. Install symbolic icon for GNOME shell. 37. Add sort by track title to playqueue. 38. Read lyrics from MP4 files. 39. Only scroll playqueue if current song changed. 40. Support disc number in CUE files. 41. Remove Gtk themeing hacks. Qt styles such as Kvantum should be used to mimic better Gtk support. 42. Japanese translation. 43. Allow single-key shortcuts. 44. Improve Mopidy support. 45. Enable support for Opus tags if enabled in TagLib. 46. URL encode online stream URLs before passing to MPD. 47. Show podcast descriptions in tooltips. 48. Parse name field in playlists. 49. Use 32-bit int for bitrate and samplerate staus values. 50. Remove Qt4, KDE4, and Ubuntu touch support. 51. When playing MPD's HTTP output stream, check periodically (for up to 2 seconds) to confirm backend is playing. 52. When playing MPD's HTTP output stream, don't stop playback on pause. 53. Add button on podcasts page to show only unplayed podcasts. 54. Add min/max duration to dynamic playlist rules. 55. Use Qt5's translation framework - ts files, not po files. 56. When trying to read lyrics files; check for .txt extension as well as .lyrics. Also check ~/.lyrics/Artist - Track.txt 57. Add 3 seek levels (5 seconds, 30 seconds, and 60 seconds), with assignable shortcuts. 58. When adding files to playqueue, and in btaches of up to 2000 files. 59. Make all of Cantata's internal actions accessible via DBUS. See README for details. 60. Add support for OriginalDate tag. 61. Bundle newer openSSL with macOS builds. 62. Update copy of libebur128 63. Use libcdio_cdparanoia 64. If 'composer genre' is set in tweaks, then use composer to sort artists. 65. Add 'Sort by track number' to playqueue. 66. Enable retina support for all builds. 67. Store replaygain settings in Cantata's config file, as it appears MPD does not persist changes. 68. If HTTP requests are redirected, copy over original headers. 69. When AudioCD changed, delete its cached downloaded cover. 70. Fix adding covers to MTP devices when transcoding. 2.0.1 ----- 1. Delay creation of Jamendo and Magnatune DBs until required. 2. Fix 'Scroll to current track' in table style play queue if track number column is hidden. 3. Add icon for proxy config - if proxy settings enabled. 4. Fix possibly missing save play queue icon. 5. Install pre-rendered PNG icons for Linux builds. 6. Use last.fm 2.0 API for finding similar artists in dynamic playlists. 7. Fix listing of CUE files. 8. Only honour 'startHidden' setting if also configured to use system tray. 9. Folder page nolonger has a search field - so if upgrading from a 1.x config with folder search visible, then hide it. 10. Don't allow copying of cue file tracks to devices. 11. When calculating collapsed window height, take into account size of menubar, if it is visible. 12. Try to ensure menu button width is at least equal to height. 13. Fix compilation on some systems. 14. Remove usage of libavutil/audioconvert.h - its no longer in ffmpeg since 1.3, and Cantata does not need it anyway. 15. Fix playback of AudioCDs 16. Fix incorrect AudioCD cover 17. Fix Qt5 gcc5 compilation. 18. Fix wrong/missing ratings in toolbar. 19. Fix compilation with Qt5.7 20. Fix drag'n'drop of non-loaded playlists. 21. Use a single-shot timer to timeout obtaining current cover. 22. Fix AudioCD playback when MPD's curl is using IPv6 23. Fix current track display when chaging from one track with no meta-data to another with no meta-data. 2.0.0 ----- 1. Use SQLite database to cache MPD library. 2. Combine Artists and Albums tabs into a single Library tab. Provide option to group library by; Genre, Artist, or Album 3. When sorting artists and albums, if there is no sort tag then remove any periods from the main tag before using it to sort. e.g. A.S.A.P. -> ASAP 4. Use SQLite for Jamendo and Magnatune libraries. 5. Rename Online view to Internet. 6. Place Streams view within Internet. 7. Place stored playlists and dynamic playlists within Playlists. 8. Remove option for non-mono sidebar icons. 9. Always build with Dynamic, Online, and Streams support. 10. If connected to MPD>=0.19 using address 127.0.0.1 or localhost, then pass local files as 'file://' URLS. 11. Use regular artist icon for "Various Artists" 12. If MPD does not support 'sticker' command, then inform user that ratings cannot be stored. 13. When populating library, check genre list. If this is empty, then do dot attempt to populate library. The UPnP database backend will not populate MPD's metadata listings (genre, artists, etc), and calling "lsinfo" on all UPnP folders is very slow and will lead to duplicate tracks. 14. Add 'Copy To Device' to playqueue. 15. Do not reset current song when shuffling albums, or sorting playqueue. 16. MPD 0.19.2 can handle m3u8, so pass stream URLs of this type straight to MPD. 17. Re-add hack to force scrollbars in large combo popups and to restrict their height. This should apply to all Linux Gtk-like styles, not just QGtkStyle. 18. Use '#' for track number column title in table view. 19. Allow setting of column alignments in table views. 20. Add 'setCollection' to Cantata's DBUS interface. 21. Add 'Collections' and 'Outputs' menus to system tray menu (Linux and Windows builds only) 22. Separate title and track number in search model table view. 23. Show rating in search model table view. 24. Rename mpd source folder to mpd-interface to help build errors when libmpdclient(?) is also installed. 25. Add option to provide a list of genres which should use composer, and not album-artist, to group albums. 26. Fix updating of composer tag. 27. Fix build with proxy config and Qt5 28. For Linux builds, if system tray icon is null (becasue QIcon cannot find it) then add icon files manually. 29. Allow local socket path to start with ~ 30. Ensure consistent order when drag'n'drop from list views as per tree views. 31. Remove touch friendly setting from builds unless -DENABLE_TOUCH_SUPPORT=ON is passed to CMake. 32. Dirble v2 API. 33. When removing duplicates, take track number and album into account as well as artist and title. 34. Resolve TuneIn radio URL's before adding to favourites (if added via TuneIn search). 35. Use mpd.cantata for DBus service names and not com.googlecode.cantata 36. Fix OpenBSD build. 37. Add option to set which prefixes to ignore when sorting. 38. Add option to disable MPRIS interface. 39. Default to UDisks2 40. Show podcast published date on sub-text, and duration in brackets. 41. Add option to control cue file handling. 42. New options to add songs to play queue - 'add and play', 'append and play', and 'insert after current' 43. Custom actions. 44. Set HTTP server to listen on all addresses, but use IP address of socket connected to MPD for HTTP URLs. 45. libCDDB can crash if it cannot talk to server, so before querying check whether we can connect. 46. Save scaled covers as PNG, as the quality is much better. 47. Use papirus icons for Windows and Mac builds. 48. Add an uninstall target for Linux builds. 49. Add 'Append Random Album' option to library, jamendo, and magnatune pages. 50. Don't use KWallet for MPD password - it's overkill, as MPD password is sent in plain text! 51. Fix Lastfm response parsing. 52. Dynamically load folder view. 53. Fix starting Cantata maximised. 54. Ignore 'The' (if configured) when sorting play queue. 55. No longer using discogs - API has changed. 56. Fix context widget backdrop retrieval from fan-art 57. Fix/work-around Qt 5.5 issues with QMenu being used in 2 actions. 58. When looking for covers, also check sub-folders and return first image. 59. When saving covers in the conver dialog, if dest folder does not exist then save in the cache folder. 60. Build with HTTP stream playback enabled by default. 61. Use LibVLC by default for MPD HTTP stream playback on Linux. 62. Default to Qt5 builds. 63. Fix copying songs to devices - incorrect number of bytes were transferred. 64. Only use MTP device with BUSNUM and DEVNUM properties. 65. Capitalise first letter of device name. 66. Use flat XML to store device music listing cache. 67. Add Composer and Performer to play queue sorts. 68. Always enable save to playlist actions, even if view is hidden. 69. For Qt-only builds - use own icons for non-KDE desktops, or if using breeze icons with KDE. 1.5.2 ----- 1. Fix Ubuntu Touch builds. 2. When refreshing search menu, clear items first! 3. Fix setting of cover when existing cover is embedded in music file. 4. Fix OSX executable name for case-sensitive filesystems. 5. Hide ratings widgets, etc, in tag editor for devices and Mopidy, etc. 6. Use Control+Alt+Number as shortcut to toggle an output. 7. Don't allow to set short-cuts for actions that are menus. 8. Add high-dpi support to Linux and Windows Qt5.4 builds. 9. When calculating ReplayGain, if peak value is less than 0.00001 then assume the calculation is invalid. 10. When parsing podcast RSS, if episode is marked as video (e.g. video/mp4) but the url ends in an audio extension then it is proably an audio podcast. 11. Correctly initialise seach category. 12. Fix potential crashes on refresh. 13. Fix duplicate notification when Cantata is started whilst playing, or when 'Replace Play Queue' is used. 14. Only show output change notification if outputs menu was not previously empty. 15. Construct a new config object, rather than changing group, to avoid any race conditions. 16. If fading volume on stop, then reset volume just before stopping. Some outputs (e.g. pulse audio) only allow setting a volume whilst playing. 17. If 'url' entry is empty in 'enclosure' section of podcast RSS file, then use 'guid' text as url - if possible. 18. Fix copying of covers to UMS, etc, devices if song is transcoded. 19. Add an option for 64 bit non KDE linux builds to install helper apps to lib64 instead of just lib. Pass -DCANTATA_HELPERS_LIB_DIR=lib64 to cmake. 20. In tag editor, only mark rating as changed if it has been. 21. For Linux non-KDE builds, use login1 interface to detect system resuming. 22. Enable scrobble 'love' button even if scrobbling is disabled. 23. Don't crash when detecting an audio CD with no tracks. 24. When playing a digitally imported (or JazzRadiom, etc) stream from the favourites section, then modify the URL if the user has a premium account (to match what existing behaviour is stream is played from the station list) 25. Workaround build issues with SpeexDSP 1.2rc2 26. Use correct stream icon in playqueue for streams with no song details. 27. Fix FreeBSD build. 28. Respect carriage returns when displaying comments in context view. 29. Fix replaygain calculation when ffmpeg is using planar formats. 30. Enable 'save' button when reading ratings from multiple files. 31. Fix cantata-remote script (used for Unity launcher integration) when this uses qdbus. 32. Fix display of rating in tag dialog when only 1 file is being edited. 33. Fix fetching of ratings in table style playqueue. 34. Don't convert -ve track, disc, or years to unsigned - set to 0. 35. Bundle openSSL libs with windows builds. 1.5.1 ----- 1. Show correct separators for windows builds. 2. Supply TagLib 1.9.1 for windows builds. 3. Convert filename to UTF16 before passing to TagLib for windows builds. 4. When emiting signal to say cover is loaded, need to adjust size by pixel ratio. 5. Fix updating of toolbar coverwidget if cover is downloaded after song has started. 6. Fix compilation when online services disabled. 7. Fix dynamic playlists with no include rules. 8. Re-add option to show artist images in tree and list views. 1.5.0.1 ------- 1. Add missing libtag.dll to windows setup. 1.5.0 ----- 1. Remove cover size setting, set automatically. 2. Artist images only shown in grid view. 3. No images, or icons, shown in basic tree view. 4. Remove GUI option to control saving of scaled covers. This is enabled by default, and can be toggled via the config file. 5. Don't re-load view when sort changes. 6. Simplify view config pages. 7. Use QUrl with server details to build HTTP address used to compare MPD http file paths against. 8. Store song ratings in MPD's sticker DB. Ratings stored using 'rating' key and values 0 to 10. In the UI, ratings are show as 5 stars. 9. If we fail to download a cover, don't keep trying next time song is played. 10. Simplify toolbar cover widget, no border. 11. Allow preference dialog to shrink smaller. If screen size is less than 800px, then views page is re-arranged to allow much smaller dialog, category selector uses smaller icons, and headers are removed.. You can check this setting by calling cantata as follows: CANTATA_NETBOOK=1 cantata 12. Support MPDs "find modified-since" with MPD 0.19 and newer. If a number is entered it is taken to be 'modified since X days in past', otherwise a date should be entered (e.g. 01/12/2001 to find all tracks since 1st Dec 2001) Cantata will first try to convert from your locale date format, default date format, and then ISO date format. 13. Show covers in search results. 14. Show performer in cover tooltip if this is set and different to album artist. 15. Always large action icons for grid view. 16. Increase gap between add and play icons in grid view. 17. Disable volume fade on stop by default - this is really something MPD itself should implement. 18. Remove 'Add albums in random order' from view context menus. 19. Reorganize playqueue context menu. 20. Use 'Metadata' and not 'Tags' as metadata/tags view in context song pane. 21. Minor changes to song progress slider. 22. Show covers in playlist tree and list views. 23. Use read-only editable combo for filename in tag editor, so that text can be selected. 24. Also read /com/canonical/desktop/interface/scrollbar-mode to determine if overlay scrollbars have been disabled. 25. Add note about 'AlbumArtist' tag to initial settings wizard. 26. Use QDesktopWidget::logicalDpiX()/96.0 to set DPI scale factor. 27. Expand ~/ to QDir::homePath() when read from UI. 28. Save QDir::homePath()/ as ~ in config file, and convert when read. 29. Allow all bar title and artist columns to be hidden in playqueue. 30. Add option to disable song info tooltips. 31. Only show icons in message widget buttons if style uses icons for dialog buttons. 32. Add option to have separate play queue search action - enabled by default. 33. Revert back to storing scaled covers as JPG. PNG is taking too much space, especially with retina displays. 34. Read ArtistSort, AlbumArtistSort, and AlbumSort from MPD. If found, use these to sort items. 35. Add 'Full Refresh' action - cuases caches to be removed, and models to be completely refreshed. 36. Add actions to mark podcast as episode as listened or new. 37. Add action to cancel podcast downloads. 38. Download podcasts sequentially. 39. Configurable limit to auto podcast downloading. 40. When starting, remove any previous partial podcast downloads. 41. Don't make media keys backend configurable, auto detect the best one. 42. Remove Gtk combo popup size hack. 43. Fix Qt5 translations. 44. Add button to status bar to eanble/disable playback of MPD HTTP output stream. 45. Show notification when outputs changed. 46. Connect MPRIS stopAfterCurrent signal to correct action. 47. Add prev/play/pause/etc actions to Unity launcher, and to windows taskbar entry. 48. Fix current track highlight in grouped view under windows for header item. 49. Use same selection drawing for all views. 50. Fix size of collapsed window in windows builds. 51. If run under Unity or OSX, then place close buttons on left. 52. Fix auto-marking of played downloaded podcasts when connected via a local socket. 53. Fix playback of local files by inbuilt HTTP server. 54. In catata-tags.exe set unhandled exception handler, to prevent windows crash dialog appearing. 55. If a command fails to be sent to MPD, and the socket is in an error state, then reconnect and update status and playqueue. 56. For windows builds, when window is not focused, draw sidebar selection as a darkened background. 57. If MPD supports https, then there is no need to convert SoundCould URLs. 58. If playlist is loaded and replaces playqueue, then start playback of first track. 59. Custom/thin scrollbars for context view. 60. If fail to read a Gtk setting from DConf then try GSettings. 61. Send a message (default to 'status') at least once every 30 seconds to keep command connection alive. 62. Fix inconsistent default cover sizes. 63. French translation - thanks to Jaussoin Timothée. 64. Seek 5seconds when control (or command for Mac) and lef/right arrow keys are pressed. (Seek setting may be changed via config file - refer to README for more details.) 1.4.2 ----- 1. When guessing song details from filename (due to missing tags), remove any extension - not just three letter extensions! 2. In tag editor, only show '(Various)' hint for the 'All tracks' entry. 3. Also capitalise composer in tag editor. 4. Resize device properties dialog when changing transcoder. 5. Initialise sidebar icon sizes before constructing. 6. Display multiple genres as separate entries in playlists page genre combo. 7. Resize cover in grouped view if it is too wide. 8. In context view, when creating links to similar artists compare artists names case-insensitively. 9. Fix downloading of podcasts when url is redirected. 10. If 'force single click' is disabled, then double-click on icon view will not add artist/album to playqueue but navigate into. 11. When using Cache config page to clear disk cover/scaled-cover cache, also clear memory cache. 12. Allow to change stream, and online-service, filter search category without closing current search field. 13. Fix combo-box text changed signal for Qt5 builds. 14. Don't have double spacer when cover-widget is hidden. 15. Fix crash in context view if song tracks changed quickly. 16. Better method of setting disabled opacity for monochrome icons. 17. Fix parsing of podcast RSS files containing "content" tag. 18. Set user-agent for podcast URL queries, otherwise requests can fail. 19. Use https://googledrive.com/host/XXX and not https://drive.google.com/uc?export=download&id=XXX for stream provider URLs, etc. Seems like the drive.google.com URLs have download limits. 20. Fix crash when changing buttons of dialogs in Qt builds. 21. Fix setting of played state for podcasts. 22. Fix display of unplayed podcast episode counts. 23. Check if downloading podcasts in closeEvent() as well as quit() 24. If initial settings wizard is canceled, ensure that it is shown at next start. 25. Ignore empty station names in shoutcast and dirble. 26. Fix potential crash when parsing cue files. 27. Replace any of (/ ? < > \ : * | ") with underscore when creating cover file cache names under windows. 1.4.1 ----- 1. Remove unused var warning when compiling without online services. 2. Remove some moc/QObject warnings in KDE builds without streams or http server. 3. Update current song to scrobble, even if disabled, so that when enabled we can scrobble. 4. Seems like MPDSribble itself is broken with regards to sending "love" status, so remove MPDScribble from list of scrobblers. 5. Last.fm replies are XML, so decode these properly! 6. Don't log scrobbler session key to debug file. 7. Remove extra margin in podcast settings dialog. 8. When adjusting track numbers in tag editor, start from first actual track. 9. Only enable 'center on current track' action if there is a current track. 10. Use comma to split multiple genres in tooltip and table views. 11. Don't URL encode scrobble keys, just values. 12. If now playing has been sent, track has not been scrobbled, then when track is played after beeing paused for more than 5 seconds resend now playing. 13. Rescrobble, and re-send now playing, if track is repeated. 14. Exceptions are required for all non Qt5 builds. 15. Fix crash in settings dialog when some system-tray options removed. 16. Use QIcon::fromTheme to set system-tray icon. 17. Fix compilation with older ffmpeg versions. 18. Fix compilation with taglib versions older than 1.8 19. Use KDE cmake macro to enable exceptions in KDE builds. 20. Don't alter text of 'Other Views' tab in preferences dialog if streams, online, and device support are disabled - as there are still 2 views; folders and search. 21. Only remove cached scaled covers if updated via cover dialog. 22. If auto-splitter hide is disabled, then restore sizes. 23. ReplayGain settings only valid for MPD v0.16 onwards. 24. Don't center align context view headers and images - as this does not always work for images. 25. Attempt to align song view selector with header. 26. Remove 'Add albums in random order' from search view context menu. 27. Remove mention of streams from initial settings wizard file settings. 28. Fix updating of playqueue background when option changed in preferences dialog. 29. Fix reading of playqueue, and context, backdrop settings. 30. Fix uneven view heights when touch friendly setting is enabled. 1.4.0 ----- 1. Allow setting of custom device name for UMS and MTP devices. 2. Allow to use table-view for search results. 3. Add option to auto-switch to context view X seconds after playback starts, and switch back X seconds after stopped. 4. Add option to auto-scroll lyrics - accessed via context-menu in lyrics view. 5. Use smaller font for sidebar. 6. Make cover-widget more stylized and smaller. 7. Move cover-widget next to song details. 8. Slightly larger play/pause button. 9. Move position slider in-between controls. 10. Use system libebur128 if found. 11. Use smaller font for second line of current song details. 12. Don't show song details in titlebar - this only duplicates info that is already clearly visible in the main window. 13. Remove space between toolbar and views, and reduce spacing elsewhere. 14. Use a thin splitter between playqueue and views. 15. Set 'no interaction' flag on more labels, so that Oxygen's window drag code works on these. 16. Add support for comments. These are read directly from the song file when the tag-editor is used, and they are NOT saved in Cantata's cache file. Comments are only read if MPD is configured to support the COMMENT field. 17. Install Cantata's list of lyrics providers to INSTALL/share/cantata/config 18. Read lyrics providers from each lyrics_*.xml file in ~/.local/share/cantata/config, and INSTALL/share/cantata/config 19. Attempt to respect menubar usage of current desktop. For Windows and Gnome; menu button is used, and menubar hidden. For Mac and Unity; menubar is used, and menu button is hidden. For KDE Plasma; by default the menu button is used, and the menu bar hidden - but a menu entry is provided to toggle. This setting can be overridden - see README for details. 20. Most songs will be less than 1 hr long, so by default we only need to reserve space for -MM:SS at each side of position slider. If song duration is longer than 1hr, then this is increased to -H:MM:SS 21. Show number of tracks under playqueue, even if total time is 0. 22. Enable song notifications in Linux builds without QtDBus. 23. Speed-up MPD response parsing, by only converting strings to UTF-8 when required. 24. In Albums view, always show album name as main text and artist as sub-text regardless of chosen sort. 25. When context view is collapsed, draw background over selector buttons. 26. Be consistent with displaying years of albums - year is shown in brackets after album name. 27. Reduce memory usage, by storing album covers in a cache - and using this for display. Size of cache can be controlled by config item, see README for details. 28. Only save albumartist tag in XML cache file if library supports this tag. 29. Use MPD status to determine when to show, and hide, 'Updating...' message. 30. Ignore mouse-events on message-overlay if cancel button is hidden. 31. Update copy of QJson to 0.8.1 32. Speed up building list of songs - by only checking if file has already been added, not song. 33. After scanning replay gain, show original tag values in tooltips if different to those calculated. 34. Don't use italic text for grouped view headers. 35. Save scaled covers as PNG files. 36. Use Q_GLOBAL_STATIC for Qt4.8, and Qt>=5.1 Qt-only builds. 37. Show music folder location in device properties dialog when called from copy/delete dialog - just don't allow setting to be changed. 38. Remove artist image support for online-services. 39. Update context-view when artist, or album, image is updated. 40. When current song is from an online-service (Jamendo, Magnatune, etc) then only show cover and title in context view. No attempt is made to get artist, album, or song information - as these are likely to fail anyway. 41. Use an external-helper app to read/write tags - to isolate Cantata from TagLib crashes. 42. Add -DUSE_OLD_DBUS_TYPEDEF=ON to CMake options. This can be used to force usage of OLD dbus XML files. 43. If Cantata detects that an album, or artist, has new songs after updating MPD's DB, then the artist and album are drawn with bold text. 44. When searching for album covers, do not look for $songFile.png/jpg 45. Hide track change popup setting if running under Linux and org.freedesktop.Notifications DBus service is not registered. 46. Remove 'home' button from listviews - only ever max 2 levels deep (Artists, Albums, Tracks), so no real need for button. 47. Make shortcut for 'Go Back' action changeable. 48. Don't attempt to find Amarok or Clementine covers. 49. Don't hard-code artist name fixes, read from $install/tag_fixes.xml 50. Remove grouping of multiple-artist albums under 'Various Artists' 51. Change default sidebar shortcuts from 'Alt+' to 'Ctrl+Shift+' so as to avoid clashes with menubar. 52. If compiled with MPD HTTP playback support, when MPD stream is paused then stop the Phonon/QMediaObject from playing. See README for config option on this. 53. When searching, search both with and without diaeresis, etc. e.g. both Queensrÿche and Queensryche should match. 54. Add 'Sort By' to playqueue context menu - allowing to re-sort tracks by artist, album artist, album, genre, or year. 55. When sorting albums view, also ignore 'The ' at start of artist names, as per artists view. 56. Add more sort options to albums view. 57. Fix crash when changing view mode whilst search is active. 58. Add a 'touch friendly' setting - toolbuttons are made wider, view actions always visible (semi-transparent in list/tree), and views are 'flick-able' 59. Use a toolbutton for listview header and back action. 60. Add 'Locate In Library' to search page. 61. Remove 'Go Back' from listview context menu. 62. Hide icons in shortcut dialog, and in actions used in menus, if Qt::AA_DontShowIconsInMenus is set. This seems to be the only way to force Unity to not show icons in menus of Qt apps. (There appears to be a bug in libdbusmenu-qt https://bugs.launchpad.net/libdbusmenu-qt/+bug/1287337) (Qt-only builds) 63. If connection to MPD faiils, attempt to ascertain if its a proxy error. 64. Add button under playqueue to re-center playqueue on current song. 65. Add option to only act on songs that pass current string or genre filter. 66. Multiple genre support. 67. Store favourite streams as '[Radio Streams]' MPD playlist. 68. For tracks for mutliple-artist albums, show as 'title - artist' and not 'artist - title' 69. In playqueue, if song's artist is different to album artist, then show song as 'title - artist' 70. Also use musicbrainz_albumid, if present, to group albums. This can be used to group multiple releases of the same album. MPD must be configured to use this tag in order for this to function. 71. Search for streams via dirble. 72. Center images and headers in context view. 73. Don't package stream providers with Cantata - add a dialog to download from Google Drive. 74. Fix 'duplicate' albums being created if flac (or other) + cue file is used, and the music file does not contain metadata. 75. Fix setting of cover for albums with cule file. 76. Fix loading individual songs from cue file. 77. Build as a Qt-only app by default. Pass -DENABLE_KDE=ON to create KDE build. 78. Scrobbling support. 79. Install podcast directories file to $install rather than embedding. 80. cantata-dynamic perl script now uses MPD's client-to-client messages for control. Therefore, MPD>=0.17 is required. 81. Remove 'Stop dynamizer on exit' option. Stop dynamzer if 'Stop playback on exit' is enabled. 82. Read podcast_directories.xml, tag-fixes.xml, and scrobblers.xml from install folder and ~/.local/share/cantata 83. Update replaygain ffmpeg input code to handle API changes in libavcodec. 84. Fix transcoding tracks to MTP devices. 85. Restore search field visibility for; PlayQueue, Artists, Albums, Folders, Playlists, Dynamic, and Devices views when started. 86. Fix custom background images (play queue and context) not appearing when Cantata started. 87. If artist is different to album-artist, then show track title as "title - artist" 88. In context-view rename Lyrics pane to Track. This is now a stack of lyrics, information, and tags. Tags contains all tags as read by taglib. 89. Add option to re-load lyrics from disk. 90. Add "Open In File Manager" to folders page for windows and mac builds. 91. Use external editor to edit lyrics. 92. Allow searching on performer tag. 93. Remove ID3v1 when saving ID3 tags. 94. 25% larger items in playqueue. Incrases covers from 32px to 40px 95. Fix replay-gain scanner if file name, or path, contains non-latin1 characters. 96. Install Ubuntu mono sys-tray icons to $prefix/share/icons/ubuntu-mono-dark/apps/22/cantata-panel.svg and $prefix/share/icons/ubuntu-mono-light/apps/22/cantata-panel.svg. But only if compiled for Ubuntu, or -DINSTALL_UBUNTU_ICONS=ON is passed to cmake. 97. Hide system-tray option in Qt builds if system tray is not available. 98. In non Dbus Qt builds, if notifications is checked ensure that sys tray is also checkedd. Conversly if sys tray is unchecked, uncheck notifications. 99. Add option to use libVLC for MPD HTTP stream playback - see INSTAL file for details. 100. Also show first IP address of interface in HTTP server settings page. 101. Fix progress update when saving Jamendo, etc, listing XML. 102. Add option to hide cover-widget. 103. Add option to hide stop button. 104. Add a Url label to exit full screen. 105. Use METADATA_BLOCK_PICTURE to extract covers from Vorbis comments, and "COVER ART (FRONT)" for APE tags. 106. Remove old library cache files when server settings changed. 107. Use QSslSocket to determine if Qt is linked to OpenSSL 108. Use Qt5's own json parser for Qt5 builds. 109. Fix Qt5 soundcloud searches. 110. Clear MPD errors after display. 111. More information in song tooltips - use table-style display. 1.3.4 ----- 1. Fix adding songs to playqueue with priority when playqueue is not empty. 2. When loading covers, if load fails, check to see if this is because of an incorrect extension. e.g. load a .jpg file with PNG type, and vice versa. 3. Fix showing of time-slider when Cantata is started minimized to system tray and MPD is playing. 4. Remove unused 'Back' action. 5. Don't treat albums with artists such as 'AlbumArtist ft Other' as multiple artist albums. 6. Fix updating of playqueue sidebar tooltip and icon. 7. Add playqueue action to main window, so that shortcut works. 8. When using track organizer, also try to move artist images, backdrop images, and playlist files. 9. After altering filename scheme from track organizer, save settings. 10. Don't set year or disc in tag editor for 'All Tracks' unless they are non zero. 11. In tag-editor, when obtaining values to use for 'All Tracks' entry, don't ignore empty fields. 12. When determining album year, ignore playlist files! 13. If MPD database is refreshed whilst listview is not at top level, then reset view to top level. 14. When TrackOrganizer is called after TagEditor, only perform MPD update once. 15. When editing filename scheme, after inserting a variable set cursor position to after newly inserted text and give focus back to line edit. 1.3.3 ----- 1. If Cantata is started whilst an instance is also running, raise window of current instance. (Linux-only fix) 2. Add song to playqueue from albums page when double-clicked. 3. Fix display of online service track album-artist in copy dialog. 4. Ensure library is loaded the very first time Cantata is run (ie. after connected to MPD via initial settings wizard). 5. When song is updated in context view, abort any current network jobs. 6. Fix expand all / collapse all actions. Expand all will only expand 1 level for main browsers. 7. When reading lines from user mpd.conf file, read as UTF-8. 8. Reset devices and online services models when artist view cover size changes. 9. If downloading online service track listing fails, stop loader and show error. 10. Use new location of Jamendo DB listing. 1.3.2 ----- 1. In cover/artist dialog, when attempting to save an image into Cantata's cache folder - ensure the 'covers' sub-folder is created. 2. When saving artist image from cover dialog, if set to save in MPD folder and song's path does not have 2 folder elements then save in cache folder. Cantata will only save artist images, and backdrops, in MPD folder if the folder structure is (e.g.) Artist/Album/Track.mp3 3. Use case-insensitive sort of folder view items when adding to playqueue. 4. When determining the 'basic' artist of a track, if albumartist is set and artist is not, then use albumartist. 5. For the web-links in the context view, replace ampersand and question mark characters in artist names with the relevant URL encoding. 6. Use CMake to locate X11 libraries and headers. 7. Set minimum size for initial settings wizard. 8. If buddy of a checkbox or radio is clicked, then toggle check or radio. 9. Disable context-view cancel actions until a job is created. 10. When searching models, also search composer field. 11. Fix enabling of "Initially collapse albums" in playlists settings tab. 12. Fix grouped playlist view initial state when set to initially collapse albums. 13. Fix calculating size of song files when copying off a device that has been previously scanned, and its contents cached. 14. When copying from a CD, check that we have enough space for track that was encoded with a 1:18 ratio. Just a sanity check really. 15. Fix song notification mem-leak (Linux only). 16. When transitioning between backdrops in cover view, draw old at correct position. 17. Don't load library twice at start-up when cache has been deleted current tab is artists or albums. 1.3.1 ----- 1. Fix collapsed context-view selector buttons text when using a dark background. 2. Apply some Fedora patches. 3. Fix MPD HTTP stream playback. 4. Fix painting of Gtk thin scrollbar style on comboboxes. 5. When waiting for replies from MPD, use Qt's standard timeout value of 30s. While the socket is still connected, Cantata will wait up to 4 times the timeout value. Therefore, the actual timeout has increased to 2minutes! 6. If a sidebar view has focus, but sidebar is collapsed, then find action should activate playqueue search widget. 7. FreeBSD 10 compile fixes. 8. Fix scaling of context view background when size of view changes. 9. Elide text of context view selector buttons, if insufficient space. 10. Only show mopidy note in copy/remove dialog if connected to mopidy. 11. Fix broken en_GB translation. 12. Fix folder-view path parsing. If two folders only differed in the last character then Cantata would have placed the contents of the second into the listing of the first. 13. Explicitly link to pthread. 14. Do not allow 'auto-hide sidebar' and 'playqueue in sidebar' to be active at the same time. 1.3.0.1 ------- 1. Fix duplicate translations - causes KDE builds to fail. 1.3.0 ----- 1. Add option to control startup-state; visible, hidden, or remembered from previous run. 2. Basic undo/redo support for playqueue. 3. Search tab. Hidden by default, enable via settings dialog. 4. Optionally save scaled covers (used in artist and albums views) to disk. 5. Add option to always collapse context into single pane. 6. Option to have small sidebar at top or bottom. 7. Scale context view backdrops. 8. Add a manual config item (mpdPoll) to control whether Cantata should poll MPD for its current state whilst playing. The value is specified in seconds. This is useful for cases where Cantata misses updates from MPD. This is only a work-around for certain scenarios. Please see README for more details. 9. When sending commands to MPD, and waiting for response, use a timeout of 2 seconds per 100K bytes of data. 10. Hide volume control if MPD returns a negative value for volume - which indicates MPD's volume mixer is disabled. 11. When closing search widget, after performing a search, collapse all bar top level items. 12. When adding files to playqueue, only add in batches of up to 10000 songs. This 'chunk size' may be altered by setting mpdListSize in Cantata's config file - see README for details. 13. Add 'Remove duplicates' functionality to play queue and play lists. 14. Only start internal HTTP server when required, and stop 1 second after last Cantata stream is removed. 15. If connected to MPD via a UNIX-domain socket, then send non-MPD files as file:/// URLs to MPD - i.e. don't use internal HTTP server. To force usage of HTTP server, set alwaysUseHttp to true in Cantata's config file. Refer to README for more details. 16. Add CMake option to disable building of internal HTTP server. 17. If listallinfo fails, then attempt to retrieve music listing via lsinfo calls on each folder. Usage of lsinfo may be forced by setting alwaysUseLsInfo in Cantata's config file - see README for details. 18. Add CMake option to disable streams, dynamic, and online services. Refer to INSTALL file for details. 19. Don't use QKeySequence::Delete to detect delete key event for play queue, instead check for no-modifiers and the Delete key itself. Closing a terminal with Ctrl-D seems to cause Cantata to see QKeySequence::Delete 20. Use &;copy for copyright symbol in text - better than (c) 21. Add config setting (for Qt only builds) to set language. 22. Set cover-widget icon size to at least twice tab-widget large icon size. 23. Remove usage of Gtk-style on-off button for Gtk style, and instead give checkboxes better text and adjust config layout. 24. Better control over 'Subscribe' button in podcast search dialog. 25. When downloading images, don't use extension to determine file format, instead look for JFIF and PNG in first few data bytes. 26. Cache folder listing as well as music listing. This cache is removed if the folder view is disabled. 27. Also search Spotify, iTunes, and Deezer for images in cover dialog. 28. Fix filename scheme help table. 29. Allow to specify filename scheme when copying to library. 30. Before calling ReplyGain after ripping CD, convert filenames to destination suffix. 31. Use Podcasts and SoundCloud icons in cover widget, and song notification, for relevant tracks. 32. Fix displaying of disc and track numbers for online services. 33. Move sidebar settings into settings dialog. Sidebar menu now just contains a 'Configure...' entry which opens settings dialog at relevent page. 34. Add new view style "Basic Tree (No Icons)". This is as per the simple tree, but only images, and not icons, will be shown. 35. Europe and Canada regions in ListenLive are not re-loadable (as they are read from listenlive.xml) 36. Set CMake defaults if not supplied on commandline; set prefix to /usr for Qt only Linux builds, path to `kde4-config --prefix` for KDE4 builds, and set build type to Release 37. Place Cantata's 'toolbar' within a QToolBar - so that the style's toolbar look is preserved, and styles that can drag window via toolbar (e.g. QtCurve) still work. 38. Call QApplication::alert() when showing errors - this causes taskbar entry to be marked. 39. Read lyrics from LYRICS tag for FLAC, Opus, Vorbis, and Speex files. 40. Be consistent with other players, and don't show disc number in list. 41. As per Amarok, split albums into discs in grouped playqueue view. 42. Fix some Mopidy 0.18 issues; cover and lyrics retrieval, folders view loading. 43. Mopidy seems to require all parameters to be quoted. 44. Only react to back or home action in listviews if view is visible! 45. Don't allow buttons in main widow to recieve focus. 46. Use Ctrl+G as short-cut to show genre combo popup. 47. Re-focus view when search closed. 48. Fix handling of playlists with a colon in their name. 49. If Mopidy playlist starts with "Smart Playlist:" then treat as a 'smart' playlist - e.g. don't try to display song count, don't try to load songs in playlists view, use sub text 'Smart Playlist' (and fix playlist name), and don't allow saving to these playlsts. 50. Remove find buttons in status bar, and use Ctrl-F for both find in views and find in playqueue. Action depends upon which view has focus. 51. Remove refresh buttons in status bar, and place into main menu. Also, prompt for confirmation before performing refresh. 52. Combine podcast subscribe and search dialogs. 53. Add option to NOT download covers from last.fm 54. Add menu to cover dialog, so that user can control which services are queried. 55. Add option to use a custom image as context view backdrop. 56. Add option to specify blur and opacity of context view backdrop image. 57. Add option to use a custom image as playqueue background. 58. Add option to specify blur and opacity of playqueue background image. 59. If list view has focus, pressing Backspace will navigate back. 60. Fix albumsview cover size when set to a tree style and Cantata is restarted. 61. Add 'Add To Playlist' menu item to playlists page. 62. Add 'Table' style view for playlists. 63. Clicking on title in list/icon view navigates back. 64. Bundle own copy of symblic media icons - so that these can be recoloured to match current theme. 64. Fix searches containing more than 4 words. 65. Use stretch header class from Clementine for table style views. 66. Don't change tray icon to play state - consistent with other apps. 67. For Linux builds, if systray.svg file exists in $prefix/share/cantata/icons/$iconTheme, then this will be used as the system tray icon. Currently Cantata supplies 2 mono SVGs - one for ubuntu-mono-dark and one for ubuntu-mono-light. 68. Show time-slider position tooltip immediately. 69. Expand interface if it is collpased and an error or question is to be displayed in message widget. 70. Hack-around issue with message widget not appearing in KDE builds when KDE's animations are disabled. 71. Remove 'copy track info' functionality, I see no need for this! 72. Remove showPage dbus function. 73. Use wmctrl to raise window under compiz for Qt5 builds. 74. Don't react to mouse-release event in coverwidget if an override cursor has been set. Fixes the case, with Qt5 builds, where a drag is started on the cover widget causes interface to expand/collapse. 75. Change views to scroll-per-pixel. 76. Don't allow expanding, or collapsing, if maximised or mesasge widget is visible. 77. Remove embedding of pre-rendered Cantata icons. Cantata requires the SVG icon plugin, so users must have this installed. 78. Use system menu icon for Linux builds. To revert to previous 3-line style icon pass -DUSE_SYSTEM_MENU_ICON=OFF to cmake. 79. When downloading, and parsing, ListenLive streams, if a station has more than 1 stream then list all streams - adding format and bitrate 80. Move search fields back to the top of views. 81. Close temporary files after write, to ensure data is written. This fixes an issue where sometimes 0 byte files were written from CoverDialog. 82. Use same order (URL, name) for both add stream-URL dialogs. 83. For UMS devices, add device and size (in brackets) after name. Should help devices with multiple partitions. 84. Use WindowText, and not ButtonText, colour for mono icons - fixes wrong colour being used with Bespin style. 85. Fix corrupted scrollbar background in views with a background image when Gtk style is used. 86. Don't draw tab-bar frame when using small sidebar style. 87. If using small sidebar on top, or bottom, then centre-align tabs. 1.2.2 ----- 1. Fix British English translation. 2. Improve internal HTTP security - only serve files to MPD host, and only files in playqueue. 3. Also remove CDDA files when Cantata exits. 4. Improve error message if 'playlist_directory' does not exist. 5. If icon theme does not have network-server, then use audio file icon for HTTP server settings page. 6. Fix truncated files served from internal HTTP server. 7. When adding songs to an existing play lists, don't allow play list files to be added. 8. If Ctrl-F is activated again whilst search widget is visible, then select all current text. 9. When MPD seeks a Cantata HTTP stream, set the Content-Range header field. This fixes seeking of FLAC files. 10. Fix toggling of checkboxes via short-cut. 11. Fix short-cuts in preferences dialog. 12. Don't clear the playqueue when Cantata is passed a song as a commandline at start-up. 13. Fix broken spinboxes in some dialogs with Gtk style. 14. Correctly style treeview in CD details dialog. 15. Only show artist name of CD track if it does not start with main CD artist. 16. Show track numbers in CD listing. 1.2.1 ----- 1. Only use old Qt DBUS type annotations for Qt less than 4.8.2 2. Use "(c)" instead of "©", as "©" seems to mess up Qt translations. 3. Fix tagtype and URL handler detection. Fixes Composer support, and streams. 4. Use 32x32 for drag'n'drop icon, 64x64 for highdpi. 5. Use audio-x-generic icon for drag'n'drop if no cover found. 6. Fix potential memory leak with cover images. 7. Fix crash when changing online view from list to tree, after a soundcloud search. 8. Use MPD status.playlistLength to determine number of playqueue items when controlling state of prev, next, and stop buttons. 1.2.0 ----- 1. Add support for opus audio format - AudioCD encoding, transcoding, HTTP server, etc. 2. Add support for user-installable stream providers. See README for file format. 3. Add config page to control which stream categories are enabled, to import new categories, and to remove imported categories. 4. Request IceCast stream list in compressed format. 5. Add ShoutCast search. 6. For some stream categories (digitally imported, jazzradio, rockradio, sky, somaFm, and some user categories) add category name to stream name when it is saved in favourites, or added to playqueue. 7. Support composer tag - group albums via composer, edit tag, rename files using tag, and use field for dynamic playlists. 8. Place HTTP stream URL into connections page, as URL could be different per connection. 9. Use QListWidget in Qt preferences dialog, so that keyboard navigation can be used. 10. Add ability to 'filter search' stream categories. 11. Add option to prevent system from suspending whilst playing (Linux only). 12. If playqueue is cleared when dynamizer is running, then also stop dynamizer. 13. Create a new SizeGrip class, so that we can force the size-grip on the main window to have the same height as toolbuttons - this makes it align to the bottom. 14. Place a 0-pixel wide 'size widget' at the bottom of each view, to attempt to ensure consistent sizes between views. 15. Make playqueue search consistent with view search - ie. only show search on activating 'Search Play Queue', do not toggle. 16. Initial MacOS port - thanks to Ian Scott 17. Podcast support. 18. Add config page to determine list of online providers. 19. If 'Basic'/'Personal' is selected in initial settings wizard, then enable 'stop playback' and 'stop dynamizer' options. 20. Store data under XDG_DATA_HOME (~/.local/share) Any existing files will be copied to the new location. NOTE: This is not backwards compatible, so if you downgrade Cantata, you will have issues (e.g. missing favourite streams) 21. Add two items to config file to modify cover-loading responsiveness. Please see README for further details. 22. Use "play" command if play button is pressed and there is no current song. 23. Use HTTPS URLs for Last.fm, Wikipedia, SoundCloud, etc, for non-windows builds. 24. Only show genre combo if we have some genres. 25. When parsing CUE files - attempt to load as UTF-8, and then "System" encoding. Support a new config item, 'cueFileCodecs', which allows extra Qt text-codecs to be tried (see README for details). If all of these fail, then revert back to the previous behaviour. 26. Don't build GNOME media-keys support into KDE builds, as these use KDE's global keyboard shortcuts. 27. With KDE builds, default to using media-keys as global shortcuts. 28. Optionally support Qxt global shortcuts for Qt-only (Qt4) Linux and Windows builds. 29. Move media-keys setting into shortcuts page. 30. Add a config item to disable all network access. Needs to be set manually, see README for details. 31. Add a config item to configure album-view to load all covers immediately, rather than waiting for then to be displayed. Again, no UI for this, and see README for details. 32. Slimmer toolbar - song times are now at the ends of the time slider. 33. Custom time slider and volume control. 34. Optionally, prompt before clearing playqueue. 35. Only show config pages, and tabs, that are relevant to the enabled views. 36. Show action short-cuts in tooltips. 37. When filtering (i.e. searching) Jamendo, Maganatue, and stream providers (apart from TuneIn and ShoutCast), then don't hide items from other services - but only filter the ones on the filtering service. 38. Also read Jamendo genre's from album tag. 39. Modify message-widget so that it uses a squeezed-text label. Because of this, always use Cantata's copy of KMessageWidget for all KDE builds. 40. Cantata requires libMTP 1.1.0 or newer. 41. When displaying paths, convert to native separators, and remove trailing separator. When reading back entry, revert this process. 42. Fix tag editing, track re-organisation, and playback of non-MPD files via HTTP server on windows. 43. Don't treat albums that have artists such as 'Abc' and 'Abc with xyz' as multiple artist albums. 44. When setting window title, use full-text from track/artist labels, and not the squeezed text! 45. Hungarian translation - thanks to Török Árpád 46. When displaying tag editor, track organiser, or replay gain dialogs, check that the song files can be accessed. (For speed reasons, only the 1st few files are checked) 47. Add a config item to control volume step - no config UI, see README for details. 48. Provide a CMake option to control whether KWallet is used to store MPD passwords for KDE builds. 49. Display total cache usage in Cache settings. 50. Fix updating of current collecion in Cantata's menu when current collection is set in settings dialog. 51. Only load ListenLive categories when requested. 52. In artists and albums views, add 'Add Albums In Random Order' entry to context menu. When selected, all highlighted albums (or albums by highlighted artists) will be added to playqueue in a random order. 53. Add 'Shuffle Albums' entry to playqueue context menu. 54. When showing a new page, place focus on view. 55. If MPD's database time is invalid, and the cache's database time is invalid, then accept a database listing. This works-around an issue with using MPD's proxy DB in versions prior to 0.18.5 - where no database time is sent. In this case, Cantata will store the current date and time in its cache file. Users with a proxy DB (and using MPD version before 0;18.5) will need to force Cantata to update to notice changes. 56. Look for backdrop.jpg/png in music folder before attempting to download. 57. Add option to store downloaded backdrops into music folder. Stored as backdrop.jpg 58. Fix crash when an album link is clicked on in context-view when current song is from a Various Artists album and the album link is to another album by the artist. 59. Show device copy/delete status in Unity launcher - show current progress, and track count to be actioned. 60. If playlists page is disabled, then hide 'Add to playlist' actions and hide playqueue save button. 61. Don't load folder list until view is visible. 62. Add 'Cancel' to context-view context menus - cancels current fetch job. 63. Correctly update tooltips when removing a short-cut from an action. 64. Instantiate network proxy factory when network access manager is constructed. 65. Using mouse wheel over position slider changes position by 5 seconds. 66. Fix fetching lyrics for songs, or artists, containing ampersand or question mark characters. 67. Use correct output argument for oggenc and faac. 68. Only load info of stored playlists when required. 69. If looking for lyrics on lyrics.wikia.com, then use its search API to locate lyrics page. 70. Radio GFM streams. 71. Fix lyrics scraping code for most providers. 72. Add leoslyrics.com to list of lyrics providers. 73. Align main popup menu to side of window. 74. Use AlbumArt.jpg as default cover name for MTP devices. Android's gallery application will ignore album covers if they are named this way. 75. Don't allow configuring of filename for MTP devices. MTP device support uses folder structure to determine 'Various Artists' album-artist (as LibMTP does not support album artist tag). 76. If song has a disc number set, then display song track as "disc.track title" - e.g. "2.01 Blah blah" 77. Better handling of Cantata stream URLs with special characters (e.g. question marks) 78. Remove Cantata streams when exiting. 79. Update album's year if song year is changed in tag editor. 80. If we can access a CUE file, but fail to parse it, then do not add CUE to album's track list. 81. Show MTP track list progress in percentages. 82. Fix memleak with MTP devices. 83. Open MTP devices in un-cached mode (faster) 84. Add clear button to Qt input dialogs. 1.1.3 ----- 1. (Hopefully) fix selection order of items - and order added to playqueue. 2. Updated translations: German, Spanish. 3. Add Russian translation, thanks to Julia Dronova. 4. Online services support does not require taglib. 5. Fix changing of Music folder for 'Personal' mpd collection. 6. Use QDesktopServices/QStandardPaths to ascertain default music folder for 'Personal' collections. 7. If no icon is to be used on a message-box button, then ensure the icon is cleared. 8. Fix display of album year in playqueue for KDE builds. 9. If we recieve an error from MPD via status, then display this in the main window. 10. Don't react twice to every volume change. Volume was changing in 10% increments, whereas it should have been (and is now) 5% 11. Bind increase volume, decrease volume, and mute actions to main window, so that shortcuts work. 12. Fix number display in replaygain dialog in Qt-only builds. 13. When reading default MPD music folder, ensure this has a trailing slash. 14. Only enable 'Edit Song Tags' on playqueue if MPD music folder is readable. 15. Slightly improve tag-guessing from tracks without tags. Now the following are acceptable: Artist/Album/TrackTitle.mp3 Artist/Album/TrackNo TrackTitle.mp3 Artist/Album/TrackNo-TrackTitle.mp3 Artist/Album/TrackNo. TrackTitle.mp3 Artist - Album/TrackTitle.mp3 Artist - Album/TrackNo TrackTitle.mp3 ...etc. 16. Fix artist and album genres. 17. Show albums by artist, etc, even if wiki/last.fm search fails. 1.1.2 ----- 1. Fix build due to broken translation files. 2. After toggling various artists grouping, or group single tracks, update list of genres associated with artists. 3. Fix decoding of details for Online service URLs sent to MPD, and subsequently read back. 4. Don't allow dragging of stream categories onto playqueue. 5. Replace # in stream names with ${hash} when passing to MPD, and revert when URL is displayed. 6. Fix covers when using album artist for multiple artist albums, and album artist name is "Various Artists" - but this has been translated in Cantata. 7. Don't draw item divider in icon views. 8. For Qt4 less than 4.8.4, then use old dbus Qt type annotations. 9. Default to exporting favourites streams XML into home folder. 10. Reset the covers-requested-per-iteration counter after each event loop iteration. This should fix the case where sometimes the system tray notifications would not have a cover image. 1.1.1 ----- 1. Fix crash in settings dialog if current connection name is removed from config file. 2. Fix starting of per-user MPD instance. 3. For Qt-only Linux builds, set file permissions of config file so that others cannot read the file - as MPD, Magnatune, or DigitallyImported passwords will be saved here. 4. Fix reading of genres from cache file. 5. Fix updating of genres after DB is refreshed. 6. Don't use https for context view searches. 7. Work-around Windows and QJson issue. 8. Remove cantata prefix when showing status message about fetching streams. 9. When adding streams that have a name assigned, but no path, then add a trailing slash. 10. Fix 'Add Stream URL' action in builds where device support is disabled. 1.1.0 ----- 1. Display 'Calculating...', and 'Deleting...' in cache settings page if relevant. 2. Add "(Muted)" to volume button tooltip if volume is currently muted. 3. If cover name contains %artist%, then replace with the album artist of the current song. Likewise for %album% 4. Add option (to sidebar context menu) to toggle usage of monochrome icons. 5. After ripping a CD, prompt as to whether to calculate ReplyGain. 6. Simplify HTTP server settings. Now only the interface can be chosen. HTTP server is used for all non-MPD files. If computer has no, or only one, active network connection, then the HTTP server settings page is hidden. 7. Only show actions on mouse-over. 8. Use larger action icons in icon view when we have larger previews. 9. Better non-monochrome radio-stream icons. Thanks to Grely 10. Don't use alternating rows in views (does not look too great with grouped view). Use a fading divider instead. 11. Combine info and lyrics pages into a new context view. By default this is not placed in the sidebar, but has a toggle to show in the main view. 12. Remove EchoNest usage, as this only seems to return English results. Use wikipedia instead. Only the introduction is displayed by default. If no entry is found on wikipedia, then last.fm is consulted. 13. Don't display stream URL as sub-text, it makes the view look messy for no real gain. (URL is still shown in tooltip) 14. Detailed tree view for folders and playlists by default. 15. Remove config compatibility with Cantata versions older than 0.7 16. Only have 2 stop actions; stop now, or stop after current. Default stop action is always 'stop now' 17. Make 'stop after current track' assignable to KDE global shortcut. 18. Add 'stop after current' to tray item menu, and to Cantata MPRIS interface. 19. Try to use better text for buttons in dialogs, and not just yes/no. 20. Don't use icons in buttons when using QGtkStyle - Gtk does not use button icons. 21. Use checkboxes in sync dialog to mark songs to be copied. 22. Show number of selected artists, albums, and songs in sync dialog. 23. Save sync dialog size. 24. Show number of songs to be copied on action dialog. Make number a link, when clicked produce a dialog showing list of songs. 25. Improve radiance CSS theme. 26. Use 24px playback icons if the icon theme has these. 27. When AudioCD is ejected, remove tracks from playqueue. 28. If Cantata is passed cdda:// then it will load, and start to play, the current AudioCD. Use cdda://?dev=$device (e.g. cdda://?dev=/dev/sr0) to specify which drive to use. 29. Provide KDE4 Solid actions file to play AudioCDs. (Only installed for KDE4 builds) 30. Update ultimate_providers.xml to match Clementine 1.1.1 31. Prompt before removing dynamic rules - to be consistent with streams and playlists. 32. Add option to replaygain dialog to show only untagged tracks. 33. Automatic accelator assignment for Qt builds. 34. Add cmake check to see if TagLib has id3version in MPEG save. 35. RTL fixes. 36. For Qt4 Linux builds, use system QJson if found. 37. Remove amazon cover fetching - required API key that Cantata never really had. 38. Add debug logging. Please see README for details. 39. Enable MPD HTTP stream playback using QtMultiMedia for Qt5 builds. Thanks to Marcel Bosling for the patch. Disabled by default, to enable pass -DENABLE_HTTP_STREAM_PLAYBACK=ON to cmake. 40. Fix Qt5 segfault on exit, due to static QIcons being destructed. 41. Work-around Qt5 bug where toolbuttons (usually with menus) stay in the raised state. 42. Add port number to library cache filename, to cater for scenarios where there is more than 1 server on the same host. 43. Fix retrieval of covers in albums view for multiple-artist albums when these are configured to be grouped under "Various Artists" 44. Refresh albums view when multiple-artist grouping is changed. 45. Add context menu to replaygain and file organizer dialogs to remove items from list. 46. Also use discogs for artist images in cover dialog. 47. Fix invalid covers showing for online services. 48. For Qt builds, if shortcut is set to default then remove entry from config file. 49. Don't show page shortcuts in tooltips, as tooltip is not updated when shortcut is changed. 50. Check that perl is installed before attempting to start cantata-dynamic in local mode. 51. If cantata-dynamic is started in server mode, then have it create any missing folders. 52. Simpler proxy settings. 53. Delay loading of local devices at start-up, so that we have time to add device to view before try to expand it. 54. If cantata-dynamic is started in server mode, then communicate status via UDP multicast messages. 55. If using server mode cantata-dynamic and this is not started, then show an error message in dynamic page. 56. Fix keyboard shortcuts of tab pages. 57. Add support for a simple profile where MPD is started by cantata, and the only settings are the music folder and cover name. 58. Combine Output and Playback config pages. 59. Remove proxy config from settings, and always use system proxy. To re-enable proxy settings pass -DENABLE_PROXY_CONFIG=ON to cmake. 60. Copy Qt5 Linux system proxy code for Qt4 builds. 61. Add option to draw current album cover as backdrop to play queue. 62. Add 'Copy Songs To Device' action to playlists page. 63. Embed pre-rendered PNG versions of cantata icon, to help with Qt5 builds on systems that do not have the Qt SVG icon engine installed. 64. Simplify streams page. Remove user-categories, instead have a set of predefined top-level items; Favourites (user streams), TuneIn, IceCast, ShoutCast, SomaFM, Digitally Imported, Jazz Radio, Rock Radio, Sky.fm, and Listen Live. 65. Search for streams via TuneIn. 66. Place search fields on bottom of views, and hide by default. Show when Ctrl-F is used for views, and Ctrl-Shift-F for playqueue. 67. When searching in dynamic page, also search rules themselves. 68. For MPD versions 0.17 and above, if Cantata can read a .cue file then it will list each track as a separate entry in the artists and albums views. 69. When loading a stream into the playqueue, show a status message at the bottom - allowing the loading to be cancelled. 70. If stream is not an AudioCD stream, and the total time is known, then enable the position slider. 71. Allow seeking in cantata HTTP streams. 72. Default to enabling use of media keys under GNOME/Unity. 73. Add SoundCloud to online services. 74. Drop usage of lame when playing back AudioCDs, its not required. 75. Always use QNetworkAccessManager - as KIO is not thread safe :-( 76. Update copy of Solid to KDE4.10.5. 77. Provide Faience CSS theme. 78. Click on time label to toggle between showing current time and time remaining. 79. Use tooltip to display position that will be jumped to if mouse is pressed on time slider. 80. Place replaygain analysis within a separate app. Saves Cantata itself needing to link to ffmpeg/mpg123 1.0.3 ----- 1. Don't display codec settings in copy dialog unless we are copying to a device, or from an AudioCD. 2. Save size of Track Organiser, Tag Editor, and ReplayGain dialogs. 3. Fix consistency of default button in messageboxes between Qt and KDE builds. 4. Unless using a dark toolbar, use same colour for toolbar menu icon as per menu icon in views. 5. Document how to make a release build. 6. When searching for artist information, if song's album artist contains artist, then use album artist. This is for tracks that have an artist such as "abc featuring 123", and album artist is "abc" 7. Improve consume icon at larger sizes. 8. Fix lyrics background when we toggle from showing cover to not showing. 9. Replace multiple blank lines with single blank line when showing lyrics. 10. Fix AudioCD playback. 11. Remove time remaining from action dialog - its not accurate enough to be meaningful. 12. Fix detection of whether streams file is writeable or not. 1.0.2.1 ------- 1. Re-spin to add Qt SVG iconengine to windows installer. 1.0.2 ----- 1. Show artist name as caption for cover dialog when setting artist images. 2. Connect 'Set Cover' signal for windows builds. 3. Fix usage of timers in threads. 4. Fix non-taglib build. 5. Fix loading of SVGs in windows build. 6. Fix cover downloading. 7. Fix deletion of threads. 8. Enable online services for windows builds. 9. Fix 'No such song' warning when removing songs from playqueue. 10. Indicate when streams file is read-only. 11. Better status labels for streams and dynamic pages. 1.0.1 ----- 1. Don't display Jamendo and Magnatune cache settings in config dialog if these have been disabled at build time. 2. Correctly calculate size of cache folders when folder does not exist. 3. Fix deletion of cache when single item selected. 1.0.0 ----- 1. Give tooltips to view actions. 2. Add 'Set Album Artist from Artist' action to tag editor. 3. Add option to specify max cover size when transfering to device. 4. Remove 'Small Control Buttons' and 'Small Playback Buttons' options. 5. Prompt before disconnecting a device. 6. For playqueue items, if we only have a filename, and no artist, etc, then attempt to ascertain artist, album, title, and track number from the filename. These will be extracted if the filename has the following pattern; "%artist%/%album%/%track% %title%" Otherwise, if we cannot extract all of these, just set the title to the filename without path. 7. Allow editing of tags, and replaygain calculation, of files from folder view which do not contain tags. 8. Merge 'External Settings' into 'Interface Settings' 9. Add option to embed covers when copying songs to devices. (ID3v2, MP4, and Vorbis comment tag types only) 10. Add a simple system dbus helper to allow user mounts of samba shares. This is intended to allow Cantata to connect to an Android device via Droid NAS. Requires cantata be built with remote device support. 11. Add first-run wizard to enable basic configuration before Cantata is started for the first time. 12. Add a 'Cache' page to settings dialog, allowing cache usage to be displayed and cache items deleted. 13. Move group warning to last stage of intial settings wizard. 14. Basic cover fetching dialog, allowing to change downloaded cover. Currently Last.fm, Google, Discogs, and CoverArtArchive are used to find images. To enable searching via Amazon, you need to obtain an 'access key' and a 'secret access key' Cantata currently does not contain these, but you may supply these in Cantata's config file. To do this, simply add the following to the "[General]" section: amazonAccessKey=xxxx amazonSecretAccessKey=xxxx 15. Use cache by default for devices. 16. GZip compress cache files. 17. Save remote device library settings on remote device. 18. When using list or icon view, only show genres that are relevant to the current level. 19. (Qt-Only) When displaying filesizes, use KiB (1024 bytes) for KDE and Windows, and kB (1000 bytes) for others. 20. Recolour repeat and shuffle icons to use button text colour - this is so that they always match consume and single icons. 21. Re-enable animation when showing messagewidget, for Qt-only builds and for non 4.9.4 KDE builds. 22. Speed up playqueue searches. 23. Add prompt before removing playlists. 24. When dropping files onto playqueue, check that they have a recognized extension (mp3, ogg, flac, wma, m4a, m4b, mp4, m4p, wav, wv, wvp, aiff, aif, aifc, ape, spx, tta, mpc, mpp, mp+, dff, dsf) 25. Add import of IceCast and SomaFM streams. 26. Move server info into a dialog. 27. Add support for Jamendo and Magnatune streams. 28. Add 'Add Stream' action to play queue menu - so that streams can be quickly added without needing to store in streams page. 29. For streams, use filename part of path as track title. 30. Fix setting of buttons in song copy dialog (Qt-only builds, KDE already working). 31. Only stop dynamizer on exit if option enabled, not if just stop playback on exit is enabled. 32. Add a new view style - 'Detailed Tree'. This uses the list-style appearance for treeviews - e.g. The album count appears under each artist, etc. 33. Install cantata-dynamic to $prefix/share/cantata/scripts - as this script is not intended to be run by users. 34. Use com.googlecode.cantata instead of org.kde.cantata for DBUS service, etc. 35. Add action icons to grouped-albums style playlists at the top level. 36. Scale image in lyrics view if it is less than 300x300px 37. Allow to move streams to different categories via drag-n-drop. 38. Read/write device cache files in non-gui thread. 39. Add option to save streams in MPD folder. 40. GZip compress streams files. 41. When reading cache (either MPD, or device), and the grouping (single tracks, or multiple artists) has changed - then don't do a rescan, just re-do the grouping. 42. If we are using QGtkStyle and this is set to Ambiance or Radiance, then apply a corresponding stylesheet to the toolbar - and allow window to be dragged by toolbar. 43. If fetching artist images for a song, and the song-folder heirarchy is 2 levels (artist/album/), then save image as artist.jpg/png within the artist folder. (Only if 'save downloaded covers in music folder' is enabled) 44. Work-around QGtkStyle issues with large entries in comboboxes. 45. Remove 'Auto' cover size setting. Now cover sizes are based upon setting and font size. 46. Initial support for Qt5. 47. When disabling system-tray icon, first set this to not visible, and then delete the item. This helps removing the entry from the Unity menubar. 48. If all streams (and categories) have been removed, then remove streams xml file as well. 49. Allow streams to have multiple genres. 50. Custom menu icon - more like the one used in chrome. 51. Use "-symbolic" icons for Qt-only Linux builds with ambiance theme. 52. If replacing playqueue with a stream, then start playing when added. 53. Fix restoring of alternate size. e.g. fix restore of collapsed size if started expanded - and vice versa. 54. Recreate menubar for KDE4 build when run under unity. (Qt builds already had this) 55. Hack-around size of QComboBox in page classes so that height is same as buttons. (QGtkStyle only) 56. Support reading of lyrics from HTTP server - specify a HTTP url as MPD music path. 57. Hack-around QGtkStyle focus issues if a spin-box is set to have no buttons. Cantata's Gtk3 style spin boxes are mimiced using a spin-box with no buttons, and 2 toolbuttons. QGtkStyle messes up the widget focus if this is used, so Cantata's spin box attempts to work-around this. 58. Improve look of Gtk3 style spinbox. 59. Use xdg-open for "Open In File Manager" action of folder page. 60. Overlay-ish looking scrollbars for ambiance theme. 61. If 'Album Artist' is empty, then store this as empty in the cache files. This is so that when these are read back, the tag-editor has the actual values. 62. If we change a genre, or year, in the tag-editor, then update views. 63. Disable connections menu when preferences dialog is open. 64. Allow wildcard genres. e.g. 'Rock*' for 'Hard Rock' and 'RockNRoll' in dynamic rules. 65. Handle multiple storage locations on MTP devices. 66. Fix creation of music folder on MTP devices. 67. Attempt to work-around lack of AlbumArtist support in libmtp. For each album that has tracks by different artists, set the AlbumArtist to 'Various Artists' (This *only* works if there are no duplicate track numbers) 68. Send cover files to MTP devices. 69. If MTP track number is 0, then attempt to ascertain this from the filename. 70. Respect ID3v2.3 / ID3v2.4 setting of tags - i.e. when save, ensure we use the same version. 71. Optionally start track organizer after tag editor, if changing the tags indicates that the files should be renamed. 72. Add extra option to filescheme dialog - "Track Artist (+Title)". This is intended to be used with Various Artist albums - so that the filename can also contain the track artist, if this is different to the album artist. 73. Expand device when connected. 74. Basic support for extracting music from audio CDs 75. Update copy of Solid to KDE4.10.1 - adds Udisks2 support. 76. Reduce memory usage - by having only Device/OnlineService derive from QObject, as opposed to every artist/album/song! 77. Fix HTTP server listening on non-loopback address. 78. Rename 'Library' tab to 'Artists' 79. Save, and restore, sizes of (most) dialogs. 80. Add stream name to fragment part of URL sent to MPD. This way, when the playqueue listing is received back from MPD, the name can be determined. 81. Improve MPRIS interface - fix CanPlay/CanPause status update, and use micro seconds for times. 82. Use EchoNest to retrieve artist information. 83. Add ability to specify extra options for sshfs. 84. Press F11 to make Cantata fullscreen. 85. If configured to use GNOME MediaKeys, then start dbus service if it is not already started. 86. Implement 'Stop after current track' 87. Add 'Stop after track' to playqueue context menu. 88. If cropping an artist image (because it is not square), always crop from the top. This is because if we have a portrait picture (where the height needs adjusting), then the artist's face is likely to be nearer the top than the middle. 89. Middle click volume button to mute/unmute. 90. Stream/category icons. 91. When dynamic mod is active, and dynamic tab is not shown, show stop dymamic button. 92. Add support for 'Auto' replaygain setting. 93. When opening ffmpeg codec (for replaygain), try to open for 'float' version. 94. If compiled with both ffmpeg and mpg123, then prefer mpg123 for mp3 files if ffmpeg does not have mp3float codec. 95. Remove duplicates from dynamic rules. 96. Add an 'Add' button to dynamic rule dialog, so that multiple rules may be created without closing dialog. 97. Show next track in tooltip of next track button. (Only valid whilst playing) 98. List untagged files in main 'artists'/'albums' views. Attempt to guess the tags based upon the filename/folder. e.g. $artist/$album/$trackNo - $trackTitle 99. Monochrome sidebar icons. Enabled by default for Linux builds, disabled by default for Windows builds. To alter setting edit Cantata's config file and set the following in the "[General]" section to either true or false: monoSidebarIcons=false 100. If HTTP is set to a dynamically allocated port, then attempt to re-open the same port number the next time Cantata is started. 101. Enable HTTP server, and use, by default. 0.9.2 ----- 1. (Qt-Only) Don't show clear button for read-only line-edits. 2. Fix 'Locate In Library' when library is not set to tree mode. 3. Use grid layout for transcoder slider, removes Qt layout warning. 4. Simplified Chinese translation - thanks to 薛景 5. Fix restoring of splitter sizes when we start minimized to system tray. 6. Fix decoding of Cantata HTTP stream URLs. 7. Work-around issue of non display of main window when using transparent QtCurve theme. 8. Fix playing of some non-MP3 files external to MPD database via Cantata's simple HTTP server. 9. Fix playing of files containing square brackets, etc, that are external to MPD database via Cantata's simple HTTP server. 10. Fix detection of some filetypes. 11. Prevent multiple KDE Cantata windows appearing if app is attempted to be started repeatedly in quick succession. 12. For streams, if we have a 'name' but no artist or album title, then just display the name (this is usually the Radio Station) 13. For a song to be 'empty', artist, album, title, and name need to be empty. This fixes detection of updates to the name field when playing streams. 14. Fix potential memory leak when stopping threads. 15. Only enable play queue save-as and clear buttons if there are items in the play queue! 16. Fix detection of streams in play queue when current song is updated. 0.9.1 ----- 1. Fix saving of 'Store covers in MPD dir' setting. 2. Show/hide main window when KDE tray item clicked. 3. Add 'Show Window' to tray item menu. 4. Don't allow to set focus onto clear button in line-edits. 5. Remove animation when showing messagewidget, seems to workaround a crash when compiled with KDE and using Oxygen. 6. Fix activation of devices tab via keyboard shortcut. 0.9.0 ----- 1. Add a 'server' mode to cantata-dynamic. This contains a basic HTTP API to list rules, update rules, and start/stop the dynamic mode. 2. Allow use of 'dynamic' playlists in windows builds, but only for server mode cantata-dynamic. 3. Add support for 'Similar Artists' in dynamic mode. 4. Add a gui setting to control the enforcement of single-click. 5. When sorting tracks, sort on duration after sorting on name, title, and genre. This way if tracks do not have a track number, disc, year, etc, then we will sort on the name/title before duration. 6. When refreshing DB, make sure Albums view is at top level. 7. Set Artist, AlbumArtist, and Album of cue files to that of assigned album. 8. Improve item-view mouse over for Gtk+ style - when hovering, draw selection into a QPixmap and set painter's opacity before drawing image. 9. Workaround issue of sometimes having an item (in icon view) staying in mouse-over state, even though mouse has left view. 10. For Qt translations, provide two strings for plural translation. One for singular (e.g. "1 Track") and one for plural (e.g. "%1 Tracks"). For languages that have more than 1 plural form, it is suggested that BOTH strings are translatated to the format "Items: %1" - e.g. "1 Track" becomes "Tracks: 1", and "%1 Tracks" becomes "Tracks: %1" 11. Device sync support for Qt-only builds. To support this, a cut-down version of Solid is included. 12. Don't enforce oxygen icons for Qt-only (Linux) builds. Check for missing icons, and use alternatives. 13. Use a random icon that matches the repeat icon better. 14. Draw the consume icon in code, so that it matches random and repeat better. 15. Add a ON/OFF style checkbox for Linux builds when NOT run under KDE, and using QGtkStyle. 16. Add a custom spinbox to closer match Gtk3's large button spinbox style. Only for Linux builds NOT run under KDE, and using QGtkStyle. 17. Add 'Show Window' action to Linux builds - so that we have a way of restoring the Cantata window from the Unity menubar. 18. Use freedesktop.org notifications for Qt-only Linux builds. 19. Show menubar when run under Unity (Qt-only build). 20. Improve MPRISv2 interface - signal when proprties change. 21. Play/Pause track when middle click on tray icon for Qt-only builds (KDE builds already have this). Thanks to spartanj for the patch. 22. Remove faded icons from background of sync dialog views (as these did not render correctly with all styles), and enforce alternate row colours - as per other views. 23. Fix image/icon size, and spacing issues, in sync dialog when the library view is set to use icon/list style. 24. Add 'Add To Playlist' action to playqueue context menu. 25. Fix retrieval of covers from file-system based devices. 26. Support for modifiable keyboard shortcuts in Qt-only builds. (Code stolen from Quassel!) 27. Add option to control whether Cantata should minimise to the notification area when closed (default), or terminate. 28. Only save settings when modified. 29. Add option to support GNOME media keys. 30. Add setting to control name (without extension) of downloaded cover files. 31. For KDE builds, if run under Unity set KStatusNotifierItem status to Active - otherwise no item appears. 32. Truncate error messages to 150 characters. Set message (up to 500 characters) as tooltip of message widget. 33. Remove setting of dockmanager item to current cover. This is better handled by an external dockmanager helper script, as per amarok, etc. 34. Remove option to enable/disable MPRIS interface, and always have enabled. 35. Disable Phonon stream playback support by default. Currently not all Phonon backends seem to work reliably, and there can be delays between pressing a button (e.g. start) and the action occurring (due to buffering?). To re-enable pass -DENABLE_PHONON=ON to cmake. 36. Handle UTF-8 playlist names. 37. Sort selected playqueue indexes before adding to stored playlist. 38. Update 'Add To Playlist' menu when rename a playlist. 39. Only need to download/parse streams (to check if they are a playlist) when added from the streams page. (Streams in an MPD playlist will not be playlists themselves, as MPD does not support this.) 40. Allow building of replaygain support with either ffmpeg or mpg123, or both. 41. Display extra information texts in messageboxs and not whats-this popups, as QGtkStyle seems to have issues with the palette in these. 42. Fix dynamic playlists with UTF-8 strings. 43. Remove unreferenced library cache's each time connection details are saved. 44. When KDE version is closed via quit action, ensure main window destructor is called - so that settings are saved. 45. Compile windows build against taglib 1.8 - enables tag editing and track reorganisation. 46. Korean translation - thanks to Min Ho Park. 47. Fix detection of audio codecs for ffmpeg 1.0. 48. Remove libmaia usage. 49. For Linux Qt builds, use dbus to determine single app status. 50. Add connect/disconnect functionality for UMS devices. 51. Fix crash when calling QFileDialog in Qt-only builds when Oxygen or QtCurve themes are used. 52. In devices view, only show covers that come from device. 53. Remove folders, albums, and cover art, when deleting tracks from MTP devices. 54. Copy covers from MTP devices. (Copying to the device is not currently supported.) 55. Show track listing progress when loading MTP devices. 56. For Qt-only Linux builds, check if current icon theme has the "document-save-as" icon. If not, then if the user has ether oxygen or gnome icon theme installed - set the icon theme name to this. 57. For Qt-only builds, allow to configure the icon theme name via Cantata's config file (~/.config/cantata/cantata.conf on Linux). Edit file and add (e.g.) the following in the "[General]" section: iconTheme=oxygen ...there is no GUI for this, as its only a work-around for some window managers. 58. If group single tracks, or multiple artists, settings are changed, then rebuild library and device models from existing set of songs - as opposed to re-reading all songs from MPD/device. 59. If window is minimized to system tray when Cantata is terminated, then restore to this state when restarted. 60. Add search line-edits, and expand/collapse all, to sync dialog. 61. Add a 1 pixel border around large cover in top-left corner. 62. When refresh button is pressed send an update and stats request to MPD. 63. Hard-code black background and grey text for cover widget tooltip. 64. Ignore quit action if we have open dialogs. 65. Fix crash when copying items to devices after a rescan has been performed. 66. Use UPower (Linux/Qt) to detect when being resumed, and if so reconnect to MPD. 67. In sync dialog, when detecting items unique to library/device, revert various artist work-around for each track if it is enabled on the device. 68. Fix memleak when copying items to/from devices. 69. When creating temp files, ensure these are in /tmp! 70. If applying various artist workaround for a remote device, apply the workaround to a local temp file, and send this. 0.8.3.1 ------- 1. Fix Qt-only compile. 0.8.3 ----- 1. Add CMake options to disable 'automagic' dependencies - required for gentoo USE flags (http://www.gentoo.org/proj/en/qa/automagic.xml). Because of this WANT_KDE_SUPPORT option has been renamed to ENABLE_KDE 2. When requesting covers from a HTTP server, use QUrl::toPercentEncoding. 3. Fix multiple download attempts when getting covers from HTTP. 4. With KDE builds (4.7 or later), and Windows Qt builds, reconnect to MPD when system is resumed. (A reconnect attempt is made every half second for a max of 15 seconds) 5. To help with windows build, embed pre-rendered versions of main icon. 6. When restoring window from notification area, also raise and activate the window. 7. Fix issue with certain styles not drawing selection background in icon view. 8. If we show an error, or info, message whilst using compact interface - then adjust size to take message widget into account. 9. If we lose MPD connection, then show error widget. 10. Reset status when connection lost. 11. Don't attempt to send commands if not connected. 12. In Qt-builds, if we fail to update files (tag editor, replaygain) then show the list of failures in the 'detailed text' section. 13. In Qt (Linux) build, also register org.kde.cantata service - so that messages from dynamic helper can be received. 14. Update version of KMessageWidget 15. Update Polish translation. 16. Use 'avcodec' if 'ffmpeg' is not found. 17. Show current song details in tooltip of notification icon for Qt builds as per KDE builds. 18. If 'showPage' is called (via DBUS interface), ensure interface is expanded. 19. Save MPD filename scheme settings with MPD server settings. 20. Use QWIDGETSIZE_MAX and not INT_MAX to set maximum height of expanded interface. 21. More consistent control of prev/next buttons. 22. Allow 'showPage' dbus command to also show playqueue (if this has been placed in the sidebar) 23. Fix handling of filename's with quotes. 24. Fix track order when adding newly added album, via folders page, to playqueue. 25. Don't split albums based upon year - this messes up compilation albums, where each track may have a different year. 26. To be consistent, use the year of the lowest track number to be an album's year. 27. Use "users" group and not "audio" when setting the group ID of covers, lyrics, and audio files. 28. If we have no song tag details, show filename in playqueue. 29. In KDE builds, check if MPD is readable each time we get a device added or removed signal from Solid if using KDE4.9. If we are using an older KDE, or using Qt-only (on Linux) then watch for changes to /proc/mounts 30. If the date string received from MPD is longer than 4 characters, just use the first 4 - as we are only interested in the year. 31. If not changing artist/albumartist/album of a track in tag editor, then just update track if possible - as opposed to removing and adding to list (which causes a complete refresh of list) 32. When displaying cover tooltip, if image is too big or image file is not found (as is the case for embedded covers), then save the image into a base64 array as a PNG - and have Qt use this in the 'img' tag. 33. Package Qt's jpeg image plugin with windows zip file - otherwise jpeg cover images cannot be loaded! 34. Package missing edit-clear-locationbar-rtl.png icons so that clear button appears in windows line-edits. 35. Use correct palette (Active/Inactive) when drawing item text. 36. Force single-click activation in views. To enable double-click mode (which depends upon the style), edit cantatarc (KDE) or cantata.conf (Qt only) and set 'forceSingleClick=false' in the '[General]' section. 37. When looking for album covers, also check for "${file}.jpg/png", "${albumArtist} - ${album}.jpg/png" and "${album}.jpg/png" within current songs folder. (These are checked AFTER cover.jpg/png, etc.) 38. Improve FancyTabWidget appearance for Gtk+ style - when hovering, draw selection into a QPixmap and set painter's opacity before drawing image. 39. Elide to right (or left for RTL) strings in sidebar. 40. Implement support for translations in Qt-only builds. These use the KDE translations. Plural forms not supported correctly, hence instead of "1 Track" / "2 Tracks" Qt will have "Tracks: 1" / "Tracks: 2" The existing translations will need to be updated to handle the Qt specific cases. 41. Don't clear genre list when clearing music library model. 42. If we are grouping multiple-artists albums under 'Various Artists', then we also need to place 'Various Artists' albums there as well. This oddity can occur when i18n('Various Artists') != 'Various Artists' 43. Add 'Back' action to context menu of list/icon views. 0.8.2 ----- 1. Fix track order when adding newly added album to playqueue. 2. When dragging one artist, or album, in treeview to play queue, show cover image (if possible). 3. Fix crash when toggling playqueue in sidebar at application start-up. 0.8.1 ----- 1. Detect when this is the first time a user has run Cantata. If so, don't place the play queue in the sidebar. 2. Don't attempt to rename a playlist to its current name - otherwise it is deleted! 3. When applying updates in tag editor, track organizer, or replay gain dialog show a progress bar to indicate activity. 4. Display version number in Qt-only about dialog. 5. When expanding interface, set max size to INT_MAX. Otherwise, on windows at least, the maximise button can get disabled. 6. If 'Music folder' in settings dialog is a HTTP folder (path starts with http://), then attempt to download cover-art from the HTTP server. 7. Don't update MPD volume just after we receive a status update - as MPD already has that setting! Fixes an issue with pulseaudio setups - where MPD disconnects from pulse audio, tells cantata that volume is -1, and cantata attempts to set volume to 0. 8. When fading on stop, reset original volume immediately before sending stop. 9. Fix saving of library to XML cache when multiple artist albums are grouped under 'Various Artists' (only save artist attribute once!!) 10. Use command list when adding songs to playlist. 0.8.0 ----- 1. Add ability to play MPD HTTP output stream via phonon. Thanks to Marcel Bosling for the idea and initial patch. 2. Implement Ctrl+MouseWheel zoom for Qt-only info view. 3. Initial attempt at a Windows port. (This is my 1st EVER windows build, so don't expect much!). See README. 4. Add Track Organizer dialog to Qt-only build. 5. Disable 'Edit Tags' and 'Organize Files' actions if MPD dir is not readable. 6. No need to check if playqueue song exists in MPD dir when performing a 'locate in library'. Its possible that the configured MPD dir does not exist - but 'locate in library' should still work. 7. Implement a basic spinner widget for item views in Qt-only builds. 8. Make Qt-only builds single instance apps, as per KDE build. 9. Add commandline file loading support to Qt-only builds. 10. Remove ThreadWeaver usage, and add ReplayGain calculation support to Qt-only builds. 11. Fix size of playback buttons in qt-only build. Force icons to have a 28x28 setting. 12. Add support for listing playlist files in folder view. Thanks to Aleksandr Beliaev for the patch. 13. When adding items to the playqueue, sort the selected items based upon their QModelIndex. 14. Add support for multiple MPD servers. Only 1 is ever active. If there are 2 or more servers defined, the settings menu will contain a sub menu to allow quick access to each server. 15. If an MPD connection has more than 1 output, show an 'Outputs' sub menu in the settings menu. 16. Disable volume control if MPD returns a volume of -1 - as it does when the mixer is disabled. 17. Read images from mp3 (ID3v2), mp4, flac, and ogg files. 18. Read lyrics from mp3 (ID3v2) files. 19. Fix genre filtering in albums view - filter was not updated when genre was changed. 20. Fix background painting in lyrics view for non-oxygen styles. 21. Enable mouse-tracking for all list/tree views. 22. Allow Icon/List for library view. 23. Add option to show artist images in library view. 24. Don't allow selection to overlap tab-widget border-lines in preferences dialog (Qt-only build). 25. Show playlists in library and album views (requires MPD 0.17) 26. Update ultimate_providers.xml to match Clementine 1.0.1 27. When clicking on a label associated with a combobox, show the combo popup. 28. Fix spacing issues of compact interface with some styles. If the style (such as the Qt Gtk style) returns -1 for its spacing, assume a spacing of 4 pixels instead. 29. Workaround tab-widget issues with Gtk style. Only draw highlight for selected item, and item currently under mouse - no fade in/out. 30. Fix/workaround issues with fetching lyrics from letras.mus.br 31. Add option to specify HTTP server listen address/interface. 32. Support MPD queue functionality. Add an 'Add With Priority' to menu of library, etc, views. Add a 'Set Priority' to play queue. Requires MPD 0.17.0 or newer. 33. Only ignore closeEvent if this is a 'spontaneous' event and we have a tray icon. 34. Drastically reduce replay-gain calculation memory usage. 35. Make more columns italic in replaygain dialog to mark entries that will be modified. 36. Make TagLib optional. 37. Adjust size of covers in grouped view, icons in views, and main cover preview based upon font size. 38. Add an extra cover-size setting (Automatic) which will adjust the covers in library and albums view based upon font size. 39. Add option to place playqueue in sidebar. 40. Add keyboard short-cut for 'back' icon in listviews. 41. Fix issue (in Qt-builds) where message widget sometimes (mainly at start-up) is not not as wide as the main window. 42. Provide sub-text for all items in folderview, fixes corruption of list mode. 43. Add option to have sidebar on the sides, top, or bottom. 44. Only show number of tracks, and duration, in playqueue status - consistent with other players. 45. Fix compile with avcodec v54 46. If KWallet is disabled, store password in config file. 47. When fetching lyrics or info for a song which has had the 'various artists' workaround applied - revert this before requesting data. 48. When reading mpd.conf file, if bind_to_address is set to "any", then use default of "localhost". 49. Fix noticing of tag changes when MPD database is updated. When comparing songs need to check all fields - not just filename! 50. Fix usage of tekstowo.pl and vagalume.com.br in lyrics settings. 51. Stop position timer whilst dragging slider. 0.7.1 ----- 1. When saving track name to cache, save the actual name, not the displayed name. 2. Attempt to fix errors in cache where the displayed name was saved. 3. Fix tag-editor's 'Various Artists' workaround when all tracks is selected. 4. Set minimum font size for info page. 5. Only automatically start playing songs if we are replacing the playqueue, or the song is the first song added via the commandline. 6. Fix transcoding with new ffmpeg - thanks to Martin Blumenstingl for the patch. 7. Have cantata-dynamic helper script send a dbus message when it starts and stops - so that cantata main window can show the current status if the helper is started externally. 8. If 'Return'/'Enter' is pressed when play queue has focus, start playing from the first selected song. 9. Fix Qt-only install failure (cannot find RUNTIME). 0.7.0 ----- 1. Add dynamic playlist support. 2. Fix detection of non standard filename covers. 3. Clear any existing search when 'Locate In Library' is activated. 4. In tag-editor, only set placeholder text to '(Various)' for fields where there are actually some values set. 5. Add now playing info to MPRIS interface - thanks to Martin Blumenstingl for the patch. 6. Better, less hacky, workaround for QAbstractItemView shift-select bug (https://bugreports.qt-project.org/browse/QTBUG-18009). Thanks to Spitfire from qtcentre.org forums for fix. 7. Add a 'search' action (Ctrl-F) that will move focus to current tab's search field - or to play queue search field (if play queue has focus). 8. Add 'Raise' support to MPRIS interface. 9. Added a cantata-specific dbus interface with 'showPage(page, focusSearch)' method. So, to raise cantata, show library page, and focus on its search field: qdbus org.kde.cantata /org/mpris/MediaPlayer2 Raise qdbus org.kde.cantata /cantata showPage library true 'page' can be either; library, albums, folders, playlists, dynamic, streams, lyrics, info, serverinfo, or devices. The page will only be shown if it is currently enabled. 10. Show song information in playqueue tooltips. 11. Default to 'Alt+' for page shortcuts. 12. Improve MPD connection reliability. When one socket (command or idle) is disconnected, only reconnect that one. If a reconnect fails, then disconnect both. If we receive an empty reply to a command and socket has been closed - then attempt to reconnect and resend command. 13. Request list of URL handlers immediately after connecting. 14. Check if user is a member of 'audio' group at start-up, and warn if they are not. If a user is a member of 'audio', then cantata will save covers, lyrics, imported songs, etc, as writeable by 'audio' 15. Attempt to read defaults from /etc/mpd.conf (This is usually readable by 'audio' group) 16. If connection fails due to password, then state this in error message. 17. Add extra-large cover sizes (64pixels library, 160pixels albums). 18. Show full size cover image in tooltip for current track cover preview widget. 19. Update copy of ebur128 20. Fix fetching of covers for grouped playlists. 21. For sshfs devices; check ksshaskpass is installed, and check that cantata was not launched from the commandline (otherwise ksshaskpass is not invoked!) 22. Add option for UMS devices to autoscan, or not, when detected - default is set to not autoscan. 23. Fix detection of some UMS devices that are already mounted at startup. 24. Load library view covers in separate thread, as per albums view. 25. Add expandAll (Ctrl +) / collapseAll (Ctrl -) actions to expand/collapse all items of currently focused treeview. 26. Emit dataChanged() for all album header items that match the album currently being expanded/collapsed in the groupedview. 27. Add a basic 'sync' dialog. Shows two lists; left has songs that are only in library, right has songs that are only on device. Each has a 'Copy to' action. When songs are copied, the lists are updated to show the remaining differences. 28. Fix albums view position when going back from album contents. 29. Add option to draw (15% opacity) tiled cover as background to lyrics page. 30. When clicking on a label, pass focus to buddy widget. If this buddy is a checkbox, then also toggle its state. 31. When grouping songs into albums, take into account song year. Its possible for an artist to release two albums with the same name in different years! 32. Remove 'Single Tracks' cover creation. This is not used in grouped view, etc. 33. Limit downloaded covers to a max of 600x600 pixels. 34. Don't update 'current song' details when playqueue is updated - the current song is updated by other means. As we now use 'plchangesposid' to speed updates, we don't always have the full song info when playqueue is updated. This fixes a bug where the cover widget sometimes gets reset to empty. 35. When updating a non-MTP device, prompt the user as to whether to perform a partial scan (only new songs are scanned), or a full scan (where all songs are rescanned). 36. When creating/editing a SSHFS remote device, allow user to use KDirRequester to select music folder. (KDirRequester is passed an sftp:// url to obtain folder listing). 37. When refreshing lyrics, instead of starting at first provider and iterating them all again (which will result in the same hit again), start at the next provider. This allows all providers to be checked. Thanks to Martin Blumenstingl for the patch. 38. Fix copying track from MTP devices. 39. When performing a custom lyrics search, and we have no current song, don't attempt to save the file to MPDs music folder (there is no valid filename). 40. When adding items to playqueue, only start to play if playqueue was previously empty. 41. When determining indexes to add to 'selectedIndexes' for a collapsed album (in grouped style play queue/lists), ensure each index is only added once. 42. Fix loading of songs via commandline if we are set to use HTTP server. 0.6.1 ----- 1. Fix grouped playqueue when we have repeated instances of an album. Now when an album is expanded/collapsed - all instances are expanded/collapsed. 0.6.0 ----- 1. Grouped style for playlists. 2. Fix reading of lyrics files with special characters - thanks to Martin Blumenstingl for the patch. 3. Give tray icon a tooltip - thanks to Martin Blumenstingl for the patch. 4. Apply 'group single track albums' to devices. 5. Add option to group albums with multiple artists under Various Artists. 6. When checking whether a song exists in device, or library, need to also check 'single tracks' and 'multiple artist' groupings - if these have been enabled. 7. Update playlists if modified by another client. 8. When an album is collapsed/expanded in playqueue, call dataChanged() so that title row is redrawn - otherwise, for single-track albums, the view might not get refreshed. 9. Provide option to *not* store downloaded covers in MPD dir. 10. If fading out to stop, and cantata is closed, then send stop command. 11. Add 'Edit Song Tags' action to playqueue context menu. 12. When loading covers, if we fail to find one of the standard names (cover.jpg/png, AlbumArt.jpg/png, folder.jpg/png), then use the first image found. 13. Add a 'Locate In Library' action, to locate the selected play queue track in the library page. 14. Fix crash when quiting Cantata whilst devices are being scanned. 15. Disable JavaScript for webview, this removes the 'Show'/'Hide' buttons from wikipedia pages. 16. Set font family for info page. (KDE only) 17. Adjust min and max sizes for sidebar. 18. Add basic lyrics editing. 19. Fix order of items added from playlist to playqueue, when individual items of the list were selected. 20. Fix logic of replacing play queue - the clear message is sent in the MPD thread just before the add message. 21. Improve albums page responsiveness when loading covers. 22. Better searching - search on all strings, in any order. 23. Automatically expand treeview items when searching. 24. Add custom icons for library and info pages. 25. Use CD icon (without music note) for albums page. 26. Better size calculation of compact view height - should prevent text clipping. 27. Use 'plchangesposid' MPD command to get list of playqueue changes - means less data needs to be read from MPD per playqueue update. 28. Support 'single' mode. 29. When using large sidebar, attempt to calculate correct min size. 30. Add option to sort albumview by artist/year/album. 31. Add dialog where user can specify artist and title to fetch lyrics - useful if automatic fetching fails. 32. Fix crash in albums model when library updated after albums removed. 33. Fix filetype resolver so that we don't attempt to read tags from video files (asf/wmv/mp4v) - these seem to crash TagLib. 34. When using MPRISv2 interface, don't attempt to play a song if the playqueue is empty! 35. Use SH_ItemView_ActivateItemOnSingleClick to detect single click mode in Qt build. 0.5.1 ----- 1. Reduce QMutex usage - have MPDStats/MPDStatus emitted as objects, and stored in relevant classes. 2. When renaming files, rename file.lyrics if it exists. 3. Use QDateTime::toString(Qt::SystemLocaleShortDate) to show server last update in server info page. 4. TagLib is required for build. 5. Enable tag editor for Qt builds. 6. Qt-only build does not install icons, so we need to embed the 16px and 22px version of each into the application. 7. Save lyrics and info pages zoom settings. 8. If compiled for Qt-only, and icon theme is not oxygen, then check to see if current icon theme has required icons (devices/media-optical-audio, etc) If not, then if oxygen is installed - set icon theme to oxygen. 9. Allocate more space for tab if required - up to a max of 50% extra. This fixes "HTTP Server" config page title under Ubuntu with Qt-only build. 10. Install icon for Qt-only build. 0.5.0 ----- 1. Add ability to calculate replaygain tags. (KDE only) 2. Add a VERY basic HTTP server so that local files can be played even when connected to MPD from a non-local socket. 3. Add grouped playqueue style - where album entries are grouped. 4. Add option (in sidebar's context menu) to auto-hide the library, etc, browser pages when the mouse is not over them. Thanks to Piotr Wicijowski for implementing this. 5. Add support for mounting a 'device' via sshfs, and for treating a local folder as a device. Currently NOT in main build, to use pass -DENABLE_REMOTE_DEVICES=1 to cmake 6. Add "Open File Manager" action to folder page context menu. (KDE only) 7. Open local files passed on commandline - if using local socket, or HTTP server. (KDE only) 8. When MPD connection is lost, indicate via message widget. 9. Don't show base dir when editing tags of tracks on a UMS device. 10. In tag editor dialog, if a field in the 'All tracks' section has multiple values - set the placeholder text to '(Various)' 11. Add a cmake option CANTATA_TRANSLATIONS, used to specify which translations should be built and installed. e.g. -DCANTATA_TRANSLATIONS="en;pl" 12. When playing, centre on current track. 13. Use correct value for transcoder parameter. 14. When saving cache file, if a track is filed under 'single artists' then store its actual album name in the cache file. Then when cache is loaded, the correct album name is available to use for the tag editor. 15. Fix crash when deleting streams. 16. Add option to sort albums in albums view by either album-artist or artist-album. 17. Fix build with "-DCMAKE_BUILD_TYPE=debugfull" set - thanks to Martin Blumenstingl. 18. Fix drag'n'drop of tracks from album view into play queue - affected list and icon/list modes. 19. Add a workaround for dockmanager issues - delay registration for .25secs. 20. Change default settings; use tree style for library, playlists, and streams. 21. Better playback button sizes. 22. Better height for collapsed interface. 23. Save/restore height of expanded interface, even when interface is collapsed. 24. Expand interface when showing error messages. 25. Combine playback and output config pages. 26. Disable MPD config items if not connected to MPD. 27. Save expanded and collapsed sizes to config file. 28. Change order of actions in system tray menu to be consistent with main window. 29. Use italic font for stream items in playqueue. 30. Update copy of KMessageWidget 31. Add ability to increment track numbers to tag editor. 32. Esc key navigates back in list views - if view has focus. 33. Better control over prev/next track buttons. 0.4.0 ----- 1. Transcode support when copying to devices. 2. Don't allow editing of device properties when device is busy. 3. Don't allow to change Music folder for UMS devices when properties dialog is shown from copy dialog. 4. Use LibMTP to read device's serial number, and use this as the config key when saving properties to cantata's config file. 5. Add option to cache contents of UMS library to an XML file on the device. 6. Basic tag editing. (KDE only) 7. Fix UMS and MTP song durations. 8. Show file location in tooltips. 9. Only save streams file if it has been modified. 10. Basic support for configurable stream/category icons. (KDE only) 11. Add track organiser dialog - so that files/folders can be renamed to match library/device scheme. (KDE only) 12. Unmaximise window when using compact interface. 13. When using local UNIX domain socket, allow playback of non database files. (This local UNIX only restriction comes from MPD) 14. Use KMessageWidget to show errors - copy of KDE code taken for pre KDE4.7 and Qt only builds. 15. Fix updating of views when genre filter is changed. 16. Improve sidebar style context menu - icon only is a separate checkbox. 17. Improve look of play queue. Display 'play' icon next to current track. 18. Disable playback buttons depending upon size of play queue. 19. When sending track change notification, also include cover (if we have one) 20. Optionally fade-out track when stopping. 21. Add genre filtering to playlists and streams pages. 22. When pressing play after pressing stop, start playing at current song - don't start at beginning of play queue. 23. Better repeat and consume icons. 24. Fix search placeholder text when navigating back in list style devices. 25. Query MPD for list of supported protocols - and only allow supported URLs. 26. Remove 'Copy Track Info' from play queue context menu - functionality is available via Ctrl-C 27. Fix streams export. 28. Save/restore maximised state when collapsing/expanding interface. 29. Add 'copy to device', etc, actions to folders page. 30. Incremental update of folder page items. 31. Use current style for position slider. 32. Fix some toolbutton clipping with some styles. 33. Fix amarok import script. 34. Fix crash when navigating listviews. 35. Manually stop KStartupInfo when app is started. App is a KUniqueApplication and further attempts to restart start a new startup-info, which stays until the timeout. This fix prevents this. 36. Fix toolbutton icon size - helps with cleanlooks. 37. When copying files to library, and user is a member of 'audio' group - then set files/dirs as writable and owned by 'audio' group. 0.3.0 ----- 1. Basic copy to/from device support - USB mass storage (UMS) and MTP support only. MTP is *very* slow. (KDE only) 2. When refreshing library/albums, only affect parts of the model that have changed. (Previously the whole model was replaced). 3. When changing name of playlist, show the original name in the line-edit. 4. Fix adding songs with spaces in their filenames to stored playlists 5. When we receive an updated signal from MPD, refresh library view. 6. When displaying songs of various artists albums, show as 'track number - artist - song' 7. German translation - thanks to Lutz Lüttke 8. Rename 'Update Database' action to 'Refresh', as the current view is now also refreshed. This action will also completely refresh the model. 9. When determining cache filename, replace slashes with underscores. 10. Improve playqueue handling when we have 1000s of entries. 11. Fix sidebar painting for Qt styles (cleanlooks, plastique, etc.) 0.2.1 ----- 1. Updated polish translation. 2. When editing a stream category name, show the current name in the entry field. 3. Use KInputDialog, not QInputDiallog, for KDE build. 4. Fix memory leak in playlistsmodel. 5. Fix crash when removing items from playlist. 6. When checking for existing covers, also check AlbumArt.jpg/png 0.2.0 ----- 1. Add option to show (and sort by) year of albums in library view. 2. Show 'Various Artists' at the top of the library view. 3. Add option to group single track artist/albums under 'Various Artists'/'Single Tracks'/'Artist - Track' 4. Improve time slider accuracy, by checking the time elapsed at each interval. 5. Fix inconsistency between adding items via drag'n'drop and using the 'add to' and 'replace' actions. 6. Add basic MPRISv2 interface - mainly so that Cantata can be controlled via IconTasks' media buttons. 7. Add option to set icon in dockmanager docks (IconTasks, Docky, DockBarX, etc) to that of the current track. 8. Provide a simple script to convert amarok radio streams into cantata streams. 9. Use .cantata extension for import/export of cantata streams. 10. Only export selected stream categories. 11. Show track details in titlebar, so that these also appear in taskbar tooltips. 12. Use 'play' icon for 'replace play queue', and 'add' icon for 'add to play queue' 13. In list/icon view, show actions of non selected/highlighted items at 20% opacity. 14. Disable position slider when playing a stream. 15. Basic elided-text class for Qt-only build. 16. Don’t build libMaia as a library, just compile into cantata. 17. Expand/collapse interface via clicking on album cover. 18. Spanish translation - thanks to Omar Campagne. 0.1.2 ----- 1. Explicitly link each Qt library. 2. Fix playqueue header hiding/restoring. 3. Allow to connect to local UNIX domain socket. 4. Jump to track position by clicking on position slider. Thanks to Piotr Wicijowski for the idea/patch. 5. Fix name of Polish translations file. 6. Better detection of when mouse is in action rect. 7. More opaque action background. 0.1.1 ----- 1. Play/Pause track when middle click on tray icon. (KDE only). Thanks to Piotr Wicijowski for the idea/patch. 2. Only connect to QTreeView::activated when in single-click. Double-click already expands/collapses the item - so if we also do this on activated, then the item is re-collapsed/expanded. 3. Only create the tray icon once! 4. Add context menu to toolbar buttons to enable using smaller buttons. 5. Polish translation - thanks to Piotr Wicijowski 6. To be more consistent with other apps, show expander for root items in tree views. 7. Show spinner over library/folder view when loading. (KDE only) 8. Fix some right-to-left issues. 9. Czech translation - thanks to Pavel Fric 0.1.0 ----- [Changes from QtMPC] 1. Use XDG_CACHE_HOME to store XML and covers 2. Hide listview headers 3. Dir view -> Folders, Library view -> Library 4. Removed setting of font style and size - only bold now set 5. In library list, use album-artist (if available, artist otherwise) to group albums. 6. Remove search combo, and always default to searching in all fields. 7. Removed warning when add to playlist, and nothing selected - was inconsistent with playlist view. (Add to playlist actions should now only be enabled if there is something selected) 8. Fix call to setupGUI so as to not create a statusbar 9. If compiled with KDE support; - use KMessageBox - store config via KConfig - make app a KUniqueApplication, and - use KWallet to store password 10. Show cover on top-left 11. Fix blank item showing in playlist when playlist is empty 12. Hide password in preferences dialog 13. Only 2 socket connections to MPD - one for commands, and one for idle messages. 14. Only load data for a tab when the tab is selected. 15. If saving to a pre-existing playlist, ask the user if it is ok to overwrite. If so, delete the existing one first, then save. 16. Add a combo to filter on genre. 17. Check for cover (cover.png/jpg) within folder of current track to get cover-art, otherwise download. 18. Fetch lyrics 19. Also check mpdFolder+filename.lyrics for lyrics file 20. Use QNetworkAccessManager for HTTP downloads. If compiled with KDE support, use KIO::Integration::AccessManager so that we can gain access to KDE's proxy settings. 21. Use sidebar instead of tabbar. 22. Support replaygain setting. 23. Split config into pages. 24. Add option to stop playback when cantata exits. 25. Fixed thread usage - now sockets are in their own thread. 26. Add album-cover view 27. Support radio streams 28. Query artist/album information via wikipedia - show in a K/QWebView 29. Port to KStatusNotifierItem --------------QtMPC ChangeLog-------------------- RELEASE 0.6.1 ------------- * Gracefully handle connection problems (ticket #35) * Fixed song parsing when track or disc is num/totalnum (ticket #213) * Crop playlist support (ticket #207) * Shuffle support (ticket #209) * Renaming of playlists (ticket #210) * Output support (enable/disable) outputs (ticket #7) * Copy song info to clipboard from playlist selection (ticket #184) RELEASE 0.6.0 ------------- SVN:Thu Oct 6 20:45:11 CEST 2010 * Added idle support * Removed bitrate from window status bar * Removed tooltip (ticket #204) for now since it was not being updated SVN:Wed Oct 5 22:48:21 CEST 2010 * Ask for confirmation when deleting playlists * Remember window size & position * Mutex the library to prevent conflicting updates SVN:Tue Oct 5 22:48:21 CEST 2010 * Fixed loading playlists with spaces, quotes (") and backslashed (\) SVN:Mon Oct 4 21:35:02 CEST 2010 * new icons for library view (credits go to the folks from amarok where i stole them) * sortable playlists view * fixed bug which was introduced by PlaylistProxyModel, playlist headers not showing on startup * delete keyhandler for playlists view * keep a row in playlist selected when removing entries (subsequent delete button presses) SVN:Sat Oct 2 01:12:24 CEST 2010 * removed last.fm error message popups (modal popup for such unimportant errors was a bit over the top) * added "Replace Playlist" button to all views (has been only been in rightclick menu so far) * two modes for loading saved playlists "Add to Playlist" adds to current playlist, "Load" replaces current playlist (doubleclick adds to current) * added right click menu to "Playlists" tab page * added doubleclick handler for DirView * only execute doubleclick handler on Songs/Files, doubleclick on higher levels only expands the treenode * caseinsensitive and locale aware sorting for library and dir view * searchable dirview * enabled autoplay on drag&drop * start playing again when replacing playlist and MPD has been playing before * fixed moving around songs in playlist which are not in sequential order (Ctrl+Click -> Move) (all songs are moved together in sequential order onto the destination position, spaces between them disappear) * preserve playlist selections over refreshs (primary usefull when moving around songs in playlist) * searchable playlist * added icons to library view (different icons for nodetypes and maybe scaled down albumart upcoming) SVN:Tue Sep 28 22:32:12 CEST 2010 * enabled clear button for kde search field in library view * added right click menus * enabled password protected connections * adapted row height for playlist * fixed dropping of files from the library into the playlist, when the droptarget is not a song * removed search button; search box searches onChange anyway * clickable sliders * added date to playlist * fixed delete key for playlist in kde build * implemented clear playlist rightclick item * implemented drag & drop for directory view SVN: fixed: * Dragging and dropping of albums and artists now also works! (ticket #145) * Initial sorting of music library and filesystem view on startup new: * Added support for enabling/disabling consume mode (items from playlist are removed once played) * No more Qt 4.4 checks. Qt 4.4 is stable so move on! * Better KDE integration (ticket #183, patch by aanisimov) * Completely disjunctive searching (patch by M. Grachten) * Playlist support removed: * Last.fm scrobbling support has been removed 0.5.0: fixed: * a file name containing a " character would make it impossible to add it to the play list * fix a segfault when adding a selection containing one or more artists to the playlist * Now only items displayed in the library can be added to the playlist (usefull when searching) * Fixed the restoring of the playlist * Check for duplicates when adding from the DirView * Re-search when chaning album/artist etc new: * separate connection for database commands * variable update interval for stats/status * Dir view (ticket #36) * Disc number support - Visible in playlist - Sort Musiclibrary tracks not only by track number but also by disc number * Better memory usage for musiclibrary and dirview * Use libmaia (xml-rpc) * Use last.fm for album covers instead of amazon * last.fm scrobbling support - Follow last.fm guide lines - scrobbling is own thread so the rest of the program had nothing to do with it. * synchttp for synchornous http support * show album release date 0.4.1: new: * Playlist stats use QSet instead of QList * mpdstat converted to a singleton * mpdstatus converted to a singleton * only load QSettings when needed * GUI tweaks * search direct trough playlist * display playlist stats also in stats window fixed: * some valgrind warnings * reset position of slider to 00:00 * add playlist item in the order they are in the music lib cantata-2.2.0/INSTALL000066400000000000000000000047001316350454000141610ustar00rootroot00000000000000Build & Installation ==================== 1. mkdir build 2. cd build 3. cmake .. 4. make 5. sudo make install Mac/Windows ----------- See README CMake Options ============= The following options may be passed to CMake: -DCMAKE_INSTALL_PREFIX=/usr Specify install location prefix. -DCMAKE_BUILD_TYPE=Release|Debug Specify which type of build. Debug builds will be *much* larger, as they will contain extra debugging information. -DENABLE_HTTP_STREAM_PLAYBACK=ON Enable support for playing back MPD HTTP streams via QtMultiMedia or lib VLC (see below) Default: ON -DENABLE_LIBVLC=ON Enable usage of libVLC for MPD HTTP stream playback. Bug report 493 (https://github.com/CDrummond/cantata/issues/493) contains more information. Default: ON (Linux) OFF (Windows/Mac) -DENABLE_HTTP_SERVER=ON Enable usage of internal HTTP server for non-MPD file playback. Default: ON -DENABLE_PROXY_CONFIG=ON Enable support for proxy settings in config dialog. If disabled, system proxy settings are used. Default: OFF Linux specific: -DENABLE_DEVICES_SUPPORT=ON Support external devices (UMS, MTP, AudioCD) Default: ON -DENABLE_REMOTE_DEVICES=ON Support remote devices (accessed via sshfs, or samba). This is EXPERIMENTAL - and requires ENABLE_DEVICES_SUPPORT to also be enabled. Default: OFF -DENABLE_UDISKS2=ON Build UDisks2 backend for solid-lite. Default: ON -DINSTALL_UBUNTU_ICONS= Install monochrome system tray icons for Ubuntu. -DCANTATA_HELPERS_LIB_DIR= For 64 bit builds, this may be used to control the lib sub-dir where Cantata helper apps will be placed. e.g. setting this to lib64 will cause the helper apps (cantata-tags, cantata-replaygain) to be install into /usr/lib64/cantata instead of /usr/lib/cantata Default: -DENABLE_SIMPLE_MPD_SUPPORT=OFF Enable support for basic, Cantata controlled, MPD instance. Default: ON Windows specific: -DCANTATA_WINDOWS_INSTALLER_DEST= Path where Inno Setpup Compiler should place the catata setup exe. Default: z:\ -DCANTATA_SSL_LIBS=/libeay32.dll;/ssleay32.dll SSL libraries Default: cantata-2.2.0/LICENSE000066400000000000000000001045131316350454000141400ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . cantata-2.2.0/README000066400000000000000000000722041316350454000140140ustar00rootroot00000000000000Table of Contents ================= 1. Introduction 2. Dependencies 3. Building 4. Bugs 5. Translations 6. Covers 7. Advanced Config Items 8. CUE Files 9. Streams 10. MultiMedia Keys 11. DBUS Actions 12. Dynamic Helper Script - Local Mode 13. Dynamic Helper Script - Server Mode 14. Source Code 15. Debug Logging 16. Credits 17. Windows 18. Mac OSX 1. Introduction =============== Cantata is a graphical client for MPD. It contains the following features: 1. Support for Linux, MacOSX, and Windows. 2. Multiple MPD collections. 3. Highly customisable layout. 4. Songs grouped by album in play queue. 5. Context view to show artist, album, and song information of current track. 6. Simple tag editor. 7. File organizer - use tags to organize files and folders. 8. Ability to calculate ReplyGain tags. (Linux only, and if relevant libraries installed) 9. Dynamic playlists. 10. Online services; Jamendo, Magnatune, SoundCloud, and Podcasts. 11. Radio stream support - with the ability to search for streams via TuneIn, ShoutCast, or Dirble. 12. USB-Mass-Storage and MTP device support. (Linux only, and if relevant libraries installed) 13. Audio CD ripping and playback. (Linux only, and if relevant libraries installed) 14. Playback of non-MPD songs - via simple in-built HTTP server if connected to MPD via a standard socket, otherwise filepath is sent to MPD. 15. MPRISv2 DBUS interface. 16. Basic support for touch-style interface (views are made 'flickable') 17. Scrobbling. 18. Ratings support. Cantata started off as a fork of QtMPC, however, the code (and user interface) is now *very* different to that of QtMPC. Unlike most other MPD clients, Cantata caches a copy of the MPD music library. This is so that it can create a proper hierarchy of artists and albums (where Cantata will use the AlbumArtist tag if found, otherwise it will fallback to the Artist tag), to provide album counts, tracks counts, etc in the browser views and tooltips, and also to help with copying songs to/from devices. This cache is updated only when MPD indicates that its version of the collection is different to Cantata's. 2. Dependencies =============== Cantata requires the following Qt libraries: 1. QtCore 2. QtGui 3. QtNetwork 4. QtXml 5. QtSql 6. QtDBus (Only for Linux builds) 7. QtWidgets (Qt5 builds - where widgets are now separated from QtGui) Cantata may also use the following optional libraries: 1. TagLib - Tag edit dialog, replaygain, track organizer, and UMS device support. 2. LibMTP - MTP devices. 3. FFMPEG (libavcodec) - ReplayGain detection code. 4. MPG123 - ReplayGain detection code. 5. CDParanoia - Read/rip Audio CDs. 6. CDDB - either this or MusicBrainz5 required for Audio CDs 7. MusicBrainz5 - either this or CDDB required for Audio CDs 8. LibEbur128 - ReplayGain detection code. If this is not found on the system then Cantata will use its own bundled copy. 3. Building =========== Please refer to the INSTALL file for general build instructions. Details on how Cantata is build for Windows can be found in section 16, and Mac OSX in section 17. 4. Bugs ======= Before reporting bugs, please check that this is not an MPD related issue with your system - e.g. please try recreating the issue with another MPD client, such as MPDroid, GMPC, etc. To report bugs please visit https://github.com/CDrummond/cantata When reporting a bug, please mention the Cantata version number, the Cantata build type (Linux, Windows, Mac), any cmake options you specified (other than the defaults listed at the top of the INSTALL file), and the version of MPD you are using. 5. Translations =============== As of version 2.1.0, Cantata uses Qt5's translation framework. To generate a new translation for Cantata: 1. Copy translations/blank.ts to cantata_LANG_CODE.ts (Replace LANG_CODE with the language code for the desired language, e.g. 'fr' for French - so this would be cantata_fr.ts) 2. Open your new ts file with Qt Linguist. 3. In Qt Linguist set the target language and country. 4. Update the translations. 5. Either email me the new translation (address is in the AUTHORS file), or create a pull request on github. 6. Covers ========= When displaying covers, Cantata will load album covers in the following order: ${configuredName} is either: a. cover - if not configured in Cantata's config dialog b. Setting from cantata with the following replacements: %albumartist% => Album Artist %artist% => Track Artist %album% => Album Name Any other % removed ...if configured to 'Cache scaled covers', Cantata will check its scaled-covers cache folder (~/.cache/cantata/covers-scaled/SIZE) for: 1. ${albumArtist}/${album}.png ...if MPD folder exists, is not specified as a http URL, and is readable, then cantata will look for the following within the folder containing the song: 2. ${configuredName}.jpg - if set 3. ${configuredName}.png - if set 4. cover.jpg 5. cover.png 6. AlbumArt.jpg 7. AlbumArt.png 8. folder.jpg 9. folder.png 10. ${albumArtist} - ${album}.jpg 11. ${albumArtist} - ${album}.png 12. ${album}.jpg 13. ${album}.png 14. Image embedded within current song's tags. 15. ANY other jpg, or png ...then Cantata will check its cache folder (~/.cache/cantata/covers), for : 16. ${albumArtist}/${album}.jpg 17. ${albumArtist}/${album}.png ...if the MPD folder was specified as a http URL 18. ${url}/${dirFromFile}/cover.jpg - or ${url}/${dirFromFile}/${configuredName}.jpg (if set) 19. ${url}/${dirFromFile}/cover.png - or ${url}/${dirFromFile}/${configuredName}.png (if set) ...lastly, if user has enabled downloading via last.fm 20. Query last.fm using ${albumArtist} and ${album}. Cantata will attempt to download the image specified with the "extralarge" size. Downloaded images will be saved as ${configuredName}.jpg/png (if set), or cover.jpg/png, within the song folder if possible. If not, then they will be saved in Cantata's cache folder. If configured to 'Cache scaled covers', then the resized cover (as displayed) will be saved as a PNG within covers-scaled. For artist images: ${configuredName} is either: a. artist - if configured Album cover ${configuredName} is empty, or does not contain % b. ${albumArtist} - if configured Album cover ${configuredName} contains % and ${albumArtist} is not 'Various Artists' c. ${basicArtist} if configured Album cover ${configuredName} contains % and ${albumArtist} is 'Various Artists' ...if configured to 'Cache scaled covers', Cantata will check its scaled-covers cache folder (~/.cache/cantata/covers-scaled/SIZE) for: 1. ${albumArtist}.png ...if MPD folder exists, is not specified as a http URL, and is readable, then cantata will look for the following within the folder containing the song: 2. ${basicArtist}.jpg 3. ${basicArtist}.png 4. ${configuredName}.jpg 5. ${configuredName}.png ...the above will be repeated for the parent folder ...if song is from a Various Artists album, or file is a non-MPD file, then cantata will look for the following within the MPD root folder: 6. ${basicArtist}/${basicArtist}.jpg 7. ${basicArtist}/${basicArtist}.png 8. ${basicArtist}/${configuredName}.jpg 9. ${basicArtist}/${configuredName}.png ...then Cantata will check its cache folder (~/.cache/cantata/covers), for : 10. ${albumArtist}.jpg 11. ${albumArtist}.png ...if the MPD folder was specified as a http URL 12. ${url}/${dirFromFile}/${configuredName}.jpg 13. ${url}/${dirFromFile}/${configuredName}.png ...the above will be repeated for the parent folder ...lastly, if user has enabled downloading via last.fm 14. Query last.fm using ${albumArtist}. Cantata will attempt to download the image specified with the "extralarge" size. Downloaded images will be saved as artist.jpg/png, within the artist folder if the folder hierarchy is 2 levels (artist/album/) and the user has write permissions. If not, then they will be saved in Cantata's cache folder. If configured to 'Cache scaled covers', then the resized cover (as displayed) will be saved as a PNG within covers-scaled. For composer images: ...if configured to 'Cache scaled covers', Cantata will check its scaled-covers cache folder (~/.cache/cantata/covers-scaled/SIZE) for: 1. ${composer}.png ...if MPD folder exists, is not specified as a http URL, and is readable, then cantata will look for the following within the folder containing the song: 2. composer.jpg 3. composer.png ...the above will be repeated for the parent folder ...then Cantata will check its cache folder (~/.cache/cantata/covers), for : 4. ${composer}.jpg 5. ${composer}.png ...if the MPD folder was specified as a http URL 6. ${url}/${dirFromFile}/composer.jpg 7. ${url}/${dirFromFile}/composer.png ...the above will be repeated for the parent folder Downloaded images will be saved as composer.jpg/png, within the artist folder if the folder hierarchy is 2 levels (artist/album/) and the user has write permissions. If not, then they will be saved in Cantata's cache folder. If configured to 'Cache scaled covers', then the resized cover (as displayed) will be saved as a PNG within covers-scaled. For context view backdrops: ...if MPD folder exists, is not specified as a http URL, and is readable, OR the song's filename starts with '/', then cantata will look for the following within the folder containing the song: 1. ${currentArtist}-backdrop.jpg 2. ${currentArtist}-backdrop.png 3. backdrop.jpg 4. backdrop.png ...the above will be repeated for the parent folder ...if song is from a Various Artists album, or file is a non-MPD file, then cantata will look for the following within the MPD root folder: 5. ${currentArtist}/${currentArtist}-backdrop.jpg 6. ${currentArtist}/${currentArtist}-backdrop.png 7. ${currentArtist}/backdrop.jpg 8. ${currentArtist}/backdrop.png ...then Cantata will check its cache folder (~/.cache/cantata/backdrops), for : 9. ${currentArtist}.jpg ...internet services: 10. MusizBrainz is queried for an artist ID. If returned, this artist ID is used to locate a cover from fanart.tv 11. Download image from discogs ...lastly: 12. If all the above fails, a backdrop is created from all the album covers featuring the artist. Downloaded images will be saved as backdrop.jpg, within the artist folder if the current song is not from a Various Artists album, the file is from MPD, the folder hierarchy is 2 levels (artist/album/), and the user has write permissions. If not, then they will be saved in Cantata's cache folder. 7. Advanced Config Items ======================== Cantata contains a few advanced config items, for which there is currently no graphical way to control the setting. For these, you will need to edit Cantata's config by hand - whilst Cantata is NOT running! Under Linux, the Cantata config file will be: ~/.config/cantata/cantata.conf ( Current User ) /etc/xdg/cantata.conf ( All Users ) The following config items should be added to the "[General]" section. networkAccessEnabled= Configure whether Cantata should be allowed to make networks requests - for accessing covers, etc. By default this is set to true. volumeStep= Volume % increments. Used when mouse wheel is activated over volume control. Default is 5. (Values 1..20 are acceptable) e.g. [General] networkAccessEnabled=false volumeStep=2 8. CUE Files ============ If Cantata is running with MPD>=0.17, then it will attempt to parse the contents of CUE files. The CUE file will be parsed to ascertain the album and track details. The track durations for all bar the last should be accurate, and Cantata will attempt to determine the duration of the last track - but this might not be 100% accurate. If the CUE file references an audio file that does not exist in the MPD music folder, then the CUE file contents will NOT be used. There is no reliable way for Cantata to ascertain the text encoding that a CUE file uses - so, for portability, it would be best if your CUE files used UTF-8 encoding. When loading a CUE file, Cantata will attempt to load the file as if it was UTF-8 encoded, if this fails it will try with the "System" codec. You may add to this list of encodings to try by using the 'cueFileCodecs' config item (as detailed in section 7 above). If Cantata fails with all configured encodings, then it will 'peek' at the 1st 1k bytes, and ask Qt to see if it can determine the encoding - and fallback to UTF-8 otherwise. (This peeking and fallback was how Cantata behaved in pre 1.2 versions - and is how Clementine behaves (where Cantata's CUE file support originates from.)) NOTE: If Cantata cannot access the CUE file (as MPD is running on a remote host, or you have not configured the music folder correctly, etc.) then Cantata will add the CUE File to the album's track listing. 9. Streams ========== As of 1.4.0, Cantata only comes with TuneIn, ShoutCast, IceCast and Dirble stream providers. To install new providers, you can either use Cantata's download dialog (to install for Cantata's extras github repo), or install from file. To import new stream categories into Cantata, these files need to be GZip compressed tarballs of the following format. Filename: CATEGORY_NAME.streams (e.g. German Radio.streams) Contents: Either streams.xml, streams.xml.gz, OR settings.json, AND Either icon.png OR icon.svg streams.xml.gz should be GZip compressed. icon.svg should be a non-compressed SVG file. Icons for sub-categories (first level only) - these should be named the same as the category (with / replaced by _) - e.g. 'Hard_Soft Rock.svg' for 'Hard/Soft Rock') settings.json is really only intended to be used by Cantata for providers stored in its extras github repo. This JSON file has the following syntax: { "type": "streamType", "url": "Stream listing URL" } "streamType" can be either di, soma, or listenlive. Cantata contains code to download from "Stream listing URL" and parse the response based upon "streamType" In general, for externally provided stream lists, it is more convenient to just list all a providers streams within stream.xml.gz. This has the following format: if "addCategoryName" is set to true, then when Cantata adds a station from this category to either favourites or the playqueue, it will prepend the Category name to the station name. e.g. To create a stream category named "Wicked Radio" that has 2 sub-categeries, and 3 stream URLs you would need: streams.xml.gz with: streams.xml.gz & icon.svg need to be placed into a GZip compressed tarball named "Wicked Radio.streams" With the above example, Cantata would list the following in the streams view: > Wicked Radio > Rock Hard Rock Soft Rock Blues When "Hard Rock" is added to the playqueue, it will be listed as "Wicked Radio - Hard Rock" If Rock.png (or Rock.svg) and/or Blues.png (or Blues.svg) also exist in the tarball, then they will be copied to the install location and used as icons for the respective category. To create .streams file: 1. Place all files in a temp folder 2. cd into temp folder 3. tar cfz NAME.tar.gz * 4. mv NAME.tar.gz NAME.streams cantata-extra on github ----------------------- The streams page on Cantata's settings dialog, will allow you to install new providers from Cantata's extras github repo. To do this, the dialog first downloads the list of providers, this is obtained from the following URL: https://raw.githubusercontent.com/CDrummond/cantata-extra/master/streams/2.1/list.xml The contents of this file will be something like: ... If you want to manually download a provider, use the listed URL to download the .streams file. e.g. for the above: wget https://raw.githubusercontent.com/CDrummond/cantata-extra/master/streams/2.1/1.fm.streams.gz Extract the contents with: tar zxvf 1.fm.streams.gz If you wish to distribute a new stream provider via Cantata's extras github repo, please send me an email with the .streams.gz file. My address is in the AUTHORS file. 10. MultiMedia Keys =================== Linux and Windows builds, as of 1.2, now also support basic media-keys shortcuts. Linux use the GNOME settings daemon's MediaKeys interface. This interface is normally started automatically under GNOME/Unity. 11. DBUS Actions ================ All of Cantata's internal actions are accessible via DBUS. The cantata-remote helper script may be used to invoke these, e.g. to toggle playback: cantata-remote cantata playpausetrack The list of action names maybe obtained by calling the listActions DBUS method, e.g.: qdbus mpd.cantata /cantata listActions 12. Dynamic Helper Script - Local Mode ====================================== When a dynamic playlist is loaded in Cantata, the cantata-dynamic helper script is executed in the background to do the actual song selection. In this way the dynamic playlist can still function even when cantata is terminated. It is possible for this script to be controlled on the command line (although it was never written with this in mind). The list of dynamic playlists may be obtained by looking in ~/.local/share/cantata/dynamic To 'load' a dynamic playlist, all you need to do is symlink the desired one in ~/.local/share/cantata/dynamic to ~/.cache/cantata/dynamic/rules. Then you can start the helper by calling '/usr/share/cantata/scripts/cantata-dynamic start' (or first call '/usr/share/cantata/scripts/cantata-dynamic stop' to stop any current playlist). To pass connection details, cantata-dynamic reads the same environment variables as mpc - namely MPD_HOST and MPD_PORT e.g. the following bash script (not tested!) will stop any current dynamic playlist, load the 'MyPlaylist' playlist, and start this on the mpd at 'hostname:1234' with password 'pass' # Stop current dynamic playlist /usr/share/cantata/scripts/cantata-dynamic stop # Clear the playqueue (this requires mpc) MPD_HOST=pass@hostname MPD_PORT=1234 mpc clear # 'Load' new playlist if [ -f "$HOME/.cache/cantata/dynamic/rules" ] ; then rm "$HOME/.cache/cantata/dynamic/rules" fi ln -s "$HOME/.local/share/cantata/dynamic/MyPlaylist.rules" "$HOME/.cache/cantata/dynamic/rules" # Restart dynamic script MPD_HOST=pass@hostname MPD_PORT=1234 /usr/share/cantata/scripts/cantata-dynamic start 13. Dynamic Helper Script - Server Mode ======================================= In addition to the above, the helper script may be installed on the system containing MPD, and run in 'server' mode. This requires MPD>=0.17, as communications are via MPD's client-to-client messages. When run in this mode, it is intended that the script be run system-wide and started when the system (or MPD) is started. e.g. The script should be started as: /share/cantata/scripts/cantata-dynamic server e.g.: /usr/share/cantata/scripts/cantata-dynamic server /etc/cantata-dynamic.conf When run as described above, the script will automatically daemonize itself. To stop the dynamizer: /share/cantata/scripts/cantata-dynamic stopserver e.g.: /usr/share/cantata/scripts/cantata-dynamic stopserver When MPD informs Cantata that the 'cantata-dynamic-in' channel has been created, Cantata will automatically switch itself to server mode. The list of rules is then loaded from the server, and Cantata will change the backdrop of the dynamic page to indicate that the rules are comming from the server. Server mode is currently the only way for dynamic playlists to be used under Windows. Configuration ------------- The server mode has a simple ini-style configuration file, that has the following parameters (shown here with the default values): pidFile=/var/run/cantata-dynamic/pid filesDir=/var/lib/mpd/dynamic activeFile=/var/run/cantata-dynamic/rules mpdHost=localhost mpdPort=6600 mpdPassword= httpPort=0 'pidFile' When started, the script will store its PID within the file configured here. This is then used to detect if the script is running, etc. 'filesDir' This controls where the dynamic rules are stored. The user the script is run as should have read/write permissions to this folder. 'activeFile' When a set of rules is made 'active', all cantata does is create a symbolic link from the rules file to the file specified in 'activeFile'. Again, the user the script is run as should have read/write permissions to this file. 'mpdHost', 'mpdPort', and 'mpdPassword' are the connection details of the MPD server. 'httpPort' if this is set to non-zero, then the script will serve up a *very* simple HTTP control page - allowing playlists to be started and stopped. Installation ------------- Within the playlists folder is a systemd service file. NOTE: This service file assumes cantata has been installed to /usr, and that cantata-dynamic is in /usr/share/cantata/scripts. If this is not the case, then this will need to be edited. Copy playlists/cantata-dynamic.conf to /etc, and edit as appropriate. 14. Source Code =============== The Cantata source folder contains the following structure: 3rdparty - Third party libraries cmake - CMake scripts context - Context view classes dbus - DBUS related classes and XML files devices - Device related classes playlists - Playlists gui - General GUI classes, e.g. MainWindow, LibraryPage, etc. http - HTTP server icons - Icons models - Most models, apart from dynamic playlist and shortcut models mpd - MPD related classes; connection, status, song, etc. network - Generic Network classes (and proxy support for Qt builds) online - Jamendo, Magantune, SoundCloud, and Podcasts po - Translations replaygain - ReplayGain calculation streams - Internet radio streams support - Generic classes that /may/ be useful to other projects. Mainly used for Qt and Gtk support. tags - Tag reading, editing, etc. widgets - Widgets that are probably Cantata specific. windows - Files specfic to Windows builds.. mac - Files specfic to MacOSX builds. Cantata's SVG icons have been 'cleaned' using: scour --strip-xml-prolog --indent=none -i in.svg -o out.svg Symbolc media icons are taken from gnome icon theme, but have been scaled with rsvg-convert -a -w 128 -f svg in.svg -o out.svg 15. Debug Logging ================= Cantata contains some debug logging that might help to diagnose certain issues. To enable this, you must set an environment variable before starting Cantata. Therefore, its probably better to start Cantata from the commandline. The following debug values may be used: MPD communications 1 0x00000001 MPD Parsing 2 0x00000002 Covers 4 0x00000004 Wikipedia context info 8 0x00000008 Last.fm context info 16 0x00000010 Combined context info 32 0x00000020 Context widget 64 0x00000040 Context backdrop 128 0x00000080 Dynamic 256 0x00000100 Stream fetching 512 0x00000200 Http server 1024 0x00000400 Song dialog file checks 2048 0x00000800 (Tag Editor, etc) Network access 4096 0x00001000 Context lyrics 8192 0x00002000 Threads 16384 0x00004000 External tags 32768 0x00008000 Scrobbling 65536 0x00010000 Devices 131072 0x00020000 SQL 262144 0x00040000 MPD HTTP stream playback 524288 0x00080000 Other 1048576 0x00100000 (e.g. MediaKeys) These values may be combined to enable multiple output. e.g. to enable MPD and covers logging: CANTATA_DEBUG=5 cantata for just covers logging: CANTATA_DEBUG=4 cantata As of Cantata 1.2.0, all logging output is saved to a file. On Linux systems this will be ~/.cache/cantata/cantata.log For Windows this will be C:\Users\USERNAME\AppData\Local\mpd\cantata\cache\cantata.log And for OSX it will be /Users/$USER/Library/Caches/cantata/cantata/cantata.log When using the external helper to read/write tags, then there might also be additional logging in cantata-tags.log (in the same location as cantata.log) To disable logging to file, and log to the terminal instead, use a negative number. e.g. CANTATA_DEBUG=-4 With windows builds, it is probably easier to set the env var from a DOS command prompt box. e.g.: 1. Run cmd.exe from start menu. 2. cd c:\Program Files\Cantata 3. set CANTATA_DEBUG=4 4. cantata.exe NOTE: Debug logging will function regardless of whether you have created a Debug or Release build. 16. Credits =========== Cantata contains code/icons from: Amarok - amarok.kde.org (Transcoding, Cover fetching code in cover dialog) Clementine - www.clementine-player.org (Lyrics searches, CUE file parsing, digitally imported support, and stretched header view) Be::MPC - Wikipedia parsing code Quassel - quassel-irc.org (Qt-only keyboard short-cut config support) Solid - solid.kde.org (Device detection for Qt-only builds) Asunder - CDDB code libkcddb - MusicBrainz code libebur128 - https://github.com/jiixyj/libebur128 (Replay gain calculation) IcoMoon - http://icomoon.io/#icons (Monochrome sidebar icons) Qxt - Multi-media key support QtSolutions - QtIOCompressor, and QtSingleApplication QMPDClient - Last.fm scrobbling 17. Windows =========== Icon created under Linux using the createicon.sh in the windows folder. The following steps are used to compile Cantata, and create the windows installer. This assumes the following folder structure: z:\cantata\src [ Checkout of Cantata's source code ] z:\cantata\build z:\cantata\install [ make install will place target files here ] z:\dev\Qt z:\dev\taglib z:\dev\zlib z:\dev\ssl [ libeay32.dll and ssleay32.dll ] s 1. Install Qt (5.3 or later), cmake, TagLib and zlib. TagLib and zlib will probably need compiling. 2. Set (or amend) the following environemnt variables: QTDIR=z:\dev\Qt\5.3\mingw482_32 PATH=z:\dev\Qt\5.3\mingw482_32\bin;z:\dev\Qt\Tools\mingw482_32\bin;z:\dev\taglib\bin;z:\dev\cmake\bin 3. Load cantata's CMakeLists.txt in QtCreator, and pass the following to cmake: ../src -DCMAKE_BUILD_TYPE=Release -DENABLE_TAGLIB=OFF -DTAGLIB_FOUND=1 -DTAGLIB_INCLUDES=z:/dev/taglib/include -DTAGLIB_LIBRARIES=z:/dev/taglib/lib/libtag.dll.a -DTAGLIB_MP4_FOUND=1 -DTAGLIB_ASF_FOUND=1 -DTAGLIB_CAN_SAVE_ID3VER=1 -DZLIB_INCLUDE_DIR=z:/dev/zlib/include -DZLIB_LIBRARY=z:/dev/zlib/lib/libz.dll.a -DCMAKE_INSTALL_PREFIX=z:/cantata/install -DCANTATA_WINDOWS_INSTALLER_DEST=z:/cantata -DCANTATA_SSL_LIBS=z:/dev/ssl/libeay32.dll;z:/dev/ssl/ssleay32.dll Notes: -DENABLE_TAGLIB=OFF stops cmake from trying to find TagLib, as the TagLib settings have been manually set, 4. Build via QtCreator 5. Create an 'install' project in QtCreator - Projects - Add/Clone Selected - Expand Build Steps, and select install 6. Build 'install' project via QtCreator This build is as per Qt-only, but does not have support for dbus, local dynamic playlists, device support, or replaygain calculation. Create Installer ---------------- Run Nullsoft Scriptable Install System, and use the cantata.nsi that has been generated in the install folder. This will place the setup exe into the install folder as well. TagLib ------ Windows version of taglib was built from TagLib 1.9.1, using QtCreator with the following passed to cmake: -DCMAKE_BUILD_TYPE=Release -DWITH_ASF=1 -DWITH_MP4=1 -DCMAKE_INSTALL_PREFIX=z:\dev\taglib 18. Mac OSX =========== Icon created under OSX using the createicon.sh in the mac folder. The following steps are used to compile Cantata, and create the OS X application bundle. These steps assume the following structure: src/ build/ install/ 1. Install HomeBrew 2. brew install cmake taglib ffmpeg openssl qt5 3. Load cantata's CMakeLists.txt in QtCreator, and pass the following to cmake: ../src -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.9.1/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=`pwd`/../install 4. Build via QtCreator 5. Create an 'install' project in QtCreator - Projects - Add/Clone Selected - Expand Build Steps, and select install 6. Build 'install' project via QtCreator Create Installer ---------------- 1. Go into the 'mac' folder within the build folder 2. Run ./create-dmg.sh cantata-2.2.0/README.md000066400000000000000000000037371316350454000144200ustar00rootroot00000000000000Introduction ============ Cantata is a graphical client for MPD, supporting the following features: 1. Support for Linux, MacOSX, and Windows. 2. Multiple MPD collections. 3. Highly customisable layout. 4. Songs grouped by album in play queue. 5. Context view to show artist, album, and song information of current track. 6. Simple tag editor. 7. File organizer - use tags to organize files and folders. 8. Ability to calculate ReplyGain tags. (Linux only, and if relevant libraries installed) 9. Dynamic playlists. 10. Online services; Jamendo, Magnatune, SoundCloud, and Podcasts. 11. Radio stream support - with the ability to search for streams via TuneIn, ShoutCast, or Dirble. 12. USB-Mass-Storage and MTP device support. (Linux only, and if relevant libraries installed) 13. Audio CD ripping and playback. (Linux only, and if relevant libraries installed) 14. Playback of non-MPD songs - via simple in-built HTTP server if connected to MPD via a standard socket, otherwise filepath is sent to MPD. 15. MPRISv2 DBUS interface. 16. Basic support for touch-style interface (views are made 'flickable') 17. Scrobbling. 18. Ratings support. Cantata started off as a fork of QtMPC, however, the code (and user interface) is now *very* different to that of QtMPC. For more detailed information, please refer to the main [README](https://raw.githubusercontent.com/CDrummond/cantata/master/README) Screenshots =========== Some (outdated, 1.x) screenshots can be found at the [kde-apps](http://kde-apps.org/content/show.php/Cantata?content=147733) page. Downloads ========= Curently I'm developing v2, and as such the older 1.x is no longer actively maintained (the code base is *very* different). However, these older versions may still be downloaded from [Cantata's github wiki](https://github.com/CDrummond/cantata/wiki/Previous-%28Google-Code%29-Downloads) --- [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/craigd) cantata-2.2.0/TODO000066400000000000000000000027331316350454000136240ustar00rootroot00000000000000- Windows port - CMake scripts not working - Streams - If radio stream is a playlist, Cantata currently only uses the 1st entry. Perhaps Cantata should loop over all entries until we find one that plays? - last.fm streams? - Devices - libMTP does not support album artist tag. Cantata contains some work-arounds for this (such as guessing the album-artist tag via the folder structure, etc). These should be removed if libMTP ever supports album artist. - Port to MTP DBus service when this is released. - RemoteFsDevices - Cantata hangs if smb service is stopped before its un-mounted - Re-enable covers in sync dialog? - CD-Text? - Fix time-remaining calculation in action dialog. - Seek support for AudioCDs. Initial implementation works sometimes, but other times the song is re-started. Not in build due to being too flaky. - Possible issues with UDisks2, might not be able to get block device (e.g. /dev/sr0)? - Playback from MTP devices. - Mopdiy - Currently does not send valid DB date time (always 0). Users will need to force Cantata to update. - Does not support 'update' command - If the response of a stats call is all 0, then we assume the connection is to a Mopdiy server. Therefore, when adding files (from devices) these are encoded. - PlayQueue - Move code out of MainWindow class. - Bugs - Handle font (High DPI detection) and palette changes in custom widgets (Volume control, time slider, etc) cantata-2.2.0/cantata-remote.cmake000077500000000000000000000046661316350454000170540ustar00rootroot00000000000000#!/bin/bash # # cantata-remote # # Copyright (c) 2011-2014 Craig Drummond # # ---- # # 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; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # # --------------------------------------------------------------------- # # This shell scrpt is intended to be invoked from the Cantata badge on # the unity task bar. # function usage() { echo "$0 cantata " echo "$0 " } if ([ $# -eq 2 ] && [ "cantata" != "$1" ]) || [ $# == 0 ] || [ $# -gt 2 ] ; then usage fi if [ $1 = "cantata" ] ; then path="/cantata" methodPrefix=mpd.cantata method="triggerAction" methodArgument=$2 else path=/org/mpris/MediaPlayer2 methodPrefix=org.mpris.MediaPlayer2.Player method=$1 fi service=@PROJECT_REV_URL@ # If we have qdbus use that... qt=`which qdbus` if [ "$qt" != "" ] ; then $qt $service > /dev/null if [ $? -ne 0 ] ; then # Cantata not started? Try to start... cantata & sleep 1s fi $qt $service $path $method $methodArgument > /dev/null exit fi # No qdbus so try dbus-send... dbus=`which dbus-send` if [ "$dbus" != "" ] ; then status=`$dbus --print-reply --reply-timeout=250 --type=method_call --session --dest=$service $path org.freedesktop.DBus.Peer.Ping 2>&1 | grep "org.freedesktop.DBus.Error.ServiceUnknown" | wc -l` if [ "$status" != "0" ] ; then # Cantata not started? Try to start... cantata & sleep 1s fi if [ "$methodArgument" != "" ] ; then methodArgument="string:$methodArgument" fi echo "dbus-send --type=method_call --session --dest=$service $path $methodPrefix.$method $methodArgument" dbus-send --type=method_call --session --dest=$service $path $methodPrefix.$method $methodArgument fi cantata-2.2.0/cantata.desktop.cmake000066400000000000000000000056611316350454000172240ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Cantata GenericName=Music Player Client GenericName[bs]=Muzički player klijent GenericName[da]=Musik afspiller GenericName[de]=Grafischer Musik Player Client GenericName[es]=Cliente de reproducción de música GenericName[fi]=Musiikkisoitinasiakas GenericName[fr]=Client de lecture de musique GenericName[gl]=Cliente de reprodución de música GenericName[hu]=Zenelejátszókliens GenericName[jv]=Musik Player Client GenericName[ko]=음악 플레이어 클라이언트 GenericName[ms]=Klien Pemain Musik GenericName[nb]=Musikkavspiller-klient GenericName[oc]=Client de lectura de musica GenericName[pl]=Odtwarzacz muzyki GenericName[pt]=Reprodutor de música GenericName[pt_BR]=Reprodutor Multimídia GenericName[ru]=Клиент музыкального проигрывателя GenericName[sq]=Clienti player muzike GenericName[tr]=Muzik Çalıcı İstemcisi Icon=cantata Exec=cantata %U Categories=Qt;KDE;AudioVideo;Player; X-DBUS-StartupType=Unique X-DBUS-ServiceName=mpd.cantata Keywords=Music;MPD; Actions=Previous;PlayPause;Stop;StopAfterCurrent;Next; [Desktop Action Previous] Name=Previous Track Name[cs]=Předchozí skladba Name[de]=Vorheriges Stück Name[es]=Pista anterior Name[hu]=Előző szám Name[ko]=이전 곡 Name[pl]=Poprzedni utwór Name[pt]=Faixa anterior Name[ru]=Предыдущий трек Name[zh_CN]=上一个 Exec=@SHARE_INSTALL_PREFIX@/@CMAKE_PROJECT_NAME@/scripts/cantata-remote Previous [Desktop Action PlayPause] Name=Play/Pause Name[cs]=Přehrát/Pozastavit Name[de]=Abspielen/Pause Name[es]=Reproducir/Pausa Name[hu]=Lejátszás/Szünet Name[ko]=연주/멈춤 Name[pl]=Odtwarzaj/Wstrzymaj Name[pt]=Reproduzir/pausa Name[ru]=Воспроизвести/Пауза Name[zh_CN]=播放/暂停 Exec=@SHARE_INSTALL_PREFIX@/@CMAKE_PROJECT_NAME@/scripts/cantata-remote PlayPause [Desktop Action Stop] Name=Stop Name[cs]=Zastavit Name[de]=Stopp Name[es]=Detener Name[hu]=Állj Name[ko]=정지 Name[pl]=Stop Name[pt]=Parar Name[ru]=Остановить Name[zh_CN]=停止 Exec=@SHARE_INSTALL_PREFIX@/@CMAKE_PROJECT_NAME@/scripts/cantata-remote Stop [Desktop Action StopAfterCurrent] Name=Stop After Current Track Name[cs]=Zastavit po současné skladbě Name[de]=Stoppe nach aktuellem Stück Name[es]=Detener después de la pista actual Name[hu]=A mostani szám után leáll Name[ko]=지금 곡 다음 정지 Name[pl]=Zatrzymaj po obecnym utworze Name[pt]=Parar após a faixa atual Name[ru]=Остановить после текущего трека Name[zh_CN]=当前音轨后停止 Exec=@SHARE_INSTALL_PREFIX@/@CMAKE_PROJECT_NAME@/scripts/cantata-remote StopAfterCurrent [Desktop Action Next] Name=Next Track Name[cs]=Další skladba Name[de]=Nächstes Stück Name[es]=Pista siguiente Name[hu]=Következő szám Name[ko]=다음 곡 Name[pl]=Następny utwór Name[pt]=Faixa seguinte Name[ru]=Следующий трек Name[zh_CN]=下一个 Exec=@SHARE_INSTALL_PREFIX@/@CMAKE_PROJECT_NAME@/scripts/cantata-remote Next cantata-2.2.0/cantata.qrc000066400000000000000000000033611316350454000152540ustar00rootroot00000000000000 icons/stars.svg icons/consume.svg icons/sidebar-library.svg icons/sidebar-devices.svg icons/sidebar-folders.svg icons/sidebar-info.svg icons/sidebar-online.svg icons/sidebar-playlists.svg icons/sidebar-playqueue.svg icons/sidebar-search.svg online/icons/soundcloud.svg online/icons/jamendo.svg online/icons/magnatune.svg online/icons/podcasts.svg online/icons/itunes.svg online/icons/gpodder.svg streams/icons/favourites.svg streams/icons/icecast.svg streams/icons/shoutcast.svg streams/icons/tunein.svg streams/icons/dirble.svg icons/media-optical.svg icons/media-optical32.svg icons/view-media-album.svg icons/view-media-artist.svg icons/view-media-genre.svg icons/dice.svg icons/playlist.svg icons/radio.svg icons/gradcap.svg cantata-2.2.0/cantata_media.qrc000066400000000000000000000006121316350454000164070ustar00rootroot00000000000000 icons/media-next.svg icons/media-pause.svg icons/media-play.svg icons/media-play-rtl.svg icons/media-prev.svg icons/media-stop.svg cantata-2.2.0/cmake/000077500000000000000000000000001316350454000142075ustar00rootroot00000000000000cantata-2.2.0/cmake/COPYING-CMAKE-SCRIPTS000066400000000000000000000024601316350454000172070ustar00rootroot00000000000000Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cantata-2.2.0/cmake/CheckTagLibFileName.cmake000066400000000000000000000010471316350454000207140ustar00rootroot00000000000000# taglib changed filenames to be a char/wchar struct on some platforms, need to check for it macro (CHECK_TAGLIB_FILENAME TAGLIB_FILENAME_COMPLEX) include (CheckCXXSourceCompiles) set (CMAKE_REQUIRED_FLAGS ${TAGLIB_CFLAGS}) set (CMAKE_REQUIRED_INCLUDES ${TAGLIB_INCLUDES}) set (CMAKE_REQUIRED_LIBRARIES ${TAGLIB_LIBRARIES}) check_cxx_source_compiles( "#include int main() { TagLib::FileName fileName1(\"char\"); TagLib::FileName fileName2(L\"wchar\"); return 0; }" ${TAGLIB_FILENAME_COMPLEX}) endmacro (CHECK_TAGLIB_FILENAME) cantata-2.2.0/cmake/DeployQt5.cmake000077500000000000000000000372331316350454000170520ustar00rootroot00000000000000# - Functions to help assemble a standalone Qt5 executable. # A collection of CMake utility functions useful for deploying # Qt5 executables. # # The following functions are provided by this module: # write_qt5_conf # resolve_qt5_paths # fixup_qt5_executable # install_qt5_plugin_path # install_qt5_plugin # install_qt5_executable # Requires CMake 2.6 or greater because it uses function and # PARENT_SCOPE. Also depends on BundleUtilities.cmake. # # WRITE_QT5_CONF( ) # Writes a qt.conf file with the into . # # RESOLVE_QT5_PATHS( []) # Loop through list and if any don't exist resolve them # relative to the (if supplied) or the CMAKE_INSTALL_PREFIX. # # FIXUP_QT5_EXECUTABLE( [ ]) # Copies Qt plugins, writes a Qt configuration file (if needed) and fixes up a # Qt5 executable using BundleUtilities so it is standalone and can be # drag-and-drop copied to another machine as long as all of the system # libraries are compatible. # # should point to the executable to be fixed-up. # # should contain a list of the names or paths of any Qt plugins # to be installed. # # will be passed to BundleUtilities and should be a list of any already # installed plugins, libraries or executables to also be fixed-up. # # will be passed to BundleUtilities and should contain and directories # to be searched to find library dependencies. # # allows an custom plugins directory to be used. # # will force a qt.conf file to be written even if not needed. # # INSTALL_QT5_PLUGIN_PATH(plugin executable copy installed_plugin_path_var ) # Install (or copy) a resolved to the default plugins directory # (or ) relative to and store the result in # . # # If is set to TRUE then the plugins will be copied rather than # installed. This is to allow this module to be used at CMake time rather than # install time. # # If is set then anything installed will use this COMPONENT. # # INSTALL_QT5_PLUGIN(plugin executable copy installed_plugin_path_var ) # Install (or copy) an unresolved to the default plugins directory # (or ) relative to and store the result in # . See documentation of INSTALL_QT5_PLUGIN_PATH. # # INSTALL_QT5_EXECUTABLE( [ ]) # Installs Qt plugins, writes a Qt configuration file (if needed) and fixes up # a Qt5 executable using BundleUtilities so it is standalone and can be # drag-and-drop copied to another machine as long as all of the system # libraries are compatible. The executable will be fixed-up at install time. # is the COMPONENT used for bundle fixup and plugin installation. # See documentation of FIXUP_QT5_BUNDLE. #============================================================================= # Copyright 2011 Mike McQuaid # Copyright 2013 Mihai Moldovan # CMake - Cross Platform Makefile Generator # Copyright 2000-2011 Kitware, Inc., Insight Software Consortium # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the names of Kitware, Inc., the Insight Software Consortium, # nor the names of their contributors may be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # The functions defined in this file depend on the fixup_bundle function # (and others) found in BundleUtilities.cmake cmake_policy(SET CMP0011 NEW) cmake_policy(SET CMP0009 NEW) include(BundleUtilities) set(DeployQt5_cmake_dir "${CMAKE_CURRENT_LIST_DIR}") set(DeployQt5_apple_plugins_dir "PlugIns") function(write_qt5_conf qt_conf_dir qt_conf_contents) set(qt_conf_path "${qt_conf_dir}/qt.conf") message(STATUS "Writing ${qt_conf_path}") file(WRITE "${qt_conf_path}" "${qt_conf_contents}") endfunction() function(resolve_qt5_paths paths_var) set(executable_path ${ARGV1}) set(paths_resolved) foreach(path ${${paths_var}}) if(EXISTS "${path}") list(APPEND paths_resolved "${path}") else() if(${executable_path}) list(APPEND paths_resolved "${executable_path}/${path}") else() list(APPEND paths_resolved "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${path}") endif() endif() endforeach() set(${paths_var} ${paths_resolved} PARENT_SCOPE) endfunction() function(fixup_qt5_executable executable) set(qtplugins ${ARGV1}) set(libs ${ARGV2}) set(dirs ${ARGV3}) set(plugins_dir ${ARGV4}) set(request_qt_conf ${ARGV5}) message(STATUS "fixup_qt5_executable") message(STATUS " executable='${executable}'") message(STATUS " qtplugins='${qtplugins}'") message(STATUS " libs='${libs}'") message(STATUS " dirs='${dirs}'") message(STATUS " plugins_dir='${plugins_dir}'") message(STATUS " request_qt_conf='${request_qt_conf}'") if(QT_LIBRARY_DIR) list(APPEND dirs "${QT_LIBRARY_DIR}") endif() if(QT_BINARY_DIR) list(APPEND dirs "${QT_BINARY_DIR}") endif() if(APPLE) set(qt_conf_dir "${executable}/Contents/Resources") set(executable_path "${executable}") set(write_qt_conf TRUE) if(NOT plugins_dir) set(plugins_dir "${DeployQt5_apple_plugins_dir}") endif() else() get_filename_component(executable_path "${executable}" PATH) if(NOT executable_path) set(executable_path ".") endif() set(qt_conf_dir "${executable_path}") set(write_qt_conf ${request_qt_conf}) endif() foreach(plugin ${qtplugins}) set(installed_plugin_path "") install_qt5_plugin("${plugin}" "${executable}" 1 installed_plugin_path) list(APPEND libs ${installed_plugin_path}) endforeach() foreach(lib ${libs}) if(NOT EXISTS "${lib}") message(FATAL_ERROR "Library does not exist: ${lib}") endif() endforeach() resolve_qt5_paths(libs "${executable_path}") if(write_qt_conf) set(qt_conf_contents "[Paths]\nPlugins = ${plugins_dir}") write_qt5_conf("${qt_conf_dir}" "${qt_conf_contents}") endif() fixup_bundle("${executable}" "${libs}" "${dirs}") endfunction() function(install_qt5_plugin_path plugin executable copy installed_plugin_path_var) set(plugins_dir ${ARGV4}) set(component ${ARGV5}) set(configurations ${ARGV6}) if(EXISTS "${plugin}") if(APPLE) if(NOT plugins_dir) set(plugins_dir "${DeployQt5_apple_plugins_dir}") endif() set(plugins_path "${executable}/Contents/${plugins_dir}") else() get_filename_component(plugins_path "${executable}" PATH) if(NOT plugins_path) set(plugins_path ".") endif() if(plugins_dir) set(plugins_path "${plugins_path}/${plugins_dir}") endif() endif() set(plugin_group "") get_filename_component(plugin_path "${plugin}" PATH) get_filename_component(plugin_parent_path "${plugin_path}" PATH) get_filename_component(plugin_parent_dir_name "${plugin_parent_path}" NAME) get_filename_component(plugin_name "${plugin}" NAME) string(TOLOWER "${plugin_parent_dir_name}" plugin_parent_dir_name) if("${plugin_parent_dir_name}" STREQUAL "plugins") get_filename_component(plugin_group "${plugin_path}" NAME) set(${plugin_group_var} "${plugin_group}") endif() set(plugins_path "${plugins_path}/${plugin_group}") if(${copy}) file(MAKE_DIRECTORY "${plugins_path}") file(COPY "${plugin}" DESTINATION "${plugins_path}") else() if(configurations AND (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE)) set(configurations CONFIGURATIONS ${configurations}) else() unset(configurations) endif() install(FILES "${plugin}" DESTINATION "${plugins_path}" ${configurations} ${component}) endif() set(${installed_plugin_path_var} "${plugins_path}/${plugin_name}" PARENT_SCOPE) endif() endfunction() function(install_qt5_plugin plugin executable copy installed_plugin_path_var) set(plugins_dir ${ARGV4}) set(component ${ARGV5}) if(EXISTS "${plugin}") install_qt5_plugin_path("${plugin}" "${executable}" "${copy}" "${installed_plugin_path_var}" "${plugins_dir}" "${component}") else() #string(TOUPPER "QT_${plugin}_PLUGIN" plugin_var) set(plugin_release) set(plugin_debug) set(plugin_tmp_path) set(plugin_find_path "${Qt5Core_DIR}/../../../plugins/") get_filename_component(plugin_find_path "${plugin_find_path}" REALPATH) set(plugin_find_release_filename "lib${plugin}.dylib") set(plugin_find_debug_filename "lib${plugin}_debug.dylib") file(GLOB_RECURSE pluginlist "${plugin_find_path}" FOLLOW_SYMLINKS "${plugin_find_path}/*/lib*.dylib") foreach(found_plugin ${pluginlist}) get_filename_component(curname "${found_plugin}" NAME) if("${curname}" STREQUAL "${plugin_find_release_filename}") set(plugin_tmp_release_path "${found_plugin}") endif() if("${curname}" STREQUAL "${plugin_find_debug_filename}") set(plugin_tmp_debug_path "${found_plugin}") endif() endforeach() if((NOT DEFINED plugin_tmp_release_path OR NOT EXISTS "${plugin_tmp_release_path}") AND (NOT DEFINED plugin_tmp_debug_PATH OR NOT EXISTS "${plugin_tmp_debug_path}")) message(WARNING "Qt plugin \"${plugin}\" not recognized or found.") endif() if(EXISTS "${plugin_tmp_release_path}") set(plugin_release "${plugin_tmp_release_path}") elseif(EXISTS "${plugin_tmp_debug_path}") set(plugin_release "${plugin_tmp_debug_path}") endif() if(EXISTS "${plugin_tmp_debug_path}") set(plugin_debug "${plugin_tmp_debug_path}") elseif(EXISTS "${plugin_tmp_release_path}") set(plugin_debug "${plugin_tmp_release_path}") endif() if(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) install_qt5_plugin_path("${plugin_release}" "${executable}" "${copy}" "${installed_plugin_path_var}_release" "${plugins_dir}" "${component}" "Release|RelWithDebInfo|MinSizeRel") install_qt5_plugin_path("${plugin_debug}" "${executable}" "${copy}" "${installed_plugin_path_var}_debug" "${plugins_dir}" "${component}" "Debug") if(CMAKE_BUILD_TYPE MATCHES "^Debug$") set(${installed_plugin_path_var} ${${installed_plugin_path_var}_debug}) else() set(${installed_plugin_path_var} ${${installed_plugin_path_var}_release}) endif() else() install_qt5_plugin_path("${plugin_release}" "${executable}" "${copy}" "${installed_plugin_path_var}" "${plugins_dir}" "${component}") endif() endif() set(${installed_plugin_path_var} ${${installed_plugin_path_var}} PARENT_SCOPE) endfunction() function(install_qt5_executable executable) set(qtplugins ${ARGV1}) set(libs ${ARGV2}) set(dirs ${ARGV3}) set(plugins_dir ${ARGV4}) set(request_qt_conf ${ARGV5}) set(component ${ARGV6}) if(QT_LIBRARY_DIR) list(APPEND dirs "${QT_LIBRARY_DIR}") endif() if(QT_BINARY_DIR) list(APPEND dirs "${QT_BINARY_DIR}") endif() if(component) set(component COMPONENT ${component}) else() unset(component) endif() get_filename_component(executable_absolute "${executable}" ABSOLUTE) if(EXISTS "${QT_QTCORE_LIBRARY_RELEASE}") gp_file_type("${executable_absolute}" "${QT_QTCORE_LIBRARY_RELEASE}" qtcore_type) elseif(EXISTS "${QT_QTCORE_LIBRARY_DEBUG}") gp_file_type("${executable_absolute}" "${QT_QTCORE_LIBRARY_DEBUG}" qtcore_type) endif() if(qtcore_type STREQUAL "system") set(qt_plugins_dir "") endif() if(QT_IS_STATIC) message(WARNING "Qt built statically: not installing plugins.") else() foreach(plugin ${qtplugins}) message(STATUS "trying to install plugin ${plugin}") set(installed_plugin_paths "") install_qt5_plugin("${plugin}" "${executable}" 0 installed_plugin_paths "${plugins_dir}" "${component}") list(APPEND libs ${installed_plugin_paths}) endforeach() endif() resolve_qt5_paths(libs "") install(CODE "include(\"${DeployQt5_cmake_dir}/DeployQt5.cmake\") set(BU_CHMOD_BUNDLE_ITEMS TRUE) FIXUP_QT5_EXECUTABLE(\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${executable}\" \"\" \"${libs}\" \"${dirs}\" \"${plugins_dir}\" \"${request_qt_conf}\")" ${component} ) endfunction() cantata-2.2.0/cmake/FindCDDB.cmake000066400000000000000000000006301316350454000165050ustar00rootroot00000000000000# - Try to find CDDB # Once done this will define # # CDDB_FOUND - system has UDev # CDDB_INCLUDE_DIR - the libudev include directory # CDDB_LIBS - The libudev libraries find_path(CDDB_INCLUDE_DIR cddb/cddb.h) find_library(CDDB_LIBS cddb) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CDDB DEFAULT_MSG CDDB_INCLUDE_DIR CDDB_LIBS) mark_as_advanced(CDDB_INCLUDE_DIR CDDB_LIBS) cantata-2.2.0/cmake/FindCdioparanoia.cmake000066400000000000000000000025241316350454000204060ustar00rootroot00000000000000# - Try to find cdio_paranoia # Once done this will define # # CDIOPARANOIA_FOUND - system has libcdio_paranoia # CDIOPARANOIA_INCLUDE_DIRS - the libcdio_paranoia include directory # CDIOPARANOIA_LIBRARIES - The libcdio_paranoia libraries include(FindPkgConfig) include(CheckIncludeFiles) if(PKG_CONFIG_FOUND) pkg_check_modules (CDIOPARANOIA libcdio_paranoia) list(APPEND CDIOPARANOIA_INCLUDE_DIRS ${CDIOPARANOIA_INCLUDEDIR}) endif() if (CDIOPARANOIA_FOUND) check_include_files(cdio/paranoia.h HAVE_CDIO_PARANOIA_H) check_include_files(cdio/cdda.h HAVE_CDIO_CDDA_H) # Issue 1022 - sometimes its cdio/paranoia/paranoia.h if (NOT HAVE_CDIO_PARANOIA_H) check_include_files(cdio/paranoia/paranoia.h HAVE_CDIO_PARANOIA_PARANOIA_H) endif() if (NOT HAVE_CDIO_CDDA_H) check_include_files(cdio/paranoia/cdda.h HAVE_CDIO_PARANOIA_CDDA_H) endif() if (NOT HAVE_CDIO_PARANOIA_H AND NOT HAVE_CDIO_PARANOIA_PARANOIA_H AND NOT HAVE_CDIO_CDDA_H AND NOT HAVE_CDIO_PARANOIA_CDDA_H) set(CDIOPARANOIA_FOUND OFF) message("Failed to determine cdio/paranoia header file location") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CDIOPARANOIA DEFAULT_MSG CDIOPARANOIA_INCLUDE_DIRS CDIOPARANOIA_LIBRARIES) mark_as_advanced(CDIOPARANOIA_INCLUDE_DIRS CDIOPARANOIA_LIBRARIES) cantata-2.2.0/cmake/FindCdparanoia.cmake000066400000000000000000000034401316350454000200540ustar00rootroot00000000000000# - Try to find the CD Paranoia libraries # Once done this will define # # CDPARANOIA_FOUND - system has cdparanoia # CDPARANOIA_INCLUDE_DIR - the cdparanoia include directory # CDPARANOIA_LIBRARIES - Link these to use cdparanoia # # Copyright (c) 2006, Richard Laerkaeng, # Copyright (c) 2007, Allen Winter, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(CheckCSourceCompiles) if (CDPARANOIA_INCLUDE_DIR AND CDPARANOIA_LIBRARIES) # in cache already SET(CDPARANOIA_FOUND TRUE) else (CDPARANOIA_INCLUDE_DIR AND CDPARANOIA_LIBRARIES) FIND_PATH(CDPARANOIA_INCLUDE_DIR cdda_interface.h PATH_SUFFIXES cdda) FIND_LIBRARY(CDPARANOIA_LIBRARY NAMES cdda_paranoia) FIND_LIBRARY(CDPARANOIA_IF_LIBRARY NAMES cdda_interface) IF (CDPARANOIA_LIBRARY AND CDPARANOIA_IF_LIBRARY) SET(CDPARANOIA_LIBRARIES ${CDPARANOIA_LIBRARY} ${CDPARANOIA_IF_LIBRARY} "-lm") ENDIF (CDPARANOIA_LIBRARY AND CDPARANOIA_IF_LIBRARY) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cdparanoia DEFAULT_MSG CDPARANOIA_LIBRARIES CDPARANOIA_INCLUDE_DIR) MARK_AS_ADVANCED(CDPARANOIA_INCLUDE_DIR CDPARANOIA_LIBRARIES) if (CDPARANOIA_FOUND) set(CMAKE_REQUIRED_INCLUDES ${CDPARANOIA_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES ${CDPARANOIA_LIBRARIES}) check_c_source_compiles("#include #include int main() { paranoia_cachemodel_size(0, 0); return 0; }" CDPARANOIA_HAS_CACHEMODEL_SIZE) endif (CDPARANOIA_FOUND) endif (CDPARANOIA_INCLUDE_DIR AND CDPARANOIA_LIBRARIES) cantata-2.2.0/cmake/FindEbur128.cmake000066400000000000000000000012731316350454000171450ustar00rootroot00000000000000# Locate libebur128 library # This module defines # EBUR128_LIBRARY, the name of the library to link against # EBUR128_FOUND, if false, do not try to link # EBUR128_INCLUDE_DIR, where to find header # set(EBUR128_FOUND FALSE) find_path(EBUR128_INCLUDE_DIR ebur128.h HINTS PATH_SUFFIXES include PATHS ~/Library/Frameworks /Library/Frameworks /usr/local/include /usr/include /sw/include /opt/local/include /opt/csw/include /opt/include /mingw ) find_library(EBUR128_LIBRARY NAMES ebur128 HINTS PATH_SUFFIXES lib64 lib PATHS /usr/local /usr /sw /opt/local /opt/csw /opt /mingw ) if(EBUR128_LIBRARY) set(EBUR128_FOUND TRUE) endif(EBUR128_LIBRARY) cantata-2.2.0/cmake/FindFFMPEG.cmake000066400000000000000000000021071316350454000167560ustar00rootroot00000000000000find_package(PkgConfig) pkg_check_modules(PC_LIBAVCODEC QUIET libavcodec) pkg_check_modules(PC_LIBAVFORMAT QUIET libavformat) pkg_check_modules(PC_LIBAVUTIL QUIET libavutil) find_path(FFMPEG_INCLUDE_DIR libavcodec/avcodec.h HINTS ${PC_LIBAVCODEC_INCLUDEDIR} ${PC_LIBAVCODEC_INCLUDE_DIRS}) find_library(LIBAVCODEC_LIBRARY NAMES avcodec avcodec-52 HINTS ${PC_LIBAVCODEC_LIBDIR} ${PC_LIBAVCODEC_LIBRARY_DIRS}) find_library(LIBAVFORMAT_LIBRARY NAMES avformat avformat-52 HINTS ${PC_LIBAVFORMAT_LIBDIR} ${PC_LIBAVFORMAT_LIBRARY_DIRS}) find_library(LIBAVUTIL_LIBRARY NAMES avutil avutil-50 HINTS ${PC_LIBAVUTIL_LIBDIR} ${PC_LIBAVUTIL_LIBRARY_DIRS}) set(FFMPEG_LIBRARIES ${LIBAVCODEC_LIBRARY} ${LIBAVFORMAT_LIBRARY} ${LIBAVUTIL_LIBRARY}) set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(FFMPEG DEFAULT_MSG LIBAVCODEC_LIBRARY LIBAVFORMAT_LIBRARY LIBAVUTIL_LIBRARY FFMPEG_INCLUDE_DIR) mark_as_advanced(LIBAVCODEC_LIBRARY LIBAVFORMAT_LIBRARY LIBAVUTIL_LIBRARY FFMPEG_INCLUDE_DIR) cantata-2.2.0/cmake/FindFoundation.cmake000066400000000000000000000013031316350454000201150ustar00rootroot00000000000000# - Find Foundation on Mac # # FOUNDATION_LIBRARY - the library to use Foundation # FOUNDATION_FOUND - true if Foundation has been found # Copyright (c) 2009, Harald Fernengel # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(CMakeFindFrameworks) cmake_find_frameworks(Foundation) if (Foundation_FRAMEWORKS) set(FOUNDATION_LIBRARY "-framework Foundation" CACHE FILEPATH "Foundation framework" FORCE) set(FOUNDATION_FOUND 1) endif (Foundation_FRAMEWORKS) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Foundation DEFAULT_MSG FOUNDATION_LIBRARY) cantata-2.2.0/cmake/FindIOKit.cmake000066400000000000000000000012751316350454000167760ustar00rootroot00000000000000# - Find IOKit on Mac # # IOKIT_LIBRARY - the library to use IOKit # IOKIT_FOUND - true if IOKit has been found # Copyright (c) 2009, Harald Fernengel # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(CMakeFindFrameworks) cmake_find_frameworks(IOKit) cmake_find_frameworks(CoreFoundation) if (IOKit_FRAMEWORKS) set(IOKIT_LIBRARY "-framework IOKit -framework CoreFoundation" CACHE FILEPATH "IOKit framework" FORCE) set(IOKIT_FOUND 1) endif (IOKit_FRAMEWORKS) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(IOKit DEFAULT_MSG IOKIT_LIBRARY) cantata-2.2.0/cmake/FindLIBVLC.cmake000066400000000000000000000064521316350454000167740ustar00rootroot00000000000000############################################################################# # VLC - CMake module # Copyright (C) 2012 Tadej Novak # Original author: Rohit Yadav # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . ############################################################################# # If it's found it sets LIBVLC_FOUND to TRUE # and following variables are set: # LIBVLC_INCLUDE_DIR # LIBVLC_LIBRARY # FIND_PATH and FIND_LIBRARY normally search standard locations # before the specified paths. To search non-standard paths first, # FIND_* is invoked first with specified paths and NO_DEFAULT_PATH # and then again with no specified paths to search the default # locations. When an earlier FIND_* succeeds, subsequent FIND_*s # searching for the same item do nothing. #Put here path to custom location #example: /home/user/vlc/include etc.. FIND_PATH(LIBVLC_INCLUDE_DIR vlc/vlc.h HINTS "$ENV{LIBVLC_INCLUDE_PATH}" PATHS #Mac OS and Contribs "${CMAKE_CURRENT_SOURCE_DIR}/contribs/include" "${CMAKE_CURRENT_SOURCE_DIR}/contribs/include/vlc" # Env "$ENV{LIB_DIR}/include" "$ENV{LIB_DIR}/include/vlc" # "/usr/include" "/usr/include/vlc" "/usr/local/include" "/usr/local/include/vlc" #mingw c:/msys/local/include ) FIND_PATH(LIBVLC_INCLUDE_DIR PATHS "${CMAKE_INCLUDE_PATH}/vlc" NAMES vlc.h) #Put here path to custom location #example: /home/user/vlc/lib etc.. FIND_LIBRARY(LIBVLC_LIBRARY NAMES vlc libvlc HINTS "$ENV{LIBVLC_LIBRARY_PATH}" PATHS "$ENV{LIB_DIR}/lib" #Mac OS "${CMAKE_CURRENT_SOURCE_DIR}/contribs/lib" "${CMAKE_CURRENT_SOURCE_DIR}/contribs/plugins" #mingw c:/msys/local/lib ) FIND_LIBRARY(LIBVLC_LIBRARY NAMES vlc libvlc) FIND_LIBRARY(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore HINTS "$ENV{LIBVLC_LIBRARY_PATH}" PATHS "$ENV{LIB_DIR}/lib" #Mac OS "${CMAKE_CURRENT_SOURCE_DIR}/contribs/lib" "${CMAKE_CURRENT_SOURCE_DIR}/contribs/plugins" #mingw c:/msys/local/lib ) FIND_LIBRARY(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore) IF (LIBVLC_INCLUDE_DIR AND LIBVLC_LIBRARY AND LIBVLCCORE_LIBRARY) SET(LIBVLC_FOUND TRUE) ENDIF (LIBVLC_INCLUDE_DIR AND LIBVLC_LIBRARY AND LIBVLCCORE_LIBRARY) IF (LIBVLC_FOUND) IF (NOT LIBVLC_FIND_QUIETLY) MESSAGE(STATUS "Found LibVLC include-dir path: ${LIBVLC_INCLUDE_DIR}") MESSAGE(STATUS "Found LibVLC library path:${LIBVLC_LIBRARY}") MESSAGE(STATUS "Found LibVLCcore library path:${LIBVLCCORE_LIBRARY}") ENDIF (NOT LIBVLC_FIND_QUIETLY) ELSE (LIBVLC_FOUND) IF (LIBVLC_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find LibVLC (install libvlc-dev ad libvlccore-dev)") ENDIF (LIBVLC_FIND_REQUIRED) ENDIF (LIBVLC_FOUND) cantata-2.2.0/cmake/FindMPG123.cmake000066400000000000000000000010571316350454000166660ustar00rootroot00000000000000find_package(PkgConfig) pkg_check_modules(PC_MPG123 QUIET libmpg123) find_path(MPG123_INCLUDE_DIR mpg123.h HINTS ${PC_MPG123_INCLUDEDIR} ${PC_MPG123_INCLUDE_DIRS}) find_library(MPG123_LIBRARY NAMES mpg123 mpg123-0 HINTS ${PC_MPG123_LIBDIR} ${PC_MPG123_LIBRARY_DIRS}) set(MPG123_LIBRARIES ${MPG123_LIBRARY}) set(MPG123_INCLUDE_DIRS ${MPG123_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MPG123 DEFAULT_MSG MPG123_LIBRARY MPG123_INCLUDE_DIR) mark_as_advanced(MPG123_INCLUDE_DIR MPG123_LIBRARY) cantata-2.2.0/cmake/FindMtp.cmake000066400000000000000000000034201316350454000165510ustar00rootroot00000000000000# - Try to find the libmtp library # Once done this will define # # MTP_FOUND - system has libmtp # MTP_INCLUDE_DIR - the libmtp include directory # MTP_LIBRARIES - Link these to use libmtp # MTP_DEFINITIONS - Compiler switches required for using libmtp # if (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND MTP_VERSION_OKAY) # in cache already SET(MTP_FOUND TRUE) else (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND MTP_VERSION_OKAY) if(NOT WIN32) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls INCLUDE(FindPkgConfig) pkg_check_modules(_MTP libmtp) set(MTP_DEFINITIONS ${_MTP_CFLAGS}) endif(NOT WIN32) FIND_PATH(MTP_INCLUDE_DIR libmtp.h ${_MTP_INCLUDE_DIRS} ) FIND_LIBRARY(MTP_LIBRARIES NAMES mtp PATHS ${_MTP_LIBRARY_DIRS} ) exec_program(${PKG_CONFIG_EXECUTABLE} ARGS --atleast-version=1.1.0 libmtp OUTPUT_VARIABLE _pkgconfigDevNull RETURN_VALUE MTP_VERSION_OKAY) if (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND MTP_VERSION_OKAY STREQUAL "0") set(MTP_FOUND TRUE) endif (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND MTP_VERSION_OKAY STREQUAL "0") if (MTP_FOUND) if (NOT Mtp_FIND_QUIETLY) message(STATUS "Found MTP: ${MTP_LIBRARIES}") endif (NOT Mtp_FIND_QUIETLY) else (MTP_FOUND) if (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND NOT MTP_VERSION_OKAY STREQUAL "0") message(STATUS "Found MTP but version requirements not met") endif (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND NOT MTP_VERSION_OKAY STREQUAL "0") if (Mtp_FIND_REQUIRED) message(FATAL_ERROR "Could NOT find MTP") endif (Mtp_FIND_REQUIRED) endif (MTP_FOUND) MARK_AS_ADVANCED(MTP_INCLUDE_DIR MTP_LIBRARIES MTP_VERSION_OKAY) endif (MTP_INCLUDE_DIR AND MTP_LIBRARIES AND MTP_VERSION_OKAY) cantata-2.2.0/cmake/FindMusicBrainz5.cmake000066400000000000000000000025101316350454000203230ustar00rootroot00000000000000# Module to find the musicbrainz-4 library # # It defines # MUSICBRAINZ5_INCLUDE_DIRS - the include dir # MUSICBRAINZ5_LIBRARIES - the required libraries # MUSICBRAINZ5_FOUND - true if both of the above have been found # Copyright (c) 2006,2007 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if(MUSICBRAINZ5_INCLUDE_DIRS AND MUSICBRAINZ5_LIBRARIES) set(MUSICBRAINZ5_FIND_QUIETLY TRUE) endif(MUSICBRAINZ5_INCLUDE_DIRS AND MUSICBRAINZ5_LIBRARIES) IF (NOT WIN32) find_package(PkgConfig) PKG_SEARCH_MODULE( MUSICBRAINZ5 libmusicbrainz5cc ) if (NOT MUSICBRAINZ5_FOUND) PKG_SEARCH_MODULE( MUSICBRAINZ5 libmusicbrainz5 ) endif (NOT MUSICBRAINZ5_FOUND) ELSE (NOT WIN32) FIND_PATH( MUSICBRAINZ5_INCLUDE_DIRS musicbrainz5/Disc.h ) FIND_LIBRARY( MUSICBRAINZ5_LIBRARIES NAMES musicbrainz5cc ) if (NOT MUSICBRAINZ5_LIBRARIES) FIND_LIBRARY( MUSICBRAINZ5_LIBRARIES NAMES musicbrainz5 ) endif (NOT MUSICBRAINZ5_LIBRARIES) ENDIF (NOT WIN32) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( MUSICBRAINZ5 DEFAULT_MSG MUSICBRAINZ5_INCLUDE_DIRS MUSICBRAINZ5_LIBRARIES) MARK_AS_ADVANCED(MUSICBRAINZ5_INCLUDE_DIRS MUSICBRAINZ5_LIBRARIES) cantata-2.2.0/cmake/FindPhonon.cmake000066400000000000000000000062671316350454000172660ustar00rootroot00000000000000# Find libphonon # Once done this will define # # PHONON_FOUND - system has Phonon Library # PHONON_INCLUDES - the Phonon include directory # PHONON_LIBS - link these to use Phonon # PHONON_VERSION - the version of the Phonon Library # Copyright (c) 2008, Matthias Kretz # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. macro(_phonon_find_version) set(_phonon_namespace_header_file "${PHONON_INCLUDE_DIR}/phonon/phononnamespace.h") if (APPLE AND EXISTS "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") set(_phonon_namespace_header_file "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") endif (APPLE AND EXISTS "${PHONON_INCLUDE_DIR}/Headers/phononnamespace.h") file(READ ${_phonon_namespace_header_file} _phonon_header LIMIT 5000 OFFSET 1000) string(REGEX MATCH "define PHONON_VERSION_STR \"(4\\.[0-9]+\\.[0-9a-z]+)\"" _phonon_version_match "${_phonon_header}") set(PHONON_VERSION "${CMAKE_MATCH_1}") message(STATUS "Phonon Version: ${PHONON_VERSION}") endmacro(_phonon_find_version) if(PHONON_FOUND) # Already found, nothing more to do except figuring out the version _phonon_find_version() else(PHONON_FOUND) if(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) set(PHONON_FIND_QUIETLY TRUE) endif(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) # As discussed on kde-buildsystem: first look at CMAKE_PREFIX_PATH, then at the suggested PATHS (kde4 install dir) find_library(PHONON_LIBRARY NAMES phonon PATHS ${KDE4_LIB_INSTALL_DIR} ${QT_LIBRARY_DIR} NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) # then at the default system locations (CMAKE_SYSTEM_PREFIX_PATH, i.e. /usr etc.) find_library(PHONON_LIBRARY NAMES phonon) find_path(PHONON_INCLUDE_DIR NAMES phonon/phonon_export.h PATHS ${KDE4_INCLUDE_INSTALL_DIR} ${QT_INCLUDE_DIR} ${INCLUDE_INSTALL_DIR} ${QT_LIBRARY_DIR} NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) find_path(PHONON_INCLUDE_DIR NAMES phonon/phonon_export.h) if(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) set(PHONON_LIBS ${phonon_LIB_DEPENDS} ${PHONON_LIBRARY}) set(PHONON_INCLUDES ${PHONON_INCLUDE_DIR}/KDE ${PHONON_INCLUDE_DIR}) set(PHONON_FOUND TRUE) _phonon_find_version() else(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) set(PHONON_FOUND FALSE) endif(PHONON_INCLUDE_DIR AND PHONON_LIBRARY) if(PHONON_FOUND) if(NOT PHONON_FIND_QUIETLY) message(STATUS "Found Phonon: ${PHONON_LIBRARY}") message(STATUS "Found Phonon Includes: ${PHONON_INCLUDES}") endif(NOT PHONON_FIND_QUIETLY) else(PHONON_FOUND) if(Phonon_FIND_REQUIRED) if(NOT PHONON_INCLUDE_DIR) message(STATUS "Phonon includes NOT found!") endif(NOT PHONON_INCLUDE_DIR) if(NOT PHONON_LIBRARY) message(STATUS "Phonon library NOT found!") endif(NOT PHONON_LIBRARY) message(FATAL_ERROR "Phonon library or includes NOT found!") else(Phonon_FIND_REQUIRED) message(STATUS "Unable to find Phonon") endif(Phonon_FIND_REQUIRED) endif(PHONON_FOUND) mark_as_advanced(PHONON_INCLUDE_DIR PHONON_LIBRARY PHONON_INCLUDES) endif(PHONON_FOUND) cantata-2.2.0/cmake/FindQJSON.cmake000066400000000000000000000006761316350454000167150ustar00rootroot00000000000000# - Try to find QJSON # Once done this will define # # QJSON_FOUND - system has QJson # QJSON_INCLUDE_DIR - the libqjson include directory # QJSON_LIBRARIES - The libqjson libraries find_path(QJSON_INCLUDE_DIR qjson/parser.h) find_library(QJSON_LIBRARIES qjson) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(QJSON DEFAULT_MSG QJSON_INCLUDE_DIR QJSON_LIBRARIES) mark_as_advanced(QJSON_INCLUDE_DIR QJSON_LIBRARIES) cantata-2.2.0/cmake/FindSqlite.cmake000066400000000000000000000050701316350454000172550ustar00rootroot00000000000000# - Try to find Sqlite # Once done this will define # # SQLITE_FOUND - system has Sqlite # SQLITE_INCLUDE_DIR - the Sqlite include directory # SQLITE_LIBRARIES - Link these to use Sqlite # SQLITE_DEFINITIONS - Compiler switches required for using Sqlite # # Copyright (c) 2008, Gilles Caulier, # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (SQLITE_INCLUDE_DIR AND SQLITE_LIBRARIES) # in cache already set(Sqlite_FIND_QUIETLY TRUE) endif (SQLITE_INCLUDE_DIR AND SQLITE_LIBRARIES) # use pkg-config to get the directories and then use these values # in the find_path() and find_library() calls if (NOT WIN32) find_package(PkgConfig) pkg_check_modules(PC_SQLITE sqlite3) set(SQLITE_DEFINITIONS ${PC_SQLITE_CFLAGS_OTHER}) endif (NOT WIN32) find_path(SQLITE_INCLUDE_DIR NAMES sqlite3.h PATHS ${PC_SQLITE_INCLUDEDIR} ${PC_SQLITE_INCLUDE_DIRS} ) find_library(SQLITE_LIBRARIES NAMES sqlite3 PATHS ${PC_SQLITE_LIBDIR} ${PC_SQLITE_LIBRARY_DIRS} ) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Sqlite DEFAULT_MSG SQLITE_INCLUDE_DIR SQLITE_LIBRARIES) # show the SQLITE_INCLUDE_DIR and SQLITE_LIBRARIES variables only in the advanced view mark_as_advanced(SQLITE_INCLUDE_DIR SQLITE_LIBRARIES) cantata-2.2.0/cmake/FindTaglib-Extras.cmake000066400000000000000000000110451316350454000204610ustar00rootroot00000000000000# - Try to find the Taglib-Extras library # Once done this will define # # TAGLIB-EXTRAS_FOUND - system has the taglib library # TAGLIB-EXTRAS_CFLAGS - the taglib cflags # TAGLIB-EXTRAS_LIBRARIES - The libraries needed to use taglib # Copyright (c) 2006, Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if(NOT TAGLIB-EXTRAS_MIN_VERSION) set(TAGLIB-EXTRAS_MIN_VERSION "1.0") endif(NOT TAGLIB-EXTRAS_MIN_VERSION) if(NOT WIN32) find_program(TAGLIB-EXTRASCONFIG_EXECUTABLE NAMES taglib-extras-config PATHS ${BIN_INSTALL_DIR} ) endif(NOT WIN32) #reset vars set(TAGLIB-EXTRAS_LIBRARIES) set(TAGLIB-EXTRAS_CFLAGS) # if taglib-extras-config has been found if(TAGLIB-EXTRASCONFIG_EXECUTABLE) exec_program(${TAGLIB-EXTRASCONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB-EXTRAS_VERSION) if(TAGLIB-EXTRAS_VERSION STRLESS "${TAGLIB-EXTRAS_MIN_VERSION}") message(STATUS "TagLib-Extras version too old: version searched :${TAGLIB-EXTRAS_MIN_VERSION}, found ${TAGLIB-EXTRAS_VERSION}") set(TAGLIB-EXTRAS_FOUND FALSE) else(TAGLIB-EXTRAS_VERSION STRLESS "${TAGLIB-EXTRAS_MIN_VERSION}") exec_program(${TAGLIB-EXTRASCONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB-EXTRAS_LIBRARIES) exec_program(${TAGLIB-EXTRASCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB-EXTRAS_CFLAGS) if(TAGLIB-EXTRAS_LIBRARIES AND TAGLIB-EXTRAS_CFLAGS) set(TAGLIB-EXTRAS_FOUND TRUE) endif(TAGLIB-EXTRAS_LIBRARIES AND TAGLIB-EXTRAS_CFLAGS) string(REGEX REPLACE " *-I" ";" TAGLIB-EXTRAS_INCLUDES "${TAGLIB-EXTRAS_CFLAGS}") endif(TAGLIB-EXTRAS_VERSION STRLESS "${TAGLIB-EXTRAS_MIN_VERSION}") mark_as_advanced(TAGLIB-EXTRAS_CFLAGS TAGLIB-EXTRAS_LIBRARIES TAGLIB-EXTRAS_INCLUDES) else(TAGLIB-EXTRASCONFIG_EXECUTABLE) find_path(TAGLIB-EXTRAS_INCLUDES NAMES tfile_helper.h PATH_SUFFIXES taglib-extras PATHS ${KDE4_INCLUDE_DIR} ${INCLUDE_INSTALL_DIR} ) IF(NOT WIN32) # on non-win32 we don't need to take care about WIN32_DEBUG_POSTFIX FIND_LIBRARY(TAGLIB-EXTRAS_LIBRARIES tag-extras PATHS ${KDE4_LIB_DIR} ${LIB_INSTALL_DIR}) ELSE(NOT WIN32) # 1. get all possible libnames SET(args PATHS ${KDE4_LIB_DIR} ${LIB_INSTALL_DIR}) SET(newargs "") SET(libnames_release "") SET(libnames_debug "") LIST(LENGTH args listCount) # just one name LIST(APPEND libnames_release "tag-extras") LIST(APPEND libnames_debug "tag-extrasd") SET(newargs ${args}) # search the release lib FIND_LIBRARY(TAGLIB-EXTRAS_LIBRARIES_RELEASE NAMES ${libnames_release} ${newargs} ) # search the debug lib FIND_LIBRARY(TAGLIB-EXTRAS_LIBRARIES_DEBUG NAMES ${libnames_debug} ${newargs} ) IF(TAGLIB-EXTRAS_LIBRARIES_RELEASE AND TAGLIB-EXTRAS_LIBRARIES_DEBUG) # both libs found SET(TAGLIB-EXTRAS_LIBRARIES optimized ${TAGLIB-EXTRAS_LIBRARIES_RELEASE} debug ${TAGLIB-EXTRAS_LIBRARIES_DEBUG}) ELSE(TAGLIB-EXTRAS_LIBRARIES_RELEASE AND TAGLIB-EXTRAS_LIBRARIES_DEBUG) IF(TAGLIB-EXTRAS_LIBRARIES_RELEASE) # only release found SET(TAGLIB-EXTRAS_LIBRARIES ${TAGLIB-EXTRAS_LIBRARIES_RELEASE}) ELSE(TAGLIB-EXTRAS_LIBRARIES_RELEASE) # only debug (or nothing) found SET(TAGLIB-EXTRAS_LIBRARIES ${TAGLIB-EXTRAS_LIBRARIES_DEBUG}) ENDIF(TAGLIB-EXTRAS_LIBRARIES_RELEASE) ENDIF(TAGLIB-EXTRAS_LIBRARIES_RELEASE AND TAGLIB-EXTRAS_LIBRARIES_DEBUG) MARK_AS_ADVANCED(TAGLIB-EXTRAS_LIBRARIES_RELEASE) MARK_AS_ADVANCED(TAGLIB-EXTRAS_LIBRARIES_DEBUG) ENDIF(NOT WIN32) INCLUDE(FindPackageMessage) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Taglib-Extras DEFAULT_MSG TAGLIB-EXTRAS_INCLUDES TAGLIB-EXTRAS_LIBRARIES) endif(TAGLIB-EXTRASCONFIG_EXECUTABLE) if(TAGLIB-EXTRAS_FOUND) if(NOT Taglib-Extras_FIND_QUIETLY AND TAGLIB-EXTRASCONFIG_EXECUTABLE) message(STATUS "Taglib-Extras found: ${TAGLIB-EXTRAS_LIBRARIES}") endif(NOT Taglib-Extras_FIND_QUIETLY AND TAGLIB-EXTRASCONFIG_EXECUTABLE) else(TAGLIB-EXTRAS_FOUND) if(Taglib-Extras_FIND_REQUIRED) message(FATAL_ERROR "Could not find Taglib-Extras") endif(Taglib-Extras_FIND_REQUIRED) endif(TAGLIB-EXTRAS_FOUND) cantata-2.2.0/cmake/FindTaglib.cmake000066400000000000000000000106471316350454000172240ustar00rootroot00000000000000# - Try to find the Taglib library # Once done this will define # # TAGLIB_FOUND - system has the taglib library # TAGLIB_CFLAGS - the taglib cflags # TAGLIB_LIBRARIES - The libraries needed to use taglib # Copyright (c) 2006, Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(CheckCXXSourceCompiles) if(NOT TAGLIB_MIN_VERSION) set(TAGLIB_MIN_VERSION "1.6") endif(NOT TAGLIB_MIN_VERSION) if(NOT WIN32) find_program(TAGLIBCONFIG_EXECUTABLE NAMES taglib-config PATHS ${BIN_INSTALL_DIR} ) endif(NOT WIN32) #reset vars set(TAGLIB_LIBRARIES) set(TAGLIB_CFLAGS) # if taglib-config has been found if(TAGLIBCONFIG_EXECUTABLE) exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_VERSION) if(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") message(STATUS "TagLib version too old: version searched :${TAGLIB_MIN_VERSION}, found ${TAGLIB_VERSION}") set(TAGLIB_FOUND FALSE) else(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_LIBRARIES) exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_CFLAGS) if(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS) set(TAGLIB_FOUND TRUE) endif(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS) string(REGEX REPLACE " *-I" ";" TAGLIB_INCLUDES "${TAGLIB_CFLAGS}") endif(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") mark_as_advanced(TAGLIB_CFLAGS TAGLIB_LIBRARIES TAGLIB_INCLUDES) else(TAGLIBCONFIG_EXECUTABLE) find_path(TAGLIB_INCLUDES NAMES tag.h PATH_SUFFIXES taglib PATHS ${KDE4_INCLUDE_DIR} ${INCLUDE_INSTALL_DIR} ) IF(NOT WIN32) # on non-win32 we don't need to take care about WIN32_DEBUG_POSTFIX FIND_LIBRARY(TAGLIB_LIBRARIES tag PATHS ${KDE4_LIB_DIR} ${LIB_INSTALL_DIR}) ELSE(NOT WIN32) # 1. get all possible libnames SET(args PATHS ${KDE4_LIB_DIR} ${LIB_INSTALL_DIR}) SET(newargs "") SET(libnames_release "") SET(libnames_debug "") LIST(LENGTH args listCount) # just one name LIST(APPEND libnames_release "tag") LIST(APPEND libnames_debug "tagd") SET(newargs ${args}) # search the release lib FIND_LIBRARY(TAGLIB_LIBRARIES_RELEASE NAMES ${libnames_release} ${newargs} ) # search the debug lib FIND_LIBRARY(TAGLIB_LIBRARIES_DEBUG NAMES ${libnames_debug} ${newargs} ) IF(TAGLIB_LIBRARIES_RELEASE AND TAGLIB_LIBRARIES_DEBUG) # both libs found SET(TAGLIB_LIBRARIES optimized ${TAGLIB_LIBRARIES_RELEASE} debug ${TAGLIB_LIBRARIES_DEBUG}) ELSE(TAGLIB_LIBRARIES_RELEASE AND TAGLIB_LIBRARIES_DEBUG) IF(TAGLIB_LIBRARIES_RELEASE) # only release found SET(TAGLIB_LIBRARIES ${TAGLIB_LIBRARIES_RELEASE}) ELSE(TAGLIB_LIBRARIES_RELEASE) # only debug (or nothing) found SET(TAGLIB_LIBRARIES ${TAGLIB_LIBRARIES_DEBUG}) ENDIF(TAGLIB_LIBRARIES_RELEASE) ENDIF(TAGLIB_LIBRARIES_RELEASE AND TAGLIB_LIBRARIES_DEBUG) MARK_AS_ADVANCED(TAGLIB_LIBRARIES_RELEASE) MARK_AS_ADVANCED(TAGLIB_LIBRARIES_DEBUG) ENDIF(NOT WIN32) INCLUDE(FindPackageMessage) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Taglib DEFAULT_MSG TAGLIB_INCLUDES TAGLIB_LIBRARIES) endif(TAGLIBCONFIG_EXECUTABLE) if(TAGLIB_FOUND) if(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE) message(STATUS "Taglib found: ${TAGLIB_LIBRARIES}") endif(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE) if(NOT WIN32) set(TAGLIB_INCLUDES "${TAGLIB_INCLUDES};/usr/local/include") endif() set(CMAKE_REQUIRED_INCLUDES ${TAGLIB_INCLUDES}) set(CMAKE_REQUIRED_LIBRARIES ${TAGLIB_LIBRARIES}) check_cxx_source_compiles("#include int main() { TagLib::MPEG::File *file; file->save(1, true, 3); return 0; }" TAGLIB_CAN_SAVE_ID3VER) else(TAGLIB_FOUND) if(Taglib_FIND_REQUIRED) message(FATAL_ERROR "Could not find Taglib") endif(Taglib_FIND_REQUIRED) endif(TAGLIB_FOUND) cantata-2.2.0/cmake/FindUDev.cmake000066400000000000000000000011531316350454000166550ustar00rootroot00000000000000# - Try to find UDev # Once done this will define # # UDEV_FOUND - system has UDev # UDEV_INCLUDE_DIR - the libudev include directory # UDEV_LIBS - The libudev libraries # Copyright (c) 2010, Rafael Fernández López, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. find_path(UDEV_INCLUDE_DIR libudev.h) find_library(UDEV_LIBS udev) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(UDev DEFAULT_MSG UDEV_INCLUDE_DIR UDEV_LIBS) mark_as_advanced(UDEV_INCLUDE_DIR UDEV_LIBS) cantata-2.2.0/cmake/MacroLogFeature.cmake000066400000000000000000000143011316350454000202270ustar00rootroot00000000000000# This file defines the Feature Logging macros. # # MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]]) # Logs the information so that it can be displayed at the end # of the configure run # VAR : TRUE or FALSE, indicating whether the feature is supported # FEATURE: name of the feature, e.g. "libjpeg" # DESCRIPTION: description what this feature provides # URL: home page # REQUIRED: TRUE or FALSE, indicating whether the featue is required # MIN_VERSION: minimum version number. empty string if unneeded # COMMENTS: More info you may want to provide. empty string if unnecessary # # MACRO_DISPLAY_FEATURE_LOG() # Call this to display the collected results. # Exits CMake with a FATAL error message if a required feature is missing # # Example: # # INCLUDE(MacroLogFeature) # # FIND_PACKAGE(JPEG) # MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "") # ... # MACRO_DISPLAY_FEATURE_LOG() # Copyright (c) 2006, Alexander Neundorf, # Copyright (c) 2006, Allen Winter, # Copyright (c) 2009, Sebastian Trueg, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. IF (NOT _macroLogFeatureAlreadyIncluded) SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_macroLogFeatureAlreadyIncluded TRUE) INCLUDE(FeatureSummary) ENDIF (NOT _macroLogFeatureAlreadyIncluded) MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments) STRING(TOUPPER "${ARGV4}" _required) SET(_minvers "${ARGV5}") SET(_comments "${ARGV6}") IF (${_var}) SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) ELSE (${_var}) IF ("${_required}" STREQUAL "TRUE") SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt) ELSE ("${_required}" STREQUAL "TRUE") SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) ENDIF ("${_required}" STREQUAL "TRUE") ENDIF (${_var}) SET(_logtext " * ${_package}") IF (NOT ${_var}) IF (${_minvers} MATCHES ".*") SET(_logtext "${_logtext} (${_minvers} or higher)") ENDIF (${_minvers} MATCHES ".*") SET(_logtext "${_logtext} <${_url}>\n ") ELSE (NOT ${_var}) SET(_logtext "${_logtext} - ") ENDIF (NOT ${_var}) SET(_logtext "${_logtext}${_description}") IF (NOT ${_var}) IF (${_comments} MATCHES ".*") SET(_logtext "${_logtext}\n ${_comments}") ENDIF (${_comments} MATCHES ".*") # SET(_logtext "${_logtext}\n") #double-space missing features? ENDIF (NOT ${_var}) FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n") IF(COMMAND SET_PACKAGE_PROPERTIES) SET_PACKAGE_PROPERTIES("${_package}" PROPERTIES DESCRIPTION "\"${_description}\"" URL "${_url}" PURPOSE PURPOSE"\"${_comments}\"") ELSEIF(COMMAND SET_PACKAGE_INFO) # in FeatureSummary.cmake since CMake 2.8.3 SET_PACKAGE_INFO("${_package}" "\"${_description}\"" "${_url}" "\"${_comments}\"") ENDIF(COMMAND SET_PACKAGE_PROPERTIES) ENDMACRO(MACRO_LOG_FEATURE) MACRO(MACRO_DISPLAY_FEATURE_LOG) IF(COMMAND FEATURE_SUMMARY) # in FeatureSummary.cmake since CMake 2.8.3 FEATURE_SUMMARY(FILENAME ${CMAKE_CURRENT_BINARY_DIR}/FindPackageLog.txt WHAT ALL) ENDIF(COMMAND FEATURE_SUMMARY) SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt) SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) SET(_printSummary TRUE) ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) IF(_printSummary) SET(_missingDeps 0) IF (EXISTS ${_enabledFile}) FILE(READ ${_enabledFile} _enabled) FILE(REMOVE ${_enabledFile}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}") ENDIF (EXISTS ${_enabledFile}) IF (EXISTS ${_disabledFile}) SET(_missingDeps 1) FILE(READ ${_disabledFile} _disabled) FILE(REMOVE ${_disabledFile}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}") ENDIF (EXISTS ${_disabledFile}) IF (EXISTS ${_missingFile}) SET(_missingDeps 1) FILE(READ ${_missingFile} _requirements) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}") FILE(REMOVE ${_missingFile}) SET(_haveMissingReq 1) ENDIF (EXISTS ${_missingFile}) IF (NOT ${_missingDeps}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.") ENDIF (NOT ${_missingDeps}) MESSAGE(${_summary}) MESSAGE("-----------------------------------------------------------------------------\n") IF(_haveMissingReq) MESSAGE(FATAL_ERROR "Exiting: Missing Requirements") ENDIF(_haveMissingReq) ENDIF(_printSummary) ENDMACRO(MACRO_DISPLAY_FEATURE_LOG) cantata-2.2.0/cmake/MacroOptionalFindPackage.cmake000066400000000000000000000036471316350454000220470ustar00rootroot00000000000000# - MACRO_OPTIONAL_FIND_PACKAGE() combines FIND_PACKAGE() with an OPTION() # MACRO_OPTIONAL_FIND_PACKAGE( [QUIT] ) # This macro is a combination of OPTION() and FIND_PACKAGE(), it # works like FIND_PACKAGE(), but additionally it automatically creates # an option name WITH_, which can be disabled via the cmake GUI. # or via -DWITH_=OFF # The standard _FOUND variables can be used in the same way # as when using the normal FIND_PACKAGE() # Copyright (c) 2006-2010 Alexander Neundorf, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # This is just a helper macro to set a bunch of variables empty. # We don't know whether the package uses UPPERCASENAME or CamelCaseName, so we try both: macro(_MOFP_SET_EMPTY_IF_DEFINED _name _var) if(DEFINED ${_name}_${_var}) set(${_name}_${_var} "") endif(DEFINED ${_name}_${_var}) string(TOUPPER ${_name} _nameUpper) if(DEFINED ${_nameUpper}_${_var}) set(${_nameUpper}_${_var} "") endif(DEFINED ${_nameUpper}_${_var}) endmacro(_MOFP_SET_EMPTY_IF_DEFINED _package _var) macro (MACRO_OPTIONAL_FIND_PACKAGE _name ) option(WITH_${_name} "Search for ${_name} package" ON) if (WITH_${_name}) find_package(${_name} ${ARGN}) else (WITH_${_name}) string(TOUPPER ${_name} _nameUpper) set(${_name}_FOUND FALSE) set(${_nameUpper}_FOUND FALSE) _mofp_set_empty_if_defined(${_name} INCLUDE_DIRS) _mofp_set_empty_if_defined(${_name} INCLUDE_DIR) _mofp_set_empty_if_defined(${_name} INCLUDES) _mofp_set_empty_if_defined(${_name} LIBRARY) _mofp_set_empty_if_defined(${_name} LIBRARIES) _mofp_set_empty_if_defined(${_name} LIBS) _mofp_set_empty_if_defined(${_name} FLAGS) _mofp_set_empty_if_defined(${_name} DEFINITIONS) endif (WITH_${_name}) endmacro (MACRO_OPTIONAL_FIND_PACKAGE) cantata-2.2.0/cmake_uninstall.cmake.in000066400000000000000000000030111316350454000177020ustar00rootroot00000000000000if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") function(rm_dir dir) if(EXISTS "${dir}" AND IS_DIRECTORY "${dir}") list(LENGTH ${dir} RES_LEN) if(RES_LEN EQUAL 0) exec_program(rmdir ARGS "${dir}" OUTPUT_VARIABLE OUT) endif() endif (EXISTS "${dir}" AND IS_DIRECTORY "${dir}") endfunction() file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif(NOT "${rm_retval}" STREQUAL 0) else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") #get_filename_component(dir $ENV{DESTDIR}${file} DIRECTORY) #rm_dir(${dir}) endforeach(file) file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_dirs" dirs) string(REGEX REPLACE "\n" ";" dirs "${dirs}") foreach(dir ${dirs}) rm_dir("${dir}") endforeach(dir) cantata-2.2.0/config.h.cmake000066400000000000000000000044731316350454000156340ustar00rootroot00000000000000#ifndef _CONFIG_H #define _CONFIG_H #include #include "support/utils.h" #if QT_VERSION >= 0x050600 #define DEVICE_PIXEL_RATIO devicePixelRatioF #else #define DEVICE_PIXEL_RATIO devicePixelRatio #endif #define CANTATA_MAKE_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c)) #define PACKAGE_NAME "@PROJECT_NAME@" #define ORGANIZATION_NAME "@ORGANIZATION_NAME@" #define PACKAGE_VERSION CANTATA_MAKE_VERSION(@CPACK_PACKAGE_VERSION_MAJOR@, @CPACK_PACKAGE_VERSION_MINOR@, @CPACK_PACKAGE_VERSION_PATCH@) #define PACKAGE_STRING PACKAGE_NAME" @CANTATA_VERSION_FULL@" #define PACKAGE_VERSION_STRING "@CANTATA_VERSION_WITH_SPIN@" #define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" #define SHARE_INSTALL_PREFIX "@SHARE_INSTALL_PREFIX@" #define ICON_INSTALL_PREFIX "@CANTATA_ICON_INSTALL_PREFIX@" #cmakedefine ENABLE_DEVICES_SUPPORT 1 #cmakedefine ENABLE_REMOTE_DEVICES 1 #cmakedefine COMPLEX_TAGLIB_FILENAME 1 #cmakedefine TAGLIB_FOUND 1 #cmakedefine TAGLIB_EXTRAS_FOUND 1 #cmakedefine TAGLIB_ASF_FOUND #cmakedefine TAGLIB_MP4_FOUND 1 #cmakedefine TAGLIB_OPUS_FOUND 1 #cmakedefine MTP_FOUND 1 #cmakedefine ENABLE_HTTP_STREAM_PLAYBACK 1 #cmakedefine ENABLE_KWALLET 1 #cmakedefine FFMPEG_FOUND 1 #cmakedefine MPG123_FOUND 1 #cmakedefine CDDB_FOUND 1 #cmakedefine MUSICBRAINZ5_FOUND 1 #cmakedefine ENABLE_REPLAYGAIN_SUPPORT 1 #cmakedefine TAGLIB_CAN_SAVE_ID3VER 1 #cmakedefine ENABLE_PROXY_CONFIG 1 #cmakedefine CDPARANOIA_HAS_CACHEMODEL_SIZE 1 #cmakedefine CDIOPARANOIA_FOUND 1 #cmakedefine QT_QTDBUS_FOUND 1 #cmakedefine ENABLE_HTTP_SERVER 1 #cmakedefine IOKIT_FOUND 1 #cmakedefine QT_MAC_EXTRAS_FOUND 1 #cmakedefine ENABLE_SIMPLE_MPD_SUPPORT 1 #cmakedefine HAVE_CDIO_PARANOIA_H 1 #cmakedefine HAVE_CDIO_PARANOIA_PARANOIA_H 1 #cmakedefine HAVE_CDIO_CDDA_H 1 #cmakedefine HAVE_CDIO_PARANOIA_CDDA_H 1 #define CANTATA_REV_URL "@PROJECT_REV_URL@" #define CANTATA_URL "@PROJECT_URL@" #define CANTATA_SYS_CONFIG_DIR Utils::systemDir(QLatin1String("config")) #define CANTATA_SYS_ICONS_DIR Utils::systemDir(QLatin1String("icons")) #define CANTATA_SYS_MPD_DIR Utils::systemDir(QLatin1String("mpd")) #define CANTATA_SYS_TRANS_DIR Utils::systemDir(QLatin1String("translations")) #define CANTATA_SYS_SCRIPTS_DIR Utils::systemDir(QLatin1String("scripts")) #define LINUX_LIB_DIR "@LINUX_LIB_DIR@" #define CANTATA_ICON_THEME "@CANTATA_ICON_THEME@" #endif cantata-2.2.0/context/000077500000000000000000000000001316350454000146135ustar00rootroot00000000000000cantata-2.2.0/context/albumview.cpp000066400000000000000000000221021316350454000173070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "albumview.h" #include "artistview.h" #include "gui/covers.h" #include "network/networkaccessmanager.h" #include "support/utils.h" #include "qtiocompressor/qtiocompressor.h" #include "contextengine.h" #include "widgets/textbrowser.h" #include "widgets/icons.h" #include "support/actioncollection.h" #include "support/action.h" #include "models/mpdlibrarymodel.h" #include #include #include #include #include #include #include #include const QLatin1String AlbumView::constCacheDir("albums/"); const QLatin1String AlbumView::constInfoExt(".html.gz"); static QString cacheFileName(const QString &artist, const QString &album, const QString &lang, bool createDir) { return Utils::cacheDir(AlbumView::constCacheDir, createDir)+Covers::encodeName(artist)+QLatin1String(" - ")+Covers::encodeName(album)+"."+lang+AlbumView::constInfoExt; } enum Parts { Cover = 0x01, Details = 0x02, All = Cover+Details }; AlbumView::AlbumView(QWidget *p) : View(p) , detailsReceived(0) { engine=ContextEngine::create(this); refreshAction = ActionCollection::get()->createAction("refreshalbum", tr("Refresh Album Information"), Icons::self()->refreshIcon); connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh())); connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(searchResponse(QString,QString))); connect(Covers::self(), SIGNAL(cover(Song,QImage,QString)), SLOT(coverRetrieved(Song,QImage,QString))); connect(Covers::self(), SIGNAL(coverUpdated(Song,QImage,QString)), SLOT(coverUpdated(Song,QImage,QString))); connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(playSong(QUrl))); text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); setStandardHeader(tr("Album")); int imageSize=fontMetrics().height()*18; setPicSize(QSize(imageSize, imageSize)); clear(); if (ArtistView::constCacheAge>0) { clearCache(); QTimer *timer=new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(clearCache())); timer->start((int)((ArtistView::constCacheAge/2.0)*1000*24*60*60)); } } void AlbumView::showContextMenu(const QPoint &pos) { QMenu *menu = text->createStandardContextMenu(); menu->addSeparator(); if (cancelJobAction->isEnabled()) { menu->addAction(cancelJobAction); } else { menu->addAction(refreshAction); } menu->exec(text->mapToGlobal(pos)); delete menu; } void AlbumView::refresh() { if (currentSong.isEmpty()) { return; } foreach (const QString &lang, engine->getLangs()) { QFile::remove(cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, engine->getPrefix(lang), false)); } update(currentSong, true); } void AlbumView::update(const Song &song, bool force) { QString streamName=song.isStandardStream() && song.album.isEmpty() ? song.name() : QString(); if (!streamName.isEmpty() && streamName!=currentSong.name()) { abort(); currentSong=song; clearDetails(); setHeader(streamName); needToUpdate=false; detailsReceived=All; pic=createPicTag(QImage(), CANTATA_SYS_ICONS_DIR+QLatin1String("stream.png")); updateDetails(); return; } if (song.isEmpty() || song.albumArtist().isEmpty() || song.album.isEmpty()) { currentSong=song; clearDetails(); abort(); return; } if (force || song.albumArtist()!=currentSong.albumArtist() || song.album!=currentSong.album) { currentSong=song; currentArtist=currentSong.basicArtist(); abort(); if (!isVisible()) { needToUpdate=true; return; } clearDetails(); setHeader(song.album.isEmpty() ? stdHeader : song.album); Covers::Image cImg=Covers::self()->requestImage(song, true); if (!cImg.img.isNull()) { detailsReceived|=Cover; pic=createPicTag(cImg.img, cImg.fileName); } getTrackListing(); getDetails(); if (All==detailsReceived) { hideSpinner(); } else { showSpinner(); } } else if (song.title!=currentSong.title) { currentSong=song; getTrackListing(); updateDetails(true); } } void AlbumView::playSong(const QUrl &url) { if (QLatin1String("cantata")==url.scheme()) { emit playSong(url.path().mid(1)); // Remove leading / } else { QDesktopServices::openUrl(url); } } void AlbumView::getTrackListing() { if (songs.isEmpty()) { songs=MpdLibraryModel::self()->getAlbumTracks(currentSong); } if (!songs.isEmpty()) { trackList=View::subHeader(tr("Tracks"))+QLatin1String("

"); foreach (const Song &s, songs) { trackList+=QLatin1String(""); } trackList+=QLatin1String("
")+QString::number(s.track)+ QLatin1String(""+ (s.file==currentSong.file ? ""+s.displayTitle()+"" : s.displayTitle())+QLatin1String("

"); updateDetails(); } } void AlbumView::getDetails() { engine->cancel(); foreach (const QString &lang, engine->getLangs()) { QString prefix=engine->getPrefix(lang); QString cachedFile=cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, prefix, false); if (QFile::exists(cachedFile)) { QFile f(cachedFile); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { QByteArray data=compressor.readAll(); if (!data.isEmpty()) { searchResponse(QString::fromUtf8(data), QString()); Utils::touchFile(cachedFile); return; } } } } engine->search(QStringList() << currentSong.albumArtist() << currentSong.album, ContextEngine::Album); } void AlbumView::coverRetrieved(const Song &s, const QImage &img, const QString &file) { if (!s.isArtistImageRequest() && (s==currentSong && pic.isEmpty())) { detailsReceived|=Cover; if (All==detailsReceived) { hideSpinner(); } pic=createPicTag(img, file); if (!pic.isEmpty()) { updateDetails(); } } } void AlbumView::coverUpdated(const Song &s, const QImage &img, const QString &file) { if (!s.isArtistImageRequest() && s==currentSong) { detailsReceived|=Cover; if (All==detailsReceived) { hideSpinner(); } pic=createPicTag(img, file); if (!pic.isEmpty()) { updateDetails(); } } } void AlbumView::searchResponse(const QString &resp, const QString &lang) { detailsReceived|=Details; if (All==detailsReceived) { hideSpinner(); } if (!resp.isEmpty()) { details=engine->translateLinks(resp); if (!lang.isEmpty()) { QFile f(cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, lang, true)); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { compressor.write(resp.toUtf8().constData()); } } updateDetails(); } } void AlbumView::updateDetails(bool preservePos) { int pos=preservePos ? text->verticalScrollBar()->value() : 0; if (!details.isEmpty()) { setHtml(pic+"
"+details+"
"+trackList); } else { setHtml(pic+trackList); } if (preservePos) { text->verticalScrollBar()->setValue(pos); } } void AlbumView::abort() { engine->cancel(); hideSpinner(); } void AlbumView::clearCache() { Utils::clearOldCache(constCacheDir, ArtistView::constCacheAge); } void AlbumView::clearDetails() { details.clear(); trackList.clear(); bio.clear(); pic.clear(); songs.clear(); clear(); engine->cancel(); detailsReceived=0; } cantata-2.2.0/context/albumview.h000066400000000000000000000040531316350454000167610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALBUM_VIEW_H #define ALBUM_VIEW_H #include "view.h" #include class QImage; class NetworkJob; class QByteArray; class QUrl; class ContextEngine; class Action; class AlbumView : public View { Q_OBJECT public: static const QLatin1String constCacheDir; static const QLatin1String constInfoExt; AlbumView(QWidget *p); void update(const Song &song, bool force=false); Q_SIGNALS: void playSong(const QString &file); public Q_SLOTS: void coverRetrieved(const Song &s, const QImage &img, const QString &file); void coverUpdated(const Song &s, const QImage &img, const QString &file); void playSong(const QUrl &u); private Q_SLOTS: void showContextMenu(const QPoint &pos); void refresh(); void clearCache(); void searchResponse(const QString &resp, const QString &lang); private: void clearDetails(); void getTrackListing(); void getDetails(); void updateDetails(bool preservePos=false); void abort(); private: QString currentArtist; Action *refreshAction; ContextEngine *engine; int detailsReceived; QString pic; QString details; QString trackList; QString bioArtist; QString bio; QList songs; }; #endif cantata-2.2.0/context/artistview.cpp000066400000000000000000000346521316350454000175320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "artistview.h" #include "gui/covers.h" #include "support/utils.h" #include "network/networkaccessmanager.h" #include "qtiocompressor/qtiocompressor.h" #include "widgets/textbrowser.h" #include "widgets/icons.h" #include "contextengine.h" #include "support/actioncollection.h" #include "support/action.h" #include "models/mpdlibrarymodel.h" #include #include #include #include #include #include #include #include #include #include static const char *constNameKey="name"; const int ArtistView::constCacheAge=0; // 0 => dont automatically clean cache const QLatin1String ArtistView::constCacheDir("artists/"); const QLatin1String ArtistView::constInfoExt(".html.gz"); const QLatin1String ArtistView::constSimilarInfoExt(".txt"); static QString cacheFileName(const QString &artist, const QString &lang, bool similar, bool createDir) { return Utils::cacheDir(ArtistView::constCacheDir, createDir)+ Covers::encodeName(artist)+(similar ? "-similar" : ("."+lang))+(similar ? ArtistView::constSimilarInfoExt : ArtistView::constInfoExt); } static QString buildUrl(const LibraryDb::Album &al) { QUrl url("cantata:///"); QUrlQuery query; query.addQueryItem("artist", al.artist); query.addQueryItem("albumId", al.id); url.setQuery(query); return url.toString(); } static QString buildUrl(const QString &artist) { QUrl url("cantata:///"); QUrlQuery query; query.addQueryItem("artist", artist); url.setQuery(query); return url.toString(); } static QString checkHaveArtist(const QSet &mpdArtists, const QString &artist) { if (mpdArtists.contains(artist)) { return QLatin1String("")+artist+QLatin1String(""); } else { // Check for AC/DC -> AC-DC QString mod=artist; mod=mod.replace("/", "-"); if (mod!=artist && mpdArtists.contains(mod)) { return QLatin1String("")+artist+QLatin1String(""); } } return QString(); } ArtistView::ArtistView(QWidget *parent) : View(parent) , currentSimilarJob(0) { engine=ContextEngine::create(this); refreshAction = ActionCollection::get()->createAction("refreshartist", tr("Refresh Artist Information"), Icons::self()->refreshIcon); connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh())); connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(searchResponse(QString,QString))); connect(Covers::self(), SIGNAL(artistImage(Song,QImage,QString)), SLOT(artistImage(Song,QImage,QString))); connect(Covers::self(), SIGNAL(coverUpdated(Song,QImage,QString)), SLOT(artistImageUpdated(Song,QImage,QString))); connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(show(QUrl))); text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); setStandardHeader(tr("Artist")); int imageHeight=fontMetrics().height()*14; int imageWidth=imageHeight*1.5; setPicSize(QSize(imageWidth, imageHeight)); clear(); if (constCacheAge>0) { clearCache(); QTimer *timer=new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(clearCache())); timer->start((int)((constCacheAge/2.0)*1000*24*60*60)); } } void ArtistView::showContextMenu(const QPoint &pos) { QMenu *menu = text->createStandardContextMenu(); menu->addSeparator(); if (cancelJobAction->isEnabled()) { menu->addAction(cancelJobAction); } else { menu->addAction(refreshAction); } menu->exec(text->mapToGlobal(pos)); delete menu; } void ArtistView::refresh() { if (currentSong.isEmpty()) { return; } foreach (const QString &lang, engine->getLangs()) { QFile::remove(cacheFileName(currentSong.artist, engine->getPrefix(lang), false, false)); } QFile::remove(cacheFileName(currentSong.artist, QString(), true, false)); update(currentSong, true); } void ArtistView::update(const Song &s, bool force) { if (s.isEmpty() || s.artist.isEmpty()) { currentSong=s; engine->cancel(); clear(); abort(); return; } Song song=s; song.artist=song.basicArtist(); bool artistChanged=song.artist!=currentSong.artist; if (artistChanged) { artistAlbums.clear(); abort(); } if (!isVisible()) { if (artistChanged) { needToUpdate=true; } currentSong=song; return; } if (artistChanged || force) { currentSong=song; clear(); pic.clear(); biography.clear(); albums.clear(); similarArtists=QString(); if (!currentSong.isEmpty()) { setHeader(currentSong.artist); Song s; s.setArtistImageRequest(); s.albumartist=currentSong.artist; if (!currentSong.isVariousArtists()) { s.file=currentSong.file; } Covers::Image img=Covers::self()->requestImage(s, true); if (!img.img.isNull()) { pic=createPicTag(img.img, img.fileName); } loadBio(); } } } const QList & ArtistView::getArtistAlbums() { if (artistAlbums.isEmpty() && !currentSong.isEmpty()) { artistAlbums=MpdLibraryModel::self()->getArtistAlbums(currentSong.artist); } return artistAlbums; } void ArtistView::artistImage(const Song &song, const QImage &i, const QString &f) { if (song.isArtistImageRequest() && song.albumartist==currentSong.artist && pic.isEmpty()) { pic=createPicTag(i, f); setBio(); } } void ArtistView::artistImageUpdated(const Song &song, const QImage &i, const QString &f) { if (song.isArtistImageRequest() && song.albumartist==currentSong.artist) { pic=createPicTag(i, f); setBio(); } } void ArtistView::loadBio() { foreach (const QString &lang, engine->getLangs()) { QString prefix=engine->getPrefix(lang); QString cachedFile=cacheFileName(currentSong.artist, prefix, false, false); if (QFile::exists(cachedFile)) { QFile f(cachedFile); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { QString data=QString::fromUtf8(compressor.readAll()); if (!data.isEmpty()) { searchResponse(data, QString()); Utils::touchFile(cachedFile); return; } } } } showSpinner(); engine->search(QStringList() << currentSong.artist, ContextEngine::Artist); } void ArtistView::loadSimilar() { QString cachedFile=cacheFileName(currentSong.artist, QString(), true, false); if (QFile::exists(cachedFile)) { QFile f(cachedFile); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { QStringList artists; while (!f.atEnd()) { QString artist=QString::fromUtf8(f.readLine()); if (!artist.isEmpty()) { artists.append(artist.trimmed()); } } if (!artists.isEmpty()) { buildSimilar(artists); setBio(); Utils::touchFile(cachedFile); return; } } } requestSimilar(); } void ArtistView::handleSimilarReply() { NetworkJob *reply = qobject_cast(sender()); if (!reply) { return; } if (reply==currentSimilarJob) { if (reply->ok()) { QByteArray data=reply->readAll(); QStringList artists=parseSimilarResponse(data); if (!artists.isEmpty()) { buildSimilar(artists); setBio(); QFile f(cacheFileName(reply->property(constNameKey).toString(), QString(), true, true)); if (f.open(QIODevice::WriteOnly|QIODevice::Text)) { QTextStream stream(&f); foreach (const QString &artist, artists) { stream << artist << endl; } } } } else { setBio(); } reply->deleteLater(); currentSimilarJob=0; } } void ArtistView::setBio() { QString html=pic+"
"+biography; if (!similarArtists.isEmpty()) { html+=similarArtists; } if (albums.isEmpty()) { getArtistAlbums(); foreach (const LibraryDb::Album &al, artistAlbums) { albums+=QLatin1String("
  • "+al.name+"
  • "; } } if (!albums.isEmpty()) { html+=View::subHeader(tr("Albums"))+QLatin1String("
      ")+albums+QLatin1String("
    "); } if (webLinks.isEmpty()) { QFile file(CANTATA_SYS_CONFIG_DIR+"weblinks.xml"); if (file.open(QIODevice::ReadOnly)) { QXmlStreamReader reader(&file); while (!reader.atEnd()) { reader.readNext(); if (QLatin1String("link")==reader.name()) { QXmlStreamAttributes attributes = reader.attributes(); QString url=attributes.value("url").toString(); QString name=attributes.value("name").toString(); if (!url.isEmpty() && !name.isEmpty()) { webLinks+=QLatin1String("
  • "+name+"
  • "; } } } } } if (!webLinks.isEmpty()) { QString artist=currentSong.artist; artist.replace(QLatin1Char('&'), QLatin1String("%26")); artist.replace(QLatin1Char('?'), QLatin1String("%3f")); html+=View::subHeader(tr("Web Links"))+QLatin1String("
      ")+QString(webLinks).replace("${artist}", artist)+QLatin1String("
    "); } setHtml(html); } void ArtistView::requestSimilar() { abort(); QUrl url("http://ws.audioscrobbler.com/2.0/"); QUrlQuery query; query.addQueryItem("method", "artist.getSimilar"); query.addQueryItem("api_key", Covers::constLastFmApiKey); query.addQueryItem("autocorrect", "1"); query.addQueryItem("artist", Covers::fixArtist(currentSong.artist)); url.setQuery(query); currentSimilarJob=NetworkAccessManager::self()->get(url); currentSimilarJob->setProperty(constNameKey, currentSong.artist); connect(currentSimilarJob, SIGNAL(finished()), this, SLOT(handleSimilarReply())); } void ArtistView::abort() { engine->cancel(); if (currentSimilarJob) { currentSimilarJob->cancelAndDelete(); currentSimilarJob=0; } hideSpinner(); } void ArtistView::searchResponse(const QString &resp, const QString &lang) { biography=engine->translateLinks(resp); hideSpinner(); if (!resp.isEmpty() && !lang.isEmpty()) { QFile f(cacheFileName(currentSong.artist, lang, false, true)); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { compressor.write(resp.toUtf8().constData()); } } loadSimilar(); setBio(); } QStringList ArtistView::parseSimilarResponse(const QByteArray &resp) { QStringList artists; QXmlStreamReader doc(resp); bool inSection=false; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (!inSection && QLatin1String("artist")==doc.name()) { inSection=true; } else if (inSection && QLatin1String("name")==doc.name()) { artists.append(doc.readElementText()); } } else if (doc.isEndElement() && inSection && QLatin1String("artist")==doc.name()) { inSection=false; } } qSort(artists); return artists; } void ArtistView::buildSimilar(const QStringList &artists) { QSet mpdArtists=MpdLibraryModel::self()->getArtists(); QSet mpdLowerArtists; bool first=true; foreach (QString artist, artists) { if (similarArtists.isEmpty()) { similarArtists=QLatin1String("
    ")+View::subHeader(tr("Similar Artists")); } // Check if we have artist in collection... QString artistLink=checkHaveArtist(mpdArtists, artist); if (artistLink.isEmpty()) { // ...and check case-insensitively... if (mpdLowerArtists.isEmpty()) { foreach (const QString &a, mpdArtists) { mpdLowerArtists.insert(a.toLower()); } } artistLink=checkHaveArtist(mpdLowerArtists, artist.toLower()); } if (!artistLink.isEmpty()) { artist=artistLink; } if (first) { first=false; } else { similarArtists+=", "; } similarArtists+=artist; } if (!similarArtists.isEmpty()) { similarArtists+="
    "; } } void ArtistView::show(const QUrl &url) { if (QLatin1String("cantata")==url.scheme()) { QUrlQuery q(url); if (q.hasQueryItem("artist")) { if (q.hasQueryItem("albumId")) { emit findAlbum(q.queryItemValue("artist"), q.queryItemValue("albumId")); } else { emit findArtist(q.queryItemValue("artist")); } } } else { QDesktopServices::openUrl(url); } } void ArtistView::clearCache() { Utils::clearOldCache(constCacheDir, constCacheAge); } cantata-2.2.0/context/artistview.h000066400000000000000000000050161316350454000171670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ARTIST_VIEW_H #define ARTIST_VIEW_H #include "view.h" #include "mpd-interface/song.h" #include "db/librarydb.h" #include class ComboBox; class QLabel; class NetworkJob; class QIODevice; class QImage; class QUrl; class ContextEngine; class Action; class ArtistView : public View { Q_OBJECT public: static const int constCacheAge; static const QLatin1String constCacheDir; static const QLatin1String constInfoExt; static const QLatin1String constSimilarInfoExt; ArtistView(QWidget *parent); virtual ~ArtistView() { abort(); } void update(const Song &s, bool force=false); const QList &getArtistAlbums(); Q_SIGNALS: void findArtist(const QString &artist); void findAlbum(const QString &artist, const QString &album); public Q_SLOTS: void artistImage(const Song &song, const QImage &i, const QString &f); void artistImageUpdated(const Song &song, const QImage &i, const QString &f); private Q_SLOTS: void showContextMenu(const QPoint &pos); void refresh(); void setBio(); void handleSimilarReply(); void show(const QUrl &url); void clearCache(); void searchResponse(const QString &resp, const QString &lang); private: void loadBio(); void loadSimilar(); void requestSimilar(); QStringList parseSimilarResponse(const QByteArray &resp); void buildSimilar(const QStringList &artists); void abort(); private: Action *refreshAction; ContextEngine *engine; QString pic; QString biography; QString similarArtists; NetworkJob *currentSimilarJob; QString provider; QString webLinks; QString albums; QList artistAlbums; }; #endif cantata-2.2.0/context/contextengine.cpp000066400000000000000000000040101316350454000201640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "contextengine.h" #include "metaengine.h" #include "wikipediaengine.h" #include "network/networkaccessmanager.h" ContextEngine * ContextEngine::create(QObject *parent) { return new MetaEngine(parent); } ContextEngine::ContextEngine(QObject *p) : QObject(p) , job(0) { } ContextEngine::~ContextEngine() { cancel(); } QStringList ContextEngine::fixQuery(const QStringList &query) const { QStringList fixedQuery; foreach (QString q, query) { if (q.contains(QLatin1String("PREVIEW: buy it at www.magnatune.com"))) { q = q.remove(QLatin1String(" (PREVIEW: buy it at www.magnatune.com)")); int index = q.indexOf(QLatin1Char('-')); if (-1!=index) { q = q.left(index - 1); } } fixedQuery.append(q); } return fixedQuery; } void ContextEngine::cancel() { if (job) { job->cancelAndDelete(); job=0; } } NetworkJob * ContextEngine::getReply(QObject *obj) { NetworkJob *reply = qobject_cast(obj); if (!reply) { return 0; } reply->deleteLater(); if (reply!=job) { return 0; } job=0; return reply; } cantata-2.2.0/context/contextengine.h000066400000000000000000000032731316350454000176430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CONTEXT_ENGINE_H #define CONTEXT_ENGINE_H #include #include class NetworkJob; class ContextEngine : public QObject { Q_OBJECT public: enum Mode { Artist, Album, Track }; static ContextEngine * create(QObject *parent); ContextEngine(QObject *p); virtual ~ContextEngine(); virtual QString translateLinks(QString text) const =0; virtual QStringList getLangs() const =0; virtual QString getPrefix(const QString &key) const =0; QStringList fixQuery(const QStringList &query) const; void cancel(); public Q_SLOTS: virtual void search(const QStringList &query, Mode mode)=0; Q_SIGNALS: void searchResult(const QString &html, const QString &lang); protected: NetworkJob * getReply(QObject *obj); protected: NetworkJob *job; }; #endif cantata-2.2.0/context/contextsettings.cpp000066400000000000000000000026471316350454000205750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "contextsettings.h" #include "wikipediasettings.h" #include "lyricsettings.h" #include "othersettings.h" ContextSettings::ContextSettings(QWidget *p) : QTabWidget(p) { wiki=new WikipediaSettings(this); lyrics=new LyricSettings(this); other=new OtherSettings(this); addTab(lyrics, tr("Lyrics Providers")); addTab(wiki, tr("Wikipedia Languages")); addTab(other, tr("Other")); } void ContextSettings::load() { wiki->load(); lyrics->load(); other->load(); } void ContextSettings::save() { wiki->save(); lyrics->save(); other->save(); } cantata-2.2.0/context/contextsettings.h000066400000000000000000000023671316350454000202410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CONTEXTSETTINGS_H #define CONTEXTSETTINGS_H #include class WikipediaSettings; class LyricSettings; class OtherSettings; class ContextSettings : public QTabWidget { Q_OBJECT public: ContextSettings(QWidget *p=0); virtual ~ContextSettings() { } void load(); void save(); private: WikipediaSettings *wiki; LyricSettings *lyrics; OtherSettings *other; }; #endif cantata-2.2.0/context/contextwidget.cpp000066400000000000000000000732661316350454000202250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "contextwidget.h" #include "artistview.h" #include "albumview.h" #include "songview.h" #include "onlineview.h" #include "mpd-interface/song.h" #include "support/utils.h" #include "gui/covers.h" #include "network/networkaccessmanager.h" #include "gui/settings.h" #include "wikipediaengine.h" #include "support/gtkstyle.h" #include "widgets/playqueueview.h" #include "widgets/treeview.h" #include "widgets/thinsplitterhandle.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Exported by QtGui void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void ContextWidget::enableDebug() { debugEnabled=true; } const QLatin1String ContextWidget::constBackdropFileName("backdrop"); //const QLatin1String ContextWidget::constHtbApiKey(0); // API key required const QLatin1String ContextWidget::constFanArtApiKey("ee86404cb429fa27ac32a1a3c117b006"); const QLatin1String ContextWidget::constCacheDir("backdrops/"); static QString cacheFileName(const QString &artist, bool createDir) { return Utils::cacheDir(ContextWidget::constCacheDir, createDir)+Covers::encodeName(artist)+".jpg"; } class ViewSelectorButton : public QToolButton { public: ViewSelectorButton(QWidget *p) : QToolButton(p) { } void paintEvent(QPaintEvent *ev) { Q_UNUSED(ev) QPainter painter(this); QString txt=text(); bool mo=underMouse(); txt.replace("&", ""); QFont f(font()); if (isChecked()) { f.setBold(true); } QFontMetrics fm(f); if (fm.width(txt)>rect().width()) { txt=fm.elidedText(txt, isRightToLeft() ? Qt::ElideLeft : Qt::ElideRight, rect().width()); } painter.setFont(f); if (isChecked() || mo) { int lh=Utils::isHighDpi() ? 5 : 3; #ifdef Q_OS_MAC QColor col=OSXStyle::self()->viewPalette().highlight().color(); #else QColor col=palette().color(QPalette::Highlight); #endif if (mo) { col.setAlphaF(isChecked() ? 0.75 : 0.5); } QRect r=rect(); painter.fillRect(r.x(), r.y()+r.height()-(lh+1), r.width(), lh, col); } painter.setPen(palette().color(QPalette::Text)); painter.drawText(rect(), Qt::AlignCenter, txt); } }; static const char *constDataProp="view-data"; ViewSelector::ViewSelector(QWidget *p) : QWidget(p) { group=new QButtonGroup(this); } void ViewSelector::addItem(const QString &label, const QVariant &data) { QHBoxLayout *l; if (buttons.isEmpty()) { l = new QHBoxLayout(this); l->setMargin(0); l->setSpacing(0); } else { l=static_cast(layout()); } QToolButton *button=new ViewSelectorButton(this); button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); button->setAutoRaise(true); button->setText(label); button->setCheckable(true); button->setProperty(constDataProp, data); connect(button, SIGNAL(toggled(bool)), this, SLOT(buttonActivated())); buttons.append(button); group->addButton(button); l->addWidget(button); } void ViewSelector::buttonActivated() { QToolButton *button=qobject_cast(sender()); if (button && button->isChecked()) { emit activated(buttons.indexOf(button)); } } QVariant ViewSelector::itemData(int index) const { return index>=0 && indexproperty(constDataProp) : QVariant(); } int ViewSelector::currentIndex() const { for (int i=0; iisChecked()) { return i; } } return -1; } void ViewSelector::setCurrentIndex(int index) { QFont f(font()); for (int i=0; iisChecked(); btn->setChecked(i==index); if (i==index) { emit activated(i); } else if (wasChecked) { btn->setFont(f); } } } void ViewSelector::wheelEvent(QWheelEvent *ev) { int numDegrees = ev->delta() / 8; int numSteps = numDegrees / 15; if (numSteps > 0) { for (int i = 0; i < numSteps; ++i) { int index=currentIndex(); setCurrentIndex(index==count()-1 ? 0 : index+1); } } else { for (int i = 0; i > numSteps; --i) { int index=currentIndex(); setCurrentIndex(index==0 ? count()-1 : index-1); } } } void ViewSelector::paintEvent(QPaintEvent *ev) { Q_UNUSED(ev) } ThinSplitter::ThinSplitter(QWidget *parent) : QSplitter(parent) { setChildrenCollapsible(true); setOrientation(Qt::Horizontal); resetAct=new QAction(tr("Reset Spacing"), this); connect(resetAct, SIGNAL(triggered()), this, SLOT(reset())); setHandleWidth(0); } QSplitterHandle * ThinSplitter::createHandle() { ThinSplitterHandle *handle=new ThinSplitterHandle(orientation(), this); handle->addAction(resetAct); handle->setContextMenuPolicy(Qt::ActionsContextMenu); handle->setHighlightUnderMouse(); return handle; } void ThinSplitter::reset() { int totalSize=0; foreach (int s, sizes()) { totalSize+=s; } QList newSizes; int size=totalSize/count(); for (int i=0; iaddWidget(standardContext); layout->setMargin(0); layout->addWidget(mainStack); animator.setPropertyName("fade"); animator.setTargetObject(this); appLinkColor=QApplication::palette().color(QPalette::Link); artist = new ArtistView(standardContext); album = new AlbumView(standardContext); song = new SongView(standardContext); minWidth=album->picSize().width()*2.5; artist->addEventFilter(this); album->addEventFilter(this); song->addEventFilter(this); connect(artist, SIGNAL(findArtist(QString)), this, SIGNAL(findArtist(QString))); connect(artist, SIGNAL(findAlbum(QString,QString)), this, SIGNAL(findAlbum(QString,QString))); connect(album, SIGNAL(playSong(QString)), this, SIGNAL(playSong(QString))); readConfig(); setZoom(); setWide(true); } void ContextWidget::setZoom() { int zoom=Settings::self()->contextZoom(); if (zoom) { artist->setZoom(zoom); album->setZoom(zoom); song->setZoom(zoom); } } void ContextWidget::setWide(bool w) { if (w==isWide) { return; } isWide=w; if (w) { if (standardContext->layout()) { delete standardContext->layout(); } QHBoxLayout *l=new QHBoxLayout(standardContext); standardContext->setLayout(l); int m=l->margin()/2; l->setMargin(0); if (stack) { stack->setVisible(false); viewSelector->setVisible(false); stack->removeWidget(artist); stack->removeWidget(album); stack->removeWidget(song); artist->setVisible(true); album->setVisible(true); song->setVisible(true); } l->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed)); QByteArray state; bool resetSplitter=splitter; if (!splitter) { splitter=new ThinSplitter(standardContext); state=Settings::self()->contextSplitterState(); } l->addWidget(splitter); artist->setParent(splitter); album->setParent(splitter); song->setParent(splitter); splitter->addWidget(artist); splitter->addWidget(album); splitter->setVisible(true); splitter->addWidget(song); if (resetSplitter) { splitter->reset(); } else if (!state.isEmpty()) { splitter->restoreState(state); } } else { if (standardContext->layout()) { delete standardContext->layout(); } QGridLayout *l=new QGridLayout(standardContext); standardContext->setLayout(l); int m=l->margin()/2; l->setMargin(0); l->setSpacing(0); if (!stack) { stack=new QStackedWidget(standardContext); } if (!viewSelector) { viewSelector=new ViewSelector(standardContext); viewSelector->addItem(tr("&Artist"), "artist"); viewSelector->addItem(tr("Al&bum"), "album"); viewSelector->addItem(tr("&Track"), "song"); viewSelector->setPalette(palette()); connect(viewSelector, SIGNAL(activated(int)), stack, SLOT(setCurrentIndex(int))); } if (splitter) { splitter->setVisible(false); } stack->setVisible(true); viewSelector->setVisible(true); artist->setParent(stack); album->setParent(stack); song->setParent(stack); stack->addWidget(artist); stack->addWidget(album); stack->addWidget(song); l->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed), 0, 0, 1, 1); l->addWidget(stack, 0, 1, 1, 1); l->addWidget(viewSelector, 1, 0, 1, 2); QString lastSaved=Settings::self()->contextSlimPage(); if (!lastSaved.isEmpty()) { for (int i=0; icount(); ++i) { if (viewSelector->itemData(i).toString()==lastSaved) { viewSelector->setCurrentIndex(i); stack->setCurrentIndex(i); break; } } } } } void ContextWidget::resizeEvent(QResizeEvent *e) { if (isVisible()) { setWide(width()>minWidth && !alwaysCollapsed); } resizeBackdrop(); QWidget::resizeEvent(e); } void ContextWidget::readConfig() { int origOpacity=backdropOpacity; int origBlur=backdropBlur; QString origCustomBackdropFile=customBackdropFile; int origType=backdropType; backdropType=Settings::self()->contextBackdrop(); backdropOpacity=Settings::self()->contextBackdropOpacity(); backdropBlur=Settings::self()->contextBackdropBlur(); customBackdropFile=Settings::self()->contextBackdropFile(); switch (backdropType) { case PlayQueueView::BI_None: if (origType!=backdropType && isVisible() && !currentArtist.isEmpty()) { updateBackdrop(true); QWidget::update(); } break; case PlayQueueView::BI_Cover: if (origType!=backdropType || backdropOpacity!=origOpacity || backdropBlur!=origBlur) { if (isVisible() && !currentArtist.isEmpty()) { updateBackdrop(true); QWidget::update(); } } break; case PlayQueueView::BI_Custom: if (origType!=backdropType || backdropOpacity!=origOpacity || backdropBlur!=origBlur || origCustomBackdropFile!=customBackdropFile) { updateImage(QImage(customBackdropFile)); } break; } useDarkBackground(Settings::self()->contextDarkBackground()); WikipediaEngine::setIntroOnly(Settings::self()->wikipediaIntroOnly()); bool wasCollpased=stack && stack->isVisible(); alwaysCollapsed=Settings::self()->contextAlwaysCollapsed(); if (alwaysCollapsed && !wasCollpased) { setWide(false); } } void ContextWidget::saveConfig() { Settings::self()->saveContextZoom(artist->getZoom()); if (viewSelector) { Settings::self()->saveContextSlimPage(viewSelector->itemData(viewSelector->currentIndex()).toString()); } if (splitter) { Settings::self()->saveContextSplitterState(splitter->saveState()); } song->saveConfig(); } void ContextWidget::useDarkBackground(bool u) { if (u!=darkBackground) { darkBackground=u; QPalette pal=darkBackground ? palette() : parentWidget()->palette(); QColor prevLinkColor; QColor linkCol; if (darkBackground) { QColor dark(32, 32, 32); QColor light(240, 240, 240); QColor linkVisited(164, 164, 164); pal.setColor(QPalette::Window, dark); pal.setColor(QPalette::Base, dark); // Dont globally change window/button text - because this can mess up scrollbar buttons // with some styles (e.g. plastique) // pal.setColor(QPalette::WindowText, light); // pal.setColor(QPalette::ButtonText, light); pal.setColor(QPalette::Text, light); pal.setColor(QPalette::Link, light); pal.setColor(QPalette::LinkVisited, linkVisited); prevLinkColor=appLinkColor; linkCol=pal.color(QPalette::Link); } else { linkCol=appLinkColor; prevLinkColor=QColor(240, 240, 240); } setPalette(pal); artist->setPal(pal, linkCol, prevLinkColor); album->setPal(pal, linkCol, prevLinkColor); song->setPal(pal, linkCol, prevLinkColor); if (viewSelector) { viewSelector->setPalette(pal); } QWidget::update(); } } void ContextWidget::showEvent(QShowEvent *e) { setWide(width()>minWidth && !alwaysCollapsed); if (backdropType) { updateBackdrop(); } QWidget::showEvent(e); } void ContextWidget::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(rect()); if (darkBackground) { p.fillRect(r, palette().background().color()); } if (backdropType) { if (!oldBackdrop.isNull()) { if (!qFuzzyCompare(fadeValue, qreal(0.0))) { p.setOpacity(1.0-fadeValue); } if (oldBackdrop.height()0) { QImage blurred(img.size(), QImage::Format_ARGB32_Premultiplied); blurred.fill(Qt::transparent); QPainter painter(&blurred); qt_blurImage(&painter, img, backdropBlur, true, false); painter.end(); img = blurred; } currentImage=img; } resizeBackdrop(); animator.stop(); if (PlayQueueView::BI_Custom==backdropType || !isVisible()) { setFade(1.0); } else { fadeValue=0.0; animator.setDuration(250); animator.setEndValue(1.0); animator.start(); } QWidget::update(); } void ContextWidget::search() { if (song->isVisible()) { song->search(); } } void ContextWidget::update(const Song &s) { Song sng=s; if (sng.isVariousArtists()) { sng.revertVariousArtists(); } if (sng.isStandardStream() && sng.artist.isEmpty() && sng.albumartist.isEmpty() && sng.album.isEmpty()) { int pos=sng.title.indexOf(QLatin1String(" - ")); if (pos>3) { sng.artist=sng.title.left(pos); sng.title=sng.title.mid(pos+3); } } if (s.albumArtist()!=currentSong.albumArtist()) { cancel(); } if (Song::OnlineSvrTrack==sng.type) { if (!onlineContext) { QWidget *onlinePage=new QWidget(mainStack); QHBoxLayout *onlineLayout=new QHBoxLayout(onlinePage); int m=onlineLayout->margin()/2; onlineLayout->setMargin(0); onlineLayout->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed)); onlineContext=new OnlineView(onlinePage); onlineLayout->addWidget(onlineContext); mainStack->addWidget(onlinePage); } onlineContext->update(sng); mainStack->setCurrentIndex(1); updateArtist=QString(); if (isVisible() && PlayQueueView::BI_Cover==backdropType) { updateBackdrop(); } return; } mainStack->setCurrentIndex(0); artist->update(sng); album->update(sng); song->update(sng); currentSong=s; updateArtist=Covers::fixArtist(sng.basicArtist()); if (isVisible() && PlayQueueView::BI_Cover==backdropType) { updateBackdrop(); } } bool ContextWidget::eventFilter(QObject *o, QEvent *e) { if (QEvent::Wheel==e->type()) { QWheelEvent *we=static_cast(e); if (Qt::ControlModifier==we->modifiers()) { int numDegrees = static_cast(e)->delta() / 8; int numSteps = numDegrees / 15; artist->setZoom(numSteps); album->setZoom(numSteps); song->setZoom(numSteps); return true; } } return QObject::eventFilter(o, e); } void ContextWidget::cancel() { if (job) { job->cancelAndDelete(); job=0; } } void ContextWidget::updateBackdrop(bool force) { DBUG << updateArtist << currentArtist << currentSong.file << force; if (!force && updateArtist==currentArtist) { return; } currentArtist=updateArtist; if (currentArtist.isEmpty()) { updateImage(QImage()); QWidget::update(); return; } QString encoded=Covers::encodeName(currentArtist); QStringList names=QStringList() << encoded+"-"+constBackdropFileName+".jpg" << encoded+"-"+constBackdropFileName+".png" << constBackdropFileName+".jpg" << constBackdropFileName+".png"; if (!currentSong.isStream()) { bool localNonMpd=currentSong.file.startsWith(Utils::constDirSep); QString dirName=localNonMpd ? QString() : MPDConnection::self()->getDetails().dir; if (localNonMpd || (!dirName.isEmpty() && !dirName.startsWith(QLatin1String("http:/")) && MPDConnection::self()->getDetails().dirReadable)) { dirName+=Utils::getDir(currentSong.file); for (int level=0; level<2; ++level) { foreach (const QString &fileName, names) { DBUG << "Checking file(1)" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img(dirName+fileName); if (!img.isNull()) { DBUG << "Got backdrop from" << QString(dirName+fileName); updateImage(img); QWidget::update(); return; } } } QDir d(dirName); d.cdUp(); dirName=Utils::fixPath(d.absolutePath()); } } } // For various artists tracks, or for non-MPD files, see if we have a matching backdrop in MPD. // e.g. artist=Wibble, look for $mpdDir/Wibble/backdrop.png if (currentSong.isVariousArtists() || currentSong.isNonMPD()) { QString dirName=MPDConnection::self()->getDetails().dirReadable ? MPDConnection::self()->getDetails().dir : QString(); if (!dirName.isEmpty() && !dirName.startsWith(QLatin1String("http:/"))) { dirName+=currentArtist+Utils::constDirSep; foreach (const QString &fileName, names) { DBUG << "Checking file(2)" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img(dirName+fileName); if (!img.isNull()) { DBUG << "Got backdrop from" << QString(dirName+fileName); updateImage(img); QWidget::update(); return; } } } } } QString cacheName=cacheFileName(currentArtist, false); QImage img(cacheName); if (img.isNull()) { getBackdrop(); } else { DBUG << "Use cache file:" << cacheName; updateImage(img); QWidget::update(); } } static QString fixArtist(const QString &artist) { QString fixed(artist.trimmed()); fixed.remove(QChar('?')); return fixed; } void ContextWidget::getBackdrop() { cancel(); getFanArtBackdrop(); } void ContextWidget::getFanArtBackdrop() { // First we need to query musicbrainz to get id getMusicbrainzId(fixArtist(currentArtist)); } static const char * constArtistProp="artist-name"; void ContextWidget::getMusicbrainzId(const QString &artist) { QUrl url("http://www.musicbrainz.org/ws/2/artist/"); QUrlQuery query; query.addQueryItem("query", "artist:"+artist); url.setQuery(query); job = NetworkAccessManager::self()->get(url); DBUG << url.toString(); job->setProperty(constArtistProp, artist); connect(job, SIGNAL(finished()), this, SLOT(musicbrainzResponse())); } void ContextWidget::musicbrainzResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QString id; QString currentId; QString artist=reply->property(constArtistProp).toString(); if (reply->ok()) { bool inSection=false; QXmlStreamReader doc(reply->actualJob()); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (!inSection && QLatin1String("artist-list")==doc.name()) { inSection=true; } else if (inSection && QLatin1String("artist")==doc.name()) { // Store this artist ID as the current ID currentId=doc.attributes().value("id").toString(); if (id.isEmpty()) { // If we have no ID set, then use the first one - for now id=currentId; } } else if (inSection && QLatin1String("name")==doc.name()) { if (doc.readElementText()==artist) { // Found an arist in the artist-list whose name matches what we are looking for, so use this. id=currentId; break; } } } else if (doc.isEndDocument() && inSection && QLatin1String("artist-list")==doc.name()) { break; } } } if (id.isEmpty()) { // MusicBrainz does not seem to like AC/DC, but AC DC works - so if we fail with an artist // containing /, then try with space... if (!artist.isEmpty() && artist.contains("/")) { artist=artist.replace("/", " "); getMusicbrainzId(artist); } else { updateImage(QImage()); } } else { QUrl url("http://webservice.fanart.tv/v3/music/"+id); QUrlQuery query; query.addQueryItem("api_key", constFanArtApiKey); url.setQuery(query); job=NetworkAccessManager::self()->get(url); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(fanArtResponse())); } } void ContextWidget::fanArtResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QString url; if (reply->ok()) { QJsonParseError jsonParseError; QVariantMap parsed=QJsonDocument::fromJson(reply->readAll(), &jsonParseError).toVariant().toMap(); bool ok=QJsonParseError::NoError==jsonParseError.error; if (ok && !parsed.isEmpty()) { QVariantList artistbackgrounds; if (parsed.contains("artistbackground")) { artistbackgrounds=parsed["artistbackground"].toList(); } else { QVariantMap artist=parsed[parsed.keys().first()].toMap(); if (artist.contains("artistbackground")) { artistbackgrounds=artist["artistbackground"].toList(); } } if (!artistbackgrounds.isEmpty()) { QVariantMap artistbackground=artistbackgrounds.first().toMap(); if (artistbackground.contains("url")) { url=artistbackground["url"].toString(); } } } } if (url.isEmpty()) { updateImage(QImage()); } else { job=NetworkAccessManager::self()->get(QUrl(url)); DBUG << url; connect(job, SIGNAL(finished()), this, SLOT(downloadResponse())); } } void ContextWidget::downloadResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QImage img; QByteArray data; if (reply->ok()) { data=reply->readAll(); img=QImage::fromData(data); } if (!img.isNull()) { bool saved=false; if (Settings::self()->storeBackdropsInMpdDir() && !currentSong.isVariousArtists() && !currentSong.isNonMPD() && MPDConnection::self()->getDetails().dirReadable) { QString mpdDir=MPDConnection::self()->getDetails().dir; QString songDir=Utils::getDir(currentSong.file); if (!mpdDir.isEmpty() && 2==songDir.split(Utils::constDirSep, QString::SkipEmptyParts).count()) { QDir d(mpdDir+songDir); d.cdUp(); QString fileName=Utils::fixPath(d.absolutePath())+constBackdropFileName+".jpg"; QFile f(fileName); if (f.open(QIODevice::WriteOnly)) { f.write(data); f.close(); DBUG << "Saved backdrop to" << fileName << "for artist" << currentArtist << ", current song" << currentSong.file; saved=true; } } else { DBUG << "Not saving to mpd folder, mpd dir:" << mpdDir << "num parts:" << songDir.split(Utils::constDirSep, QString::SkipEmptyParts).count(); } } else { DBUG << "Not saving to mpd folder - set to save in mpd?" << Settings::self()->storeBackdropsInMpdDir() << "isVa:" << currentSong.isVariousArtists() << "isNonMPD:" << currentSong.isNonMPD() << "mpd readable:" << MPDConnection::self()->getDetails().dirReadable; } if (!saved) { QString cacheName=cacheFileName(currentArtist, true); QFile f(cacheName); if (f.open(QIODevice::WriteOnly)) { DBUG << "Saved backdrop to (cache)" << cacheName << "for artist" << currentArtist << ", current song" << currentSong.file; f.write(data); f.close(); } } } updateImage(img); } void ContextWidget::resizeBackdrop() { if (!currentImage.isNull() &&( currentBackdrop.isNull() || (!currentBackdrop.isNull() && currentBackdrop.width()!=width()))) { QSize sz(width(), width()*currentImage.height()/currentImage.width()); currentBackdrop = QPixmap::fromImage(currentImage.scaled(sz, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } } NetworkJob * ContextWidget::getReply(QObject *obj) { NetworkJob *reply = qobject_cast(obj); if (!reply) { return 0; } reply->deleteLater(); if (reply!=job) { return 0; } job=0; return reply; } cantata-2.2.0/context/contextwidget.h000066400000000000000000000077101316350454000176610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CONTEXT_WIDGET_H #define CONTEXT_WIDGET_H #include #include #include #include #include #include "mpd-interface/song.h" class ArtistView; class AlbumView; class SongView; class NetworkJob; class QStackedWidget; class QComboBox; class QImage; class QToolButton; class QButtonGroup; class QWheelEvent; class OnlineView; class ViewSelector : public QWidget { Q_OBJECT public: ViewSelector(QWidget *p); virtual ~ViewSelector() { } void addItem(const QString &label, const QVariant &data); QVariant itemData(int index) const; int count() { return buttons.count(); } int currentIndex() const; void setCurrentIndex(int index); private: void wheelEvent(QWheelEvent *ev); void paintEvent(QPaintEvent *); private Q_SLOTS: void buttonActivated(); Q_SIGNALS: void activated(int); private: QButtonGroup *group; QList buttons; }; class ThinSplitter : public QSplitter { Q_OBJECT public: ThinSplitter(QWidget *parent); QSplitterHandle *createHandle(); public Q_SLOTS: void reset(); private: QAction *resetAct; }; class ContextWidget : public QWidget { Q_OBJECT Q_PROPERTY(float fade READ fade WRITE setFade) public: static void enableDebug(); static const QLatin1String constBackdropFileName; static const QLatin1String constCacheDir; static const QLatin1String constFanArtApiKey; ContextWidget(QWidget *parent=0); void readConfig(); void saveConfig(); void useDarkBackground(bool u); void update(const Song &s); void showEvent(QShowEvent *e); void paintEvent(QPaintEvent *e); float fade() { return fadeValue; } void setFade(float value); void updateImage(QImage img); void search(); Q_SIGNALS: void findArtist(const QString &artist); void findAlbum(const QString &artist, const QString &album); void playSong(const QString &file); private Q_SLOTS: void musicbrainzResponse(); void fanArtResponse(); void downloadResponse(); private: void setZoom(); void setWide(bool w); void resizeEvent(QResizeEvent *e); bool eventFilter(QObject *o, QEvent *e); void cancel(); void updateBackdrop(bool force=false); void getBackdrop(); void getFanArtBackdrop(); void getMusicbrainzId(const QString &artist); void createBackdrop(); void resizeBackdrop(); NetworkJob * getReply(QObject *obj); private: NetworkJob *job; bool alwaysCollapsed; int backdropType; int backdropOpacity; int backdropBlur; QString customBackdropFile; bool darkBackground; Song currentSong; QImage currentImage; QPixmap oldBackdrop; QPixmap currentBackdrop; QString currentArtist; QString updateArtist; ArtistView *artist; AlbumView *album; SongView *song; QColor appLinkColor; double fadeValue; QPropertyAnimation animator; int minWidth; bool isWide; QStackedWidget *mainStack; QStackedWidget *stack; QWidget *standardContext; OnlineView *onlineContext; ThinSplitter *splitter; ViewSelector *viewSelector; }; #endif cantata-2.2.0/context/lastfmengine.cpp000066400000000000000000000132651316350454000200020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "lastfmengine.h" #include "network/networkaccessmanager.h" #include "gui/covers.h" #include "config.h" #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void LastFmEngine::enableDebug() { debugEnabled=true; } const QLatin1String LastFmEngine::constLang("lastfm"); const QLatin1String LastFmEngine::constLinkPlaceholder("XXX_CONTEXT_READ_MORE_ON_LASTFM_XXX"); static const char * constModeProperty="mode"; static const char * constQuery="query"; LastFmEngine::LastFmEngine(QObject *p) : ContextEngine(p) { } QStringList LastFmEngine::getLangs() const { QStringList langs; langs.append(constLang); return langs; } QString LastFmEngine::translateLinks(QString text) const { text=text.replace(constLinkPlaceholder, tr("Read more on last.fm")); return text; } void LastFmEngine::search(const QStringList &query, Mode mode) { QStringList fixedQuery=fixQuery(query); QUrl url("https://ws.audioscrobbler.com/2.0/"); QUrlQuery urlQuery; switch (mode) { case Artist: urlQuery.addQueryItem("method", "artist.getInfo"); break; case Album: urlQuery.addQueryItem("method", "album.getInfo"); urlQuery.addQueryItem("album", fixedQuery.at(1)); break; case Track: urlQuery.addQueryItem("method", "track.getInfo"); urlQuery.addQueryItem("track", fixedQuery.at(1)); break; } urlQuery.addQueryItem("api_key", Covers::constLastFmApiKey); urlQuery.addQueryItem("autocorrect", "1"); urlQuery.addQueryItem("artist", Covers::fixArtist(fixedQuery.at(0))); url.setQuery(urlQuery); job=NetworkAccessManager::self()->get(url); job->setProperty(constModeProperty, (int)mode); QStringList queryString; foreach (QString q, fixedQuery) { q=q.replace("/", "%2F"); q=q.replace(" ", "+"); queryString.append(q); } job->setProperty(constQuery, queryString.join("/")); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(parseResponse())); } void LastFmEngine::parseResponse() { DBUG << __FUNCTION__; NetworkJob *reply = getReply(sender()); if (!reply) { return; } QByteArray data=reply->readAll(); if (!reply->ok() || data.isEmpty()) { DBUG << "Empty/error"; emit searchResult(QString(), QString()); return; } Mode mode=(Mode)reply->property(constModeProperty).toInt(); QString text; switch (mode) { case Artist: text=parseResponse(data, QLatin1String("artist"), QLatin1String("bio")); break; case Album: text=parseResponse(data, QLatin1String("album"), QLatin1String("wiki")); break; case Track: text=parseResponse(data, QLatin1String("track"), QLatin1String("wiki")); break; } if (!text.isEmpty()) { static const QRegExp constLicense("User-contributed text is available.*"); text.remove(constLicense); text.replace("\n", "
    "); text=text.simplified(); text.replace("
    ", "
    "); // Remove last.fm read more link (as we add our own!!) int end=text.lastIndexOf(QLatin1String("on Last.fm")); if (-1!=end) { int start=text.lastIndexOf(QLatin1String("
    ")+constLinkPlaceholder+QLatin1String(""); text.replace("




    ", "

    "); text.replace("



    ", "

    "); text.replace("


    ", "

    "); } emit searchResult(text, text.isEmpty() ? QString() : constLang); } QString LastFmEngine::parseResponse(const QByteArray &data, const QString &firstTag, const QString &secondTag) { DBUG << __FUNCTION__ << data; QXmlStreamReader xml(data); xml.setNamespaceProcessing(false); while (xml.readNextStartElement()) { if (firstTag==xml.name()) { while (xml.readNextStartElement()) { if (secondTag==xml.name()) { while (xml.readNextStartElement()) { if (QLatin1String("content")==xml.name()) { return xml.readElementText().trimmed(); } else { xml.skipCurrentElement(); } } } else { xml.skipCurrentElement(); } } } } return QString(); } cantata-2.2.0/context/lastfmengine.h000066400000000000000000000030531316350454000174410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LASTFM_ENGINE_H #define LASTFM_ENGINE_H #include "contextengine.h" #include class LastFmEngine : public ContextEngine { Q_OBJECT public: static const QLatin1String constLang; static const QLatin1String constLinkPlaceholder; static void enableDebug(); LastFmEngine(QObject *p); QStringList getLangs() const; virtual QString getPrefix(const QString &) const { return constLang; } QString translateLinks(QString text) const; public Q_SLOTS: void search(const QStringList &query, Mode mode); private Q_SLOTS: void parseResponse(); private: QString parseResponse(const QByteArray &data, const QString &firstTag, const QString &secondTag); }; #endif cantata-2.2.0/context/lyrics_providers.xml000066400000000000000000000364341316350454000207510ustar00rootroot00000000000000 cantata-2.2.0/context/lyricsdialog.cpp000066400000000000000000000066161316350454000200150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "support/icon.h" #include "lyricsdialog.h" LyricsDialog::LyricsDialog(const Song &s, QWidget *parent) : Dialog(parent) , prev(s) { QWidget *mw=new QWidget(this); QGridLayout *mainLayout=new QGridLayout(mw); QWidget *wid = new QWidget(mw); QFormLayout *layout = new QFormLayout(wid); int row=0; QLabel *lbl=new QLabel(tr("If Cantata has failed to find lyrics, or has found the wrong ones, " "use this dialog to enter new search details. For example, the current " "song may actually be a cover-version - if so, then searching for " "lyrics by the original artist might help.\n\nIf this search does find " "new lyrics, these will still be associated with the original song title " "and artist as displayed in Cantata."), mw); lbl->setWordWrap(true); QLabel *icn=new QLabel(mw); icn->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); int iconSize=Icon::dlgIconSize(); icn->setFixedSize(iconSize, iconSize); icn->setPixmap(Icon("dialog-information").pixmap(iconSize, iconSize)); mainLayout->setMargin(0); layout->setMargin(0); mainLayout->addWidget(icn, 0, 0, 1, 1); mainLayout->addWidget(lbl, 0, 1, 1, 1); mainLayout->addItem(new QSpacerItem(8, 4, QSizePolicy::Fixed, QSizePolicy::Fixed), 1, 0); mainLayout->addWidget(wid, 2, 0, 1, 2); titleEntry = new LineEdit(wid); artistEntry = new LineEdit(wid); layout->setWidget(row, QFormLayout::LabelRole, new QLabel(tr("Title:"), wid)); layout->setWidget(row++, QFormLayout::FieldRole, titleEntry); layout->setWidget(row, QFormLayout::LabelRole, new QLabel(tr("Artist:"), wid)); layout->setWidget(row++, QFormLayout::FieldRole, artistEntry); setCaption(tr("Search For Lyrics")); setMainWidget(mw); setButtons(Ok|Cancel); enableButton(Ok, false); connect(titleEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); connect(artistEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); titleEntry->setFocus(); titleEntry->setText(s.title); artistEntry->setText(s.artist); } Song LyricsDialog::song() const { Song s=prev; s.artist=artistEntry->text().trimmed(); s.title=titleEntry->text().trimmed(); return s; } void LyricsDialog::changed() { Song s=song(); enableButton(Ok, !s.artist.isEmpty() && !s.title.isEmpty() && (s.artist!=prev.artist || s.title!=prev.title)); } cantata-2.2.0/context/lyricsdialog.h000066400000000000000000000023411316350454000174510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LYRICSDIALOG_H #define LYRICSDIALOG_H #include "support/dialog.h" #include "support/lineedit.h" #include "mpd-interface/song.h" class LyricsDialog : public Dialog { Q_OBJECT public: LyricsDialog(const Song &s, QWidget *parent); Song song() const; private Q_SLOTS: void changed(); private: Song prev; LineEdit *titleEntry; LineEdit *artistEntry; }; #endif cantata-2.2.0/context/lyricsettings.cpp000066400000000000000000000037501316350454000202270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #include "lyricsettings.h" #include "ultimatelyricsprovider.h" #include "ultimatelyrics.h" #include "config.h" #include "gui/settings.h" LyricSettings::LyricSettings(QWidget *p) : ToggleList(p) , loadedXml(false) { label->setText(tr("Choose the websites you want to use when searching for lyrics.")); } void LyricSettings::load() { } void LyricSettings::save() { if (!loadedXml) { return; } QStringList enabled; for (int i=0; icount(); ++i) { enabled.append(selected->item(i)->data(Qt::UserRole).toString()); } UltimateLyrics::self()->setEnabled(enabled); } void LyricSettings::showEvent(QShowEvent *e) { if (!loadedXml) { const QList &lprov=UltimateLyrics::self()->getProviders(); available->clear(); selected->clear(); foreach (const UltimateLyricsProvider *provider, lprov) { QListWidgetItem *item = new QListWidgetItem(provider->isEnabled() ? selected : available); item->setText(provider->displayName()); item->setData(Qt::UserRole, provider->getName()); } loadedXml=true; } QWidget::showEvent(e); } cantata-2.2.0/context/lyricsettings.h000066400000000000000000000023701316350454000176710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #ifndef LYRICSETTINGS_H #define LYRICSETTINGS_H #include #include #include #include "togglelist.h" class QListWidgetItem; class UltimateLyricsProvider; class LyricSettings : public ToggleList { Q_OBJECT public: LyricSettings(QWidget *p); virtual ~LyricSettings() { } void load(); void save(); void showEvent(QShowEvent *e); private: bool loadedXml; }; #endif // LYRICSETTINGS_H cantata-2.2.0/context/metaengine.cpp000066400000000000000000000073721316350454000174440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "metaengine.h" #include "wikipediaengine.h" #include "lastfmengine.h" #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void MetaEngine::enableDebug() { debugEnabled=true; } static const QLatin1String constBlankResp("-"); MetaEngine::MetaEngine(QObject *p) : ContextEngine(p) { wiki=new WikipediaEngine(this); lastfm=new LastFmEngine(this); connect(wiki, SIGNAL(searchResult(QString,QString)), SLOT(wikiResponse(QString,QString))); connect(lastfm, SIGNAL(searchResult(QString,QString)), SLOT(lastFmResponse(QString,QString))); } QStringList MetaEngine::getLangs() const { QStringList langs=wiki->getLangs(); langs.append(LastFmEngine::constLang); return langs; } QString MetaEngine::getPrefix(const QString &key) const { return key==LastFmEngine::constLang ? LastFmEngine::constLang : wiki->getPrefix(key); } QString MetaEngine::translateLinks(QString text) const { return lastfm->translateLinks(wiki->translateLinks(text)); } void MetaEngine::search(const QStringList &query, Mode mode) { DBUG << query << (int)mode; responses.clear(); wiki->cancel(); lastfm->cancel(); wiki->search(query, mode); lastfm->search(query, mode); } void MetaEngine::wikiResponse(const QString &html, const QString &lang) { DBUG << html.isEmpty() << lang.isEmpty(); if (!html.isEmpty()) { // Got a wikipedia reponse, use it! DBUG << "Got wiki response!"; lastfm->cancel(); emit searchResult(html, lang); responses.clear(); } else if (responses[LastFm].html.isEmpty()) { DBUG << "Wiki empty, but not received last.fm"; // Wikipedia response is empty, but have not received last.fm reply yet. // So, indicate that Wikipedia was empty - and wait for last.fm responses[Wiki]=Response(constBlankResp, lang); } else if (constBlankResp==responses[LastFm].html) { DBUG << "Both responses empty"; // Last.fm is empty as well :-( emit searchResult(QString(), QString()); responses.clear(); } else { // Got a last.fm response, use that! DBUG << "Wiki empty, last.fm not - so use last.fm"; emit searchResult(responses[LastFm].html, responses[LastFm].lang); responses.clear(); } } void MetaEngine::lastFmResponse(const QString &html, const QString &lang) { DBUG << html.isEmpty() << lang.isEmpty(); if (constBlankResp==responses[Wiki].html) { // Wikipedia failed, so use last.fm response... DBUG << "Wiki failed, so use last.fm"; emit searchResult(html, lang); responses.clear(); } else if (responses[Wiki].html.isEmpty()) { // No Wikipedia response yet, so save last.fm response... DBUG << "No wiki response, save last.fm"; responses[LastFm]=Response(html.isEmpty() ? constBlankResp : html, lang); } } cantata-2.2.0/context/metaengine.h000066400000000000000000000034551316350454000171070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef META_ENGINE_H #define META_ENGINE_H #include "contextengine.h" #include #include class WikipediaEngine; class LastFmEngine; class MetaEngine : public ContextEngine { Q_OBJECT enum Engines { Wiki = 0, LastFm = 1 }; struct Response { Response(const QString &h=QString(), const QString &l=QString()) : html(h), lang(l) { } QString html; QString lang; }; public: static void enableDebug(); MetaEngine(QObject *p); QStringList getLangs() const; QString getPrefix(const QString &key) const; QString translateLinks(QString text) const; public Q_SLOTS: void search(const QStringList &query, Mode mode); private Q_SLOTS: void wikiResponse(const QString &html, const QString &lang); void lastFmResponse(const QString &html, const QString &lang); private: QMap responses; WikipediaEngine *wiki; LastFmEngine *lastfm; }; #endif cantata-2.2.0/context/onlineview.cpp000066400000000000000000000030421316350454000174750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlineview.h" #include "gui/covers.h" OnlineView::OnlineView(QWidget *p) : View(p) { setStandardHeader(tr("Song Information")); int imageSize=fontMetrics().height()*18; setPicSize(QSize(imageSize, imageSize)); } void OnlineView::update(const Song &song, bool force) { if (force || song!=currentSong) { currentSong=song; if (!isVisible()) { needToUpdate=true; return; } setHeader(song.describe(true)); Covers::Image cImg=Covers::self()->requestImage(song, true); if (!cImg.img.isNull()) { setHtml(createPicTag(cImg.img, cImg.fileName)); } else { setHtml(QString()); } } } cantata-2.2.0/context/onlineview.h000066400000000000000000000020461316350454000171450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_VIEW_H #define ONLINE_VIEW_H #include "view.h" class OnlineView : public View { Q_OBJECT public: OnlineView(QWidget *p); void update(const Song &song, bool force=false); }; #endif cantata-2.2.0/context/othersettings.cpp000066400000000000000000000122551316350454000202260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "othersettings.h" #include "gui/settings.h" #include "support/pathrequester.h" #include "widgets/playqueueview.h" static const char *constValueProperty="value"; OtherSettings::OtherSettings(QWidget *p) : QWidget(p) { setupUi(this); connect(wikipediaIntroOnly, SIGNAL(toggled(bool)), SLOT(toggleWikiNote())); contextBackdrop_none->setProperty(constValueProperty, PlayQueueView::BI_None); contextBackdrop_artist->setProperty(constValueProperty, PlayQueueView::BI_Cover); contextBackdrop_custom->setProperty(constValueProperty, PlayQueueView::BI_Custom); contextBackdropFile->setDirMode(false); contextBackdropFile->setFilter(tr("Images (*.png *.jpg)")); int labelWidth=qMax(fontMetrics().width(QLatin1String("100%")), fontMetrics().width(tr("10px", "pixels"))); contextBackdropOpacityLabel->setFixedWidth(labelWidth); contextBackdropBlurLabel->setFixedWidth(labelWidth); connect(contextBackdropOpacity, SIGNAL(valueChanged(int)), SLOT(setContextBackdropOpacityLabel())); connect(contextBackdropBlur, SIGNAL(valueChanged(int)), SLOT(setContextBackdropBlurLabel())); connect(contextBackdrop_none, SIGNAL(toggled(bool)), SLOT(enableContextBackdropOptions())); connect(contextBackdrop_artist, SIGNAL(toggled(bool)), SLOT(enableContextBackdropOptions())); connect(contextBackdrop_custom, SIGNAL(toggled(bool)), SLOT(enableContextBackdropOptions())); } void OtherSettings::load() { wikipediaIntroOnly->setChecked(Settings::self()->wikipediaIntroOnly()); contextDarkBackground->setChecked(Settings::self()->contextDarkBackground()); contextAlwaysCollapsed->setChecked(Settings::self()->contextAlwaysCollapsed()); int bgnd=Settings::self()->contextBackdrop(); contextBackdrop_none->setChecked(bgnd==contextBackdrop_none->property(constValueProperty).toInt()); contextBackdrop_artist->setChecked(bgnd==contextBackdrop_artist->property(constValueProperty).toInt()); contextBackdrop_custom->setChecked(bgnd==contextBackdrop_custom->property(constValueProperty).toInt()); contextBackdropOpacity->setValue(Settings::self()->contextBackdropOpacity()); contextBackdropBlur->setValue(Settings::self()->contextBackdropBlur()); contextBackdropFile->setText(Utils::convertPathForDisplay(Settings::self()->contextBackdropFile(), false)); contextSwitchTime->setValue(Settings::self()->contextSwitchTime()); toggleWikiNote(); setContextBackdropOpacityLabel(); setContextBackdropBlurLabel(); enableContextBackdropOptions(); } void OtherSettings::save() { Settings::self()->saveWikipediaIntroOnly(wikipediaIntroOnly->isChecked()); Settings::self()->saveContextDarkBackground(contextDarkBackground->isChecked()); Settings::self()->saveContextAlwaysCollapsed(contextAlwaysCollapsed->isChecked()); if (contextBackdrop_none->isChecked()) { Settings::self()->saveContextBackdrop(contextBackdrop_none->property(constValueProperty).toInt()); } else if (contextBackdrop_artist->isChecked()) { Settings::self()->saveContextBackdrop(contextBackdrop_artist->property(constValueProperty).toInt()); } else if (contextBackdrop_custom->isChecked()) { Settings::self()->saveContextBackdrop(contextBackdrop_custom->property(constValueProperty).toInt()); } Settings::self()->saveContextBackdropOpacity(contextBackdropOpacity->value()); Settings::self()->saveContextBackdropBlur(contextBackdropBlur->value()); Settings::self()->saveContextBackdropFile(Utils::convertPathFromDisplay(contextBackdropFile->text(), false)); Settings::self()->saveContextSwitchTime(contextSwitchTime->value()); } void OtherSettings::toggleWikiNote() { wikipediaIntroOnlyNote->setOn(!wikipediaIntroOnly->isChecked()); } void OtherSettings::setContextBackdropOpacityLabel() { contextBackdropOpacityLabel->setText(tr("%1%", "value%").arg(contextBackdropOpacity->value())); } void OtherSettings::setContextBackdropBlurLabel() { contextBackdropBlurLabel->setText(tr("%1 px", "pixels").arg(contextBackdropBlur->value())); } void OtherSettings::enableContextBackdropOptions() { contextBackdropOpacity->setEnabled(!contextBackdrop_none->isChecked()); contextBackdropOpacityLabel->setEnabled(contextBackdropOpacity->isEnabled()); contextBackdropBlur->setEnabled(contextBackdropOpacity->isEnabled()); contextBackdropBlurLabel->setEnabled(contextBackdropOpacity->isEnabled()); } cantata-2.2.0/context/othersettings.h000066400000000000000000000024241316350454000176700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef OTHER_SETTINGS_H #define OTHER_SETTINGS_H #include "ui_othersettings.h" class OtherSettings : public QWidget, private Ui::OtherSettings { Q_OBJECT public: OtherSettings(QWidget *p); virtual ~OtherSettings() { } void load(); void save(); private Q_SLOTS: void toggleWikiNote(); void setContextBackdropOpacityLabel(); void setContextBackdropBlurLabel(); void enableContextBackdropOptions(); }; #endif cantata-2.2.0/context/othersettings.ui000066400000000000000000000206751316350454000200660ustar00rootroot00000000000000 OtherSettings 0 0 658 450 QFormLayout::ExpandingFieldsGrow 0 0 Background Image None Artist image Custom image: false 0 0 Blur: 0 false 20 1 Qt::Horizontal QSlider::TicksBelow 1 false 10px Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Opacity: 0 false 100 10 Qt::Horizontal QSlider::TicksBelow 10 false 40% Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Automatically switch to view after: contextSwitchTime Do not auto-switch ms 5000 500 Dark background Darken background, and use white text, regardless of current color palette. Always collapse into a single pane Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Qt::Vertical QSizePolicy::MinimumExpanding 20 8 BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    PathRequester QLineEdit
    support/pathrequester.h
    1
    contextBackdrop_custom toggled(bool) contextBackdropFile setEnabled(bool) 80 90 172 92
    cantata-2.2.0/context/songview.cpp000066400000000000000000000767321316350454000171770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "songview.h" #include "lyricsdialog.h" #include "ultimatelyricsprovider.h" #include "ultimatelyrics.h" #include "contextengine.h" #include "gui/settings.h" #include "gui/covers.h" #include "support/squeezedtextlabel.h" #include "support/utils.h" #include "support/messagebox.h" #include "support/monoicon.h" #ifdef TAGLIB_FOUND #include "tags/tags.h" #endif #include "widgets/icons.h" #include "support/utils.h" #include "support/action.h" #include "support/actioncollection.h" #include "network/networkaccessmanager.h" #include "widgets/textbrowser.h" #include "gui/stdactions.h" #include "mpd-interface/mpdstatus.h" #include "qtiocompressor/qtiocompressor.h" #include #include #include #include #include #include #include #include #include const QLatin1String SongView::constLyricsDir("lyrics/"); const QLatin1String SongView::constExtension(".lyrics"); const QLatin1String SongView::constCacheDir("tracks/"); const QLatin1String SongView::constInfoExt(".html.gz"); static QString infoCacheFileName(const Song &song, const QString &lang, bool createDir) { QString artist=song.artist; QString title=song.title; title.replace("/", "_"); artist.replace("/", "_"); QString dir=Utils::cacheDir(SongView::constCacheDir+Covers::encodeName(artist)+Utils::constDirSep, createDir); if (dir.isEmpty()) { return QString(); } return dir+Covers::encodeName(title)+"."+lang+SongView::constInfoExt; } static QString lyricsCacheFileName(const Song &song, bool createDir=false) { QString artist=song.artist; QString title=song.title; title.replace("/", "_"); artist.replace("/", "_"); QString dir=Utils::cacheDir(SongView::constLyricsDir+Covers::encodeName(artist)+Utils::constDirSep, createDir); if (dir.isEmpty()) { return QString(); } return dir+Covers::encodeName(title)+SongView::constExtension; } #if !defined Q_OS_WIN && !defined Q_OS_MAC static QString lyricsOtherFileName(const Song &song, bool createDir=false) { QString artist=song.artist; QString title=song.title; title.replace("/", "_"); artist.replace("/", "_"); return QDir::homePath()+"/.lyrics/"+Covers::encodeName(artist)+" - "+Covers::encodeName(title)+".txt"; } #endif static inline QString mpdLyricsFilePath(const QString &songFile) { return Utils::changeExtension(MPDConnection::self()->getDetails().dir+songFile, SongView::constExtension); } static inline QString mpdLyricsFilePath(const Song &song) { return mpdLyricsFilePath(song.filePath()); } static inline QString fixNewLines(const QString &o) { return QString(o).replace(QLatin1String("\n\n\n"), QLatin1String("\n\n")).replace("\n", "
    "); } static QString actualFile(const Song &song) { QString songFile=song.filePath(); if (song.isCantataStream()) { QUrl qu(songFile); QUrlQuery u(qu); songFile=u.hasQueryItem("file") ? u.queryItemValue("file") : QString(); } return songFile; } SongView::SongView(QWidget *p) : View(p, QStringList() << tr("Lyrics") << tr("Information") << tr("Metadata")) , scrollTimer(0) , songPos(0) , currentProvider(-1) , currentRequest(0) , mode(Mode_Display) , job(0) , currentProv(0) , lyricsNeedsUpdating(true) , infoNeedsUpdating(true) , metadataNeedsUpdating(true) { QColor iconCol=Utils::monoIconColor(); scrollAction = ActionCollection::get()->createAction("scrolllyrics", tr("Scroll Lyrics"), MonoIcon::icon(FontAwesome::chevrondown, iconCol)); refreshAction = ActionCollection::get()->createAction("refreshlyrics", tr("Refresh Lyrics"), Icons::self()->refreshIcon); editAction = ActionCollection::get()->createAction("editlyrics", tr("Edit Lyrics"), Icons::self()->editIcon); delAction = ActionCollection::get()->createAction("dellyrics", tr("Delete Lyrics File"), Icons::self()->removeIcon); scrollAction->setCheckable(true); scrollAction->setChecked(Settings::self()->contextAutoScroll()); connect(scrollAction, SIGNAL(toggled(bool)), SLOT(toggleScroll())); connect(refreshAction, SIGNAL(triggered()), SLOT(update())); connect(editAction, SIGNAL(triggered()), SLOT(edit())); connect(delAction, SIGNAL(triggered()), SLOT(del())); connect(UltimateLyrics::self(), SIGNAL(lyricsReady(int, QString)), SLOT(lyricsReady(int, QString))); engine=ContextEngine::create(this); refreshInfoAction = ActionCollection::get()->createAction("refreshtrack", tr("Refresh Track Information"), Icons::self()->refreshIcon); cancelInfoJobAction=new Action(Icons::self()->cancelIcon, tr("Cancel"), this); cancelInfoJobAction->setEnabled(false); connect(refreshInfoAction, SIGNAL(triggered()), SLOT(refreshInfo())); connect(cancelInfoJobAction, SIGNAL(triggered()), SLOT(abortInfoSearch())); connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(infoSearchResponse(QString,QString))); foreach (TextBrowser *t, texts) { connect(t, SIGNAL(anchorClicked(QUrl)), SLOT(showMoreInfo(QUrl))); } text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); texts.at(Page_Information)->setContextMenuPolicy(Qt::CustomContextMenu); connect(texts.at(Page_Information), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showInfoContextMenu(QPoint))); connect(this, SIGNAL(viewChanged()), this, SLOT(curentViewChanged())); setMode(Mode_Blank); setStandardHeader(tr("Track")); clear(); toggleScroll(); setCurrentView(Settings::self()->contextTrackView()); } SongView::~SongView() { UltimateLyrics::self()->release(); } void SongView::update() { QString mpdName=mpdFileName(); QString cacheName=cacheFileName(); bool mpdExists=!mpdName.isEmpty() && QFile::exists(mpdName); bool cacheExists=!cacheName.isEmpty() && QFile::exists(cacheName); if (mpdExists || cacheExists) { switch (MessageBox::warningYesNoCancel(this, tr("Reload lyrics?\n\nReload from disk, or delete disk copy and download?"), tr("Reload"), GuiItem(tr("Reload From Disk")), GuiItem(tr("Download")))) { case MessageBox::Yes: break; case MessageBox::No: if (mpdExists) { QFile::remove(mpdName); } if (cacheExists) { QFile::remove(cacheName); } break; default: return; } } update(currentSong, true); } void SongView::saveConfig() { Settings::self()->saveContextAutoScroll(scrollAction->isChecked()); Settings::self()->saveContextTrackView(currentView()); } void SongView::search() { setMode(Mode_Display); Song song=currentSong; LyricsDialog dlg(currentSong, this); if (QDialog::Accepted==dlg.exec()) { if ((song.artist!=currentSong.artist || song.title!=currentSong.title) && MessageBox::No==MessageBox::warningYesNo(this, tr("Current playing song has changed, still perform search?"), tr("Song Changed"), GuiItem(tr("Perform Search")), StdGuiItem::cancel())) { return; } QString mpdName=mpdFileName(); if (!mpdName.isEmpty() && QFile::exists(mpdName)) { QFile::remove(mpdName); } QString cacheName=cacheFileName(); if (!cacheName.isEmpty() && QFile::exists(cacheName)) { QFile::remove(cacheName); } update(dlg.song(), true); } } void SongView::edit() { QDesktopServices::openUrl(QUrl::fromLocalFile(lyricsFile)); } void SongView::del() { if (MessageBox::No==MessageBox::warningYesNo(this, tr("Delete lyrics file?"), tr("Delete File"), StdGuiItem::del(), StdGuiItem::cancel())) { return; } QString mpdName=mpdFileName(); QString cacheName=cacheFileName(); if (!cacheName.isEmpty() && QFile::exists(cacheName)) { QFile::remove(cacheName); } if (!mpdName.isEmpty() && QFile::exists(mpdName)) { QFile::remove(mpdName); } } void SongView::showContextMenu(const QPoint &pos) { QMenu *menu = text->createStandardContextMenu(); switch (mode) { case Mode_Blank: break; case Mode_Display: menu->addSeparator(); menu->addAction(scrollAction); menu->addAction(refreshAction); menu->addAction(StdActions::self()->searchAction); menu->addSeparator(); menu->addAction(editAction); menu->addAction(delAction); break; } if (cancelJobAction->isEnabled()) { menu->addSeparator(); menu->addAction(cancelJobAction); } menu->exec(text->mapToGlobal(pos)); delete menu; } void SongView::showInfoContextMenu(const QPoint &pos) { QMenu *menu = texts.at(Page_Information)->createStandardContextMenu(); menu->addSeparator(); if (cancelInfoJobAction->isEnabled()) { menu->addAction(cancelInfoJobAction); } else { menu->addAction(refreshInfoAction); } menu->exec(texts.at(Page_Information)->mapToGlobal(pos)); delete menu; } void SongView::toggleScroll() { if (scrollAction->isChecked()) { scrollTimer=new QTimer(this); scrollTimer->setSingleShot(false); scrollTimer->setInterval(1000); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(songPosition())); connect(scrollTimer, SIGNAL(timeout()), this, SLOT(scroll())); scroll(); } else { disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(songPosition())); if (scrollTimer) { connect(scrollTimer, SIGNAL(timeout()), this, SLOT(scroll())); scrollTimer->stop(); } } } void SongView::songPosition() { if (scrollTimer) { songPos=MPDStatus::self()->timeElapsed(); scroll(); } } void SongView::scroll() { if (Mode_Display==mode && scrollAction->isChecked() && scrollTimer) { QScrollBar *bar=text->verticalScrollBar(); if (bar && bar->isSliderDown()) { scrollTimer->stop(); return; } if (MPDStatus::self()->timeTotal()<=0) { scrollTimer->stop(); return; } if (MPDState_Playing==MPDStatus::self()->state()) { if (!scrollTimer->isActive()) { scrollTimer->start(); } } else { scrollTimer->stop(); } if (MPDStatus::self()->guessedElapsed()>=MPDStatus::self()->timeTotal()) { if (scrollTimer) { scrollTimer->stop(); } return; } if (bar->isVisible()) { int newSliderPosition = MPDStatus::self()->guessedElapsed() * (bar->maximum() + bar->pageStep()) / MPDStatus::self()->timeTotal() - bar->pageStep() / 2; bar->setSliderPosition(newSliderPosition); } } } void SongView::curentViewChanged() { if (!isVisible()) { return; } switch (currentView()) { case Page_Lyrics: loadLyrics(); break; case Page_Information: loadInfo(); break; case Page_Metadata: loadMetadata(); break; default: break; } } void SongView::loadLyrics() { if (!lyricsNeedsUpdating) { return; } lyricsNeedsUpdating=false; if (!MPDConnection::self()->getDetails().dir.isEmpty() && !currentSong.file.isEmpty() && !currentSong.isNonMPD()) { QString songFile=actualFile(currentSong); QString mpdLyrics=mpdLyricsFilePath(songFile); if (MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http:/"))) { QUrl url(mpdLyrics); job=NetworkAccessManager::self()->get(url); job->setProperty("file", currentSong.file); connect(job, SIGNAL(finished()), this, SLOT(downloadFinished())); return; } else { #ifdef TAGLIB_FOUND QString tagLyrics=Tags::readLyrics(MPDConnection::self()->getDetails().dir+songFile); if (!tagLyrics.isEmpty()) { text->setText(fixNewLines(tagLyrics)); setMode(Mode_Display); // controls->setVisible(false); return; } #endif // Stop here if we found lyrics in the cache dir. if (setLyricsFromFile(mpdLyrics)) { lyricsFile=mpdLyrics; setMode(Mode_Display); return; } // Try .txt extension mpdLyrics=Utils::changeExtension(mpdLyrics, ".txt"); if (setLyricsFromFile(mpdLyrics)) { lyricsFile=mpdLyrics; setMode(Mode_Display); return; } } } // Check for cached file... QString file=lyricsCacheFileName(currentSong); if (setLyricsFromFile(file)) { // We just wanted a normal update without explicit re-fetching. We can return // here because we got cached lyrics and we don't want an explicit re-fetch. lyricsFile=file; setMode(Mode_Display); return; } file=Utils::changeExtension(file, ".txt"); if (setLyricsFromFile(file)) { // We just wanted a normal update without explicit re-fetching. We can return // here because we got cached lyrics and we don't want an explicit re-fetch. lyricsFile=file; setMode(Mode_Display); return; } #if !defined Q_OS_WIN && !defined Q_OS_MAC file=lyricsOtherFileName(currentSong); if (setLyricsFromFile(file)) { // We just wanted a normal update without explicit re-fetching. We can return // here because we got cached lyrics and we don't want an explicit re-fetch. lyricsFile=file; setMode(Mode_Display); return; } #endif getLyrics(); } void SongView::loadInfo() { if (!infoNeedsUpdating) { return; } infoNeedsUpdating=false; foreach (const QString &lang, engine->getLangs()) { QString prefix=engine->getPrefix(lang); QString cachedFile=infoCacheFileName(currentSong, prefix, false); if (QFile::exists(cachedFile)) { QFile f(cachedFile); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { QByteArray data=compressor.readAll(); if (!data.isEmpty()) { infoSearchResponse(QString::fromUtf8(data), QString()); Utils::touchFile(cachedFile); return; } } } } searchForInfo(); } static inline QString fixNewLine(QString s) { return s.replace(QLatin1String("\n"), QLatin1String("
    ")); } static QString createRow(const QString &key, const QString &value) { return value.isEmpty() ? QString() : QString("%1: %2").arg(key).arg(fixNewLine(value)); } struct MapEntry { MapEntry(int v=0, const QString &s=QString()) : val(v), str(s) { } int val; QString str; }; #ifdef TAGLIB_FOUND static QString clean(QString v) { if (v.length()>1) { v.replace("_", " "); v=v[0]+v.toLower().mid(1); } return v; } #endif void SongView::loadMetadata() { if (!metadataNeedsUpdating) { return; } metadataNeedsUpdating=false; QMultiMap tags; QMultiMap audioProperties; #ifdef TAGLIB_FOUND if (!currentSong.isStandardStream() && !MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http:/"))) { QString songFile=actualFile(currentSong); if (!songFile.isEmpty()) { static QMap tagMap; static QMap tagTimeMap; static const QString constTitle=QLatin1String("TITLE"); static const QString constPerformer=QLatin1String("PERFORMER:"); static const QString constAudio=QLatin1String("X-AUDIO:"); if (tagMap.isEmpty()) { int pos=0; tagMap.insert(QLatin1String("ARTIST"), MapEntry(pos++, tr("Artist"))); tagMap.insert(QLatin1String("ALBUMARTIST"), MapEntry(pos++, tr("Album artist"))); tagMap.insert(QLatin1String("COMPOSER"), MapEntry(pos++, tr("Composer"))); pos++;// For performer... tagMap.insert(QLatin1String("LYRICIST"), MapEntry(pos++, tr("Lyricist"))); tagMap.insert(QLatin1String("CONDUCTOR"), MapEntry(pos++, tr("Conductor"))); tagMap.insert(QLatin1String("REMIXER"), MapEntry(pos++, tr("Remixer"))); tagMap.insert(QLatin1String("ALBUM"), MapEntry(pos++, tr("Album"))); tagMap.insert(QLatin1String("SUBTITLE"), MapEntry(pos++, tr("Subtitle"))); tagMap.insert(QLatin1String("TRACKNUMBER"), MapEntry(pos++, tr("Track number"))); tagMap.insert(QLatin1String("DISCNUMBER"), MapEntry(pos++, tr("Disc number"))); tagMap.insert(QLatin1String("GENRE"), MapEntry(pos++, tr("Genre"))); tagMap.insert(QLatin1String("DATE"), MapEntry(pos++, tr("Date"))); tagMap.insert(QLatin1String("ORIGINALDATE"), MapEntry(pos++, tr("Original date"))); tagMap.insert(QLatin1String("COMMENT"), MapEntry(pos++, tr("Comment"))); tagMap.insert(QLatin1String("COPYRIGHT"), MapEntry(pos++, tr("Copyright"))); tagMap.insert(QLatin1String("LABEL"), MapEntry(pos++, tr("Label"))); tagMap.insert(QLatin1String("CATALOGNUMBER"), MapEntry(pos++, tr("Catalogue number"))); tagMap.insert(QLatin1String("TITLESORT"), MapEntry(pos++, tr("Title sort"))); tagMap.insert(QLatin1String("ARTISTSORT"), MapEntry(pos++, tr("Artist sort"))); tagMap.insert(QLatin1String("ALBUMARTISTSORT"), MapEntry(pos++, tr("Album artist sort"))); tagMap.insert(QLatin1String("ALBUMSORT"), MapEntry(pos++, tr("Album sort"))); tagMap.insert(QLatin1String("ENCODEDBY"), MapEntry(pos++, tr("Encoded by"))); tagMap.insert(QLatin1String("ENCODING"), MapEntry(pos++, tr("Encoder"))); tagMap.insert(QLatin1String("MOOD"), MapEntry(pos++, tr("Mood"))); tagMap.insert(QLatin1String("MEDIA"), MapEntry(pos++, tr("Media"))); tagMap.insert(constAudio+QLatin1String("BITRATE"), MapEntry(pos++, tr("Bitrate"))); tagMap.insert(constAudio+QLatin1String("SAMPLERATE"), MapEntry(pos++, tr("Sample rate"))); tagMap.insert(constAudio+QLatin1String("CHANNELS"), MapEntry(pos++, tr("Channels"))); tagTimeMap.insert(QLatin1String("TAGGING TIME"), MapEntry(pos++, tr("Tagging time"))); } QMap allTags=Tags::readAll(MPDConnection::self()->getDetails().dir+actualFile(currentSong)); if (!allTags.isEmpty()) { QMap::ConstIterator it=allTags.constBegin(); QMap::ConstIterator end=allTags.constEnd(); for (; it!=end; ++it) { if (it.key()==constTitle) { continue; } if (tagMap.contains(it.key())) { if (it.key().startsWith(constAudio)) { audioProperties.insert(tagMap[it.key()].val, createRow(tagMap[it.key()].str, it.value())); } else { tags.insert(tagMap[it.key()].val, createRow(tagMap[it.key()].str, it.value())); } } else if (tagTimeMap.contains(it.key())) { tags.insert(tagTimeMap[it.key()].val, createRow(tagTimeMap[it.key()].str, QString(it.value()).replace("T", " "))); } else if (it.key().startsWith(constPerformer)) { tags.insert(3, createRow(tr("Performer (%1)").arg(clean(it.key().mid(constPerformer.length()))), it.value())); } else { tags.insert(tagMap.count()+tagTimeMap.count(), createRow(clean(it.key()), it.value())); } } } } } #endif int audioPos=1024; if (audioProperties.isEmpty()) { if (MPDStatus::self()->bitrate()>0) { audioProperties.insert(audioPos++, createRow(tr("Bitrate"), tr("%1 kb/s").arg(MPDStatus::self()->bitrate()))); } if (MPDStatus::self()->samplerate()>0) { audioProperties.insert(audioPos++, createRow(tr("Sample rate"), tr("%1 Hz").arg(MPDStatus::self()->samplerate()))); } if (MPDStatus::self()->channels()>0) { audioProperties.insert(audioPos++, createRow(tr("Channels"), QString::number(MPDStatus::self()->channels()))); } } if (MPDStatus::self()->bits()>0) { audioProperties.insert(audioPos++, createRow(tr("Bits"), QString::number(MPDStatus::self()->bits()))); } if (tags.isEmpty()) { int pos=0; tags.insert(pos++, createRow(tr("Artist"), currentSong.artist)); tags.insert(pos++, createRow(tr("Album artist"), currentSong.albumartist)); tags.insert(pos++, createRow(tr("Composer"), currentSong.composer())); tags.insert(pos++, createRow(tr("Performer"), currentSong.performer())); tags.insert(pos++, createRow(tr("Album"), currentSong.album)); tags.insert(pos++, createRow(tr("Track number"), 0==currentSong.track ? QString() : QString::number(currentSong.track))); tags.insert(pos++, createRow(tr("Disc number"), 0==currentSong.disc ? QString() : QString::number(currentSong.disc))); tags.insert(pos++, createRow(tr("Genre"), currentSong.displayGenre())); tags.insert(pos++, createRow(tr("Year"), 0==currentSong.track ? QString() : QString::number(currentSong.year))); tags.insert(pos++, createRow(tr("Comment"), fixNewLine(currentSong.comment()))); } QString tagInfo; if (!tags.isEmpty()) { tagInfo=QLatin1String(""); QMultiMap::ConstIterator it=tags.constBegin(); QMultiMap::ConstIterator end=tags.constEnd(); for (; it!=end; ++it) { if (!it.value().isEmpty()) { tagInfo+=it.value(); } } } if (!audioProperties.isEmpty()) { if (tagInfo.isEmpty()) { tagInfo=QLatin1String("
    "); } else { tagInfo+=QLatin1String(""); } QMultiMap::ConstIterator it=audioProperties.constBegin(); QMultiMap::ConstIterator end=audioProperties.constEnd(); for (; it!=end; ++it) { if (!it.value().isEmpty()) { tagInfo+=it.value(); } } } if (tagInfo.isEmpty()) { tagInfo=QLatin1String("
    "); } else { tagInfo+=QLatin1String(""); } if (MPDConnection::self()->getDetails().dirReadable) { QString path=Utils::getDir(MPDConnection::self()->getDetails().dir+currentSong.filePath()); tagInfo+=createRow(tr("Filename"), QLatin1String("")+ currentSong.filePath()+QLatin1String("")); } else { tagInfo+=createRow(tr("Filename"), currentSong.filePath()); } tagInfo+=QLatin1String("
    "); setHtml(tagInfo, Page_Metadata); } void SongView::refreshInfo() { if (currentSong.isEmpty()) { return; } foreach (const QString &lang, engine->getLangs()) { QFile::remove(infoCacheFileName(currentSong, engine->getPrefix(lang), false)); } searchForInfo(); } void SongView::searchForInfo() { cancelInfoJobAction->setEnabled(true); engine->search(QStringList() << currentSong.artist << currentSong.title, ContextEngine::Track); showSpinner(false); } void SongView::infoSearchResponse(const QString &resp, const QString &lang) { cancelInfoJobAction->setEnabled(false); hideSpinner(); QString str; if (!resp.isEmpty()) { str=engine->translateLinks(resp); if (!lang.isEmpty()) { QFile f(infoCacheFileName(currentSong, lang, true)); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { compressor.write(resp.toUtf8().constData()); } } } setHtml(str, Page_Information); } void SongView::abortInfoSearch() { if (cancelInfoJobAction->isEnabled()) { cancelInfoJobAction->setEnabled(false); engine->cancel(); hideSpinner(); } } void SongView::showMoreInfo(const QUrl &url) { QDesktopServices::openUrl(url); } void SongView::hideSpinner() { if (!cancelInfoJobAction->isEnabled() && !cancelJobAction->isEnabled()) { View::hideSpinner(); } } void SongView::abort() { if (job) { job->cancelAndDelete(); job=0; } currentProvider=-1; if (currentProv) { currentProv->abort(); currentProv=0; text->setText(QString()); // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() ? mpdLyricsFilePath(currentSong) : lyricsCacheFileName(currentSong); setMode(Mode_Display); } cancelJobAction->setEnabled(false); hideSpinner(); } void SongView::update(const Song &s, bool force) { if (s.isEmpty() || s.title.isEmpty() || s.artist.isEmpty()) { currentSong=s; infoNeedsUpdating=metadataNeedsUpdating=lyricsNeedsUpdating=false; clear(); abort(); return; } Song song(s); bool songChanged = song.artist!=currentSong.artist || song.title!=currentSong.title; if (songChanged) { abort(); } if (!isVisible()) { if (songChanged) { needToUpdate=true; } currentSong=song; return; } if (force || songChanged) { setMode(Mode_Blank); // controls->setVisible(true); currentRequest++; currentSong=song; if (song.title.isEmpty() || song.artist.isEmpty()) { clear(); return; } // Only reset the provider if the refresh was an automatic one or if the song has // changed. Otherwise we'll keep the provider so the user can cycle through the lyrics // offered by the various providers. if (!force || songChanged) { currentProvider=-1; } infoNeedsUpdating=metadataNeedsUpdating=lyricsNeedsUpdating=true; setHeader(song.title); curentViewChanged(); } } void SongView::downloadFinished() { NetworkJob *reply=qobject_cast(sender()); if (reply) { reply->deleteLater(); if (job==reply) { job=0; if (reply->ok()) { QString file=reply->property("file").toString(); if (!file.isEmpty() && file==currentSong.file) { QTextStream str(reply->actualJob()); QString lyrics=str.readAll(); if (!lyrics.isEmpty()) { text->setText(fixNewLines(lyrics)); cancelJobAction->setEnabled(false); hideSpinner(); return; } } } } } getLyrics(); } void SongView::lyricsReady(int id, QString lyrics) { if (id != currentRequest) { return; } lyrics=lyrics.trimmed(); if (lyrics.isEmpty()) { getLyrics(); } else { cancelJobAction->setEnabled(false); hideSpinner(); QString before=text->toHtml(); text->setText(fixNewLines(lyrics)); // Remove formatting, as we dont save this anyway... QString plain=text->toPlainText().trimmed(); if (plain.isEmpty()) { text->setText(before); getLyrics(); } else { text->setText(fixNewLines(plain)); lyricsFile=QString(); if (! ( Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() && saveFile(mpdLyricsFilePath(currentSong))) ) { saveFile(lyricsCacheFileName(currentSong, true)); } setMode(Mode_Display); } } } bool SongView::saveFile(const QString &fileName) { QFile f(fileName); if (f.open(QIODevice::WriteOnly)) { QTextStream(&f) << text->toPlainText(); f.close(); lyricsFile=fileName; return true; } return false; } QString SongView::mpdFileName() const { return currentSong.file.isEmpty() || MPDConnection::self()->getDetails().dir.isEmpty() || currentSong.isNonMPD() ? QString() : mpdLyricsFilePath(currentSong); } QString SongView::cacheFileName() const { return currentSong.artist.isEmpty() || currentSong.title.isEmpty() ? QString() : lyricsCacheFileName(currentSong); } void SongView::getLyrics() { currentProv=UltimateLyrics::self()->getNext(currentProvider); if (currentProv) { text->setText(tr("Fetching lyrics via %1").arg(currentProv->displayName())); currentProv->fetchInfo(currentRequest, currentSong); showSpinner(); } else { text->setText(QString()); currentProvider=-1; // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() ? mpdLyricsFilePath(currentSong) : lyricsCacheFileName(currentSong); setMode(Mode_Display); } } void SongView::setMode(Mode m) { if (Mode_Display==m) { cancelJobAction->setEnabled(false); hideSpinner(); } if (mode==m) { return; } mode=m; bool fileExists=Mode_Display==m && !lyricsFile.isEmpty() && QFileInfo(lyricsFile).isWritable(); delAction->setEnabled(fileExists); refreshAction->setEnabled(fileExists); if (scrollAction->isChecked()) { if (Mode_Display==mode) { scroll(); } else if (scrollTimer) { scrollTimer->stop(); } } } bool SongView::setLyricsFromFile(const QString &filePath) { QFile f(filePath); if (f.exists() && f.open(QIODevice::ReadOnly)) { // Read the file using a QTextStream so we get automatic UTF8 detection. QTextStream inputStream(&f); text->setText(fixNewLines(inputStream.readAll())); cancelJobAction->setEnabled(false); hideSpinner(); f.close(); return true; } return false; } cantata-2.2.0/context/songview.h000066400000000000000000000064621316350454000166350ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SONG_VIEW_H #define SONG_VIEW_H #include #include "view.h" #include "config.h" class UltimateLyricsProvider; class QImage; class Action; class NetworkJob; class QTimer; class ContextEngine; class SongView : public View { Q_OBJECT enum Mode { Mode_Blank, Mode_Display }; enum Pages { Page_Lyrics, Page_Information, Page_Metadata }; public: static const QLatin1String constLyricsDir; static const QLatin1String constExtension; static const QLatin1String constCacheDir; static const QLatin1String constInfoExt; SongView(QWidget *p); ~SongView(); void update(const Song &s, bool force=false); void saveConfig(); Q_SIGNALS: void providersUpdated(); public Q_SLOTS: void downloadFinished(); void lyricsReady(int, QString lyrics); void update(); void search(); void edit(); void del(); void showContextMenu(const QPoint &pos); void showInfoContextMenu(const QPoint &pos); private Q_SLOTS: void toggleScroll(); void songPosition(); void scroll(); void curentViewChanged(); void refreshInfo(); void infoSearchResponse(const QString &resp, const QString &lang); void abortInfoSearch(); void showMoreInfo(const QUrl &url); private: void loadLyrics(); void loadInfo(); void loadMetadata(); void searchForInfo(); void hideSpinner(); void abort(); QString mpdFileName() const; QString cacheFileName() const; void getLyrics(); void setMode(Mode m); bool saveFile(const QString &fileName); /** * Reads the lyrics from the given filePath and updates * the UI with those lyrics. * * @param filePath The path to the lyrics file which will be read. * * @return Returns true if the file could be read; otherwise false. */ bool setLyricsFromFile(const QString &filePath); private: QTimer *scrollTimer; qint32 songPos; int currentProvider; int currentRequest; Action *scrollAction; Action *refreshAction; Action *searchAction; Action *editAction; Action *saveAction; Action *cancelEditAction; Action *delAction; Mode mode; QString lyricsFile; QString preEdit; NetworkJob *job; UltimateLyricsProvider *currentProv; bool lyricsNeedsUpdating; bool infoNeedsUpdating; bool metadataNeedsUpdating; Action *refreshInfoAction; Action *cancelInfoJobAction; ContextEngine *engine; }; #endif cantata-2.2.0/context/togglelist.cpp000066400000000000000000000072471316350454000175060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "togglelist.h" #include "gui/settings.h" #include "widgets/basicitemdelegate.h" #include "widgets/icons.h" #include "support/utils.h" ToggleList::ToggleList(QWidget *p) : QWidget(p) { setupUi(this); connect(upButton, SIGNAL(clicked()), SLOT(moveUp())); connect(downButton, SIGNAL(clicked()), SLOT(moveDown())); connect(addButton, SIGNAL(clicked()), SLOT(add())); connect(removeButton, SIGNAL(clicked()), SLOT(remove())); connect(available, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(availableChanged(QListWidgetItem*))); connect(selected, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(selectedChanged(QListWidgetItem*))); upButton->setIcon(Icons::self()->upIcon); downButton->setIcon(Icons::self()->downIcon); bool rtl=isRightToLeft(); addButton->setIcon(rtl ? Icons::self()->leftIcon : Icons::self()->rightIcon); removeButton->setIcon(rtl ? Icons::self()->rightIcon : Icons::self()->leftIcon); upButton->setEnabled(false); downButton->setEnabled(false); addButton->setEnabled(false); removeButton->setEnabled(false); available->setAlternatingRowColors(false); available->setItemDelegate(new BasicItemDelegate(available)); selected->setAlternatingRowColors(false); selected->setItemDelegate(new BasicItemDelegate(selected)); } void ToggleList::moveUp() { move(-1); } void ToggleList::moveDown() { move(+1); } void ToggleList::add() { int index=available->currentRow(); if (index<0 || index>available->count()) { return; } QListWidgetItem *item = available->takeItem(index); QListWidgetItem *newItem=new QListWidgetItem(selected); newItem->setText(item->text()); newItem->setData(Qt::UserRole, item->data(Qt::UserRole)); delete item; } void ToggleList::remove() { int index=selected->currentRow(); if (index<0 || index>selected->count()) { return; } QListWidgetItem *item = selected->takeItem(index); QListWidgetItem *newItem=new QListWidgetItem(available); newItem->setText(item->text()); newItem->setData(Qt::UserRole, item->data(Qt::UserRole)); delete item; } void ToggleList::move(int d) { const int row = selected->currentRow(); QListWidgetItem *item = selected->takeItem(row); selected->insertItem(row + d, item); selected->setCurrentRow(row + d); } void ToggleList::availableChanged(QListWidgetItem *item) { addButton->setEnabled(item); } void ToggleList::selectedChanged(QListWidgetItem *item) { if (!item) { upButton->setEnabled(false); downButton->setEnabled(false); removeButton->setEnabled(false); } else { const int row = available->row(item); upButton->setEnabled(row != 0); downButton->setEnabled(row != available->count() - 1); } removeButton->setEnabled(item); } cantata-2.2.0/context/togglelist.h000066400000000000000000000024011316350454000171360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TOGGLELIST_H #define TOGGLELIST_H #include "ui_togglelist.h" class Spinner; class ToggleList : public QWidget, protected Ui::ToggleList { Q_OBJECT public: ToggleList(QWidget *p); protected Q_SLOTS: void moveUp(); void moveDown(); void move(int d); void add(); void remove(); void availableChanged(QListWidgetItem *item); void selectedChanged(QListWidgetItem *item); }; #endif cantata-2.2.0/context/togglelist.ui000066400000000000000000000064711316350454000173370ustar00rootroot00000000000000 ToggleList 0 0 391 296 Available: Selected: true Qt::Vertical 20 74 false Qt::Vertical 20 89 Qt::Vertical 20 73 Qt::Vertical 20 73 true available addButton removeButton selected upButton downButton FlatToolButton QToolButton
    support/flattoolbutton.h
    cantata-2.2.0/context/ultimatelyrics.cpp000066400000000000000000000147271316350454000204040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #include "ultimatelyrics.h" #include "ultimatelyricsprovider.h" #include "gui/settings.h" #include "support/globalstatic.h" #include #include #include #include #include GLOBAL_STATIC(UltimateLyrics, instance) static bool compareLyricProviders(const UltimateLyricsProvider *a, const UltimateLyricsProvider *b) { return a->getRelevance() < b->getRelevance(); } static QString parseInvalidIndicator(QXmlStreamReader *reader) { QString ret = reader->attributes().value("value").toString(); reader->skipCurrentElement(); return ret; } static UltimateLyricsProvider::Rule parseRule(QXmlStreamReader *reader) { UltimateLyricsProvider::Rule ret; while (!reader->atEnd()) { reader->readNext(); if (QXmlStreamReader::EndElement==reader->tokenType()) { break; } if (QXmlStreamReader::StartElement==reader->tokenType()) { if (QLatin1String("item")==reader->name()) { QXmlStreamAttributes attr = reader->attributes(); if (attr.hasAttribute("tag")) { ret << UltimateLyricsProvider::RuleItem(attr.value("tag").toString(), QString()); } else if (attr.hasAttribute("begin")) { ret << UltimateLyricsProvider::RuleItem(attr.value("begin").toString(), attr.value("end").toString()); } } reader->skipCurrentElement(); } } return ret; } static UltimateLyricsProvider * parseProvider(QXmlStreamReader *reader) { QXmlStreamAttributes attributes = reader->attributes(); UltimateLyricsProvider* scraper = new UltimateLyricsProvider; scraper->setName(attributes.value("name").toString()); scraper->setCharset(attributes.value("charset").toString()); scraper->setUrl(attributes.value("url").toString()); while (!reader->atEnd()) { reader->readNext(); if (QXmlStreamReader::EndElement==reader->tokenType()) { break; } if (QXmlStreamReader::StartElement==reader->tokenType()) { if (QLatin1String("extract")==reader->name()) { scraper->addExtractRule(parseRule(reader)); } else if (QLatin1String("exclude")==reader->name()) { scraper->addExcludeRule(parseRule(reader)); } else if (QLatin1String("invalidIndicator")==reader->name()) { scraper->addInvalidIndicator(parseInvalidIndicator(reader)); } else if (QLatin1String("urlFormat")==reader->name()) { scraper->addUrlFormat(reader->attributes().value("replace").toString(), reader->attributes().value("with").toString()); reader->skipCurrentElement(); } else { reader->skipCurrentElement(); } } } return scraper; } void UltimateLyrics::release() { foreach (UltimateLyricsProvider *provider, providers) { delete provider; } providers.clear(); } const QList UltimateLyrics::getProviders() { load(); return providers; } UltimateLyricsProvider * UltimateLyrics::providerByName(const QString &name) const { foreach (UltimateLyricsProvider *provider, providers) { if (provider->getName() == name) { return provider; } } return 0; } UltimateLyricsProvider * UltimateLyrics::getNext(int &index) { load(); index++; if (index>-1 && indexisEnabled()) { index=i; return providers.at(i); } } } return 0; } void UltimateLyrics::load() { if (!providers.isEmpty()) { return; } QStringList dirs=QStringList() << Utils::dataDir() << CANTATA_SYS_CONFIG_DIR; QSet providerNames; foreach (const QString &d, dirs) { if (d.isEmpty()) { continue; } QFileInfoList files=QDir(d).entryInfoList(QStringList() << QLatin1String("lyrics_*.xml"), QDir::NoDotAndDotDot|QDir::Files); foreach (const QFileInfo &f, files) { QFile file(f.absoluteFilePath()); if (file.open(QIODevice::ReadOnly)) { QXmlStreamReader reader(&file); while (!reader.atEnd()) { reader.readNext(); if (QLatin1String("provider")==reader.name()) { QString name=reader.attributes().value("name").toString(); if (!providerNames.contains(name)) { UltimateLyricsProvider *provider = parseProvider(&reader); if (provider) { providers << provider; connect(provider, SIGNAL(lyricsReady(int,QString)), this, SIGNAL(lyricsReady(int,QString))); providerNames.insert(name); } } } } } } } setEnabled(Settings::self()->lyricProviders()); } void UltimateLyrics::setEnabled(const QStringList &enabled) { foreach (UltimateLyricsProvider *provider, providers) { provider->setEnabled(false); provider->setRelevance(0xFFFF); } int relevance=0; foreach (const QString &p, enabled) { UltimateLyricsProvider *provider=providerByName(p); if (provider) { provider->setEnabled(true); provider->setRelevance(relevance++); } } qSort(providers.begin(), providers.end(), compareLyricProviders); Settings::self()->saveLyricProviders(enabled); } cantata-2.2.0/context/ultimatelyrics.h000066400000000000000000000027101316350454000200360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #ifndef ULTIMATELYRICS_H #define ULTIMATELYRICS_H #include class UltimateLyricsProvider; class UltimateLyrics : public QObject { Q_OBJECT public: static UltimateLyrics * self(); UltimateLyrics() { } UltimateLyricsProvider * getNext(int &index); const QList getProviders(); void release(); void setEnabled(const QStringList &enabled); Q_SIGNALS: void lyricsReady(int id, const QString &data); private: UltimateLyricsProvider * providerByName(const QString &name) const; void load(); private: QList providers; }; #endif // ULTIMATELYRICS_H cantata-2.2.0/context/ultimatelyricsprovider.cpp000066400000000000000000000324511316350454000221510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #include "ultimatelyricsprovider.h" #include "network/networkaccessmanager.h" #include #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << "Lyrics" << __FUNCTION__ void UltimateLyricsProvider::enableDebug() { debugEnabled=true; } static const QString constArtistArg=QLatin1String("{Artist}"); static const QString constArtistLowerArg=QLatin1String("{artist}"); static const QString constArtistLowerNoSpaceArg=QLatin1String("{artist2}"); static const QString constArtistFirstCharArg=QLatin1String("{a}"); static const QString constAlbumArg=QLatin1String("{Album}"); static const QString constAlbumLowerArg=QLatin1String("{album}"); static const QString constAlbumLowerNoSpaceArg=QLatin1String("{album2}"); static const QString constTitleLowerArg=QLatin1String("{title}"); static const QString constTitleArg=QLatin1String("{Title}"); static const QString constTitleCaseArg=QLatin1String("{Title2}"); static const QString constYearArg=QLatin1String("{year}"); static const QString constTrackNoArg=QLatin1String("{track}"); static QString noSpace(const QString &text) { QString ret(text); ret.remove(' '); return ret; } static QString firstChar(const QString &text) { return text.isEmpty() ? text : text[0].toLower(); } static QString titleCase(const QString &text) { if (0==text.length()) { return QString(); } if (1==text.length()) { return text[0].toUpper(); } return text[0].toUpper() + text.right(text.length() - 1).toLower(); } static QString doTagReplace(QString str, const Song &song) { if (str.contains(QLatin1Char('{'))) { QString artistFixed=song.basicArtist(); str.replace(constArtistArg, artistFixed); str.replace(constArtistFirstCharArg, firstChar(artistFixed)); str.replace(constAlbumArg, song.album); str.replace(constTitleArg, song.title); str.replace(constYearArg, QString::number(song.year)); str.replace(constTrackNoArg, QString::number(song.track)); } return str; } static QString extract(const QString &source, const QString &begin, const QString &end, bool isTag=false) { DBUG << "Looking for" << begin << end; int beginIdx = source.indexOf(begin, 0, Qt::CaseInsensitive); bool skipTagClose=false; if (-1==beginIdx && isTag) { beginIdx = source.indexOf(QString(begin).remove(">"), 0, Qt::CaseInsensitive); skipTagClose=true; } if (-1==beginIdx) { DBUG << "Failed to find begin"; return QString(); } if (skipTagClose) { int closeIdx=source.indexOf(">", beginIdx); if (-1!=closeIdx) { beginIdx=closeIdx+1; } else { beginIdx += begin.length(); } } else { beginIdx += begin.length(); } int endIdx = source.indexOf(end, beginIdx, Qt::CaseInsensitive); if (-1==endIdx && QLatin1String("null")!=end) { DBUG << "Failed to find end"; return QString(); } DBUG << "Found match"; return source.mid(beginIdx, endIdx - beginIdx - 1); } static QString extractXmlTag(const QString &source, const QString &tag) { DBUG << "Looking for" << tag; QRegExp re("<(\\w+).*>"); // ಠ_ಠ if (-1==re.indexIn(tag)) { DBUG << "Failed to find tag"; return QString(); } DBUG << "Found match"; return extract(source, tag, "", true); } static QString exclude(const QString &source, const QString &begin, const QString &end) { int beginIdx = source.indexOf(begin, 0, Qt::CaseInsensitive); if (-1==beginIdx) { return source; } int endIdx = source.indexOf(end, beginIdx + begin.length(), Qt::CaseInsensitive); if (-1==endIdx) { return source; } return source.left(beginIdx) + source.right(source.length() - endIdx - end.length()); } static QString excludeXmlTag(const QString &source, const QString &tag) { QRegExp re("<(\\w+).*>"); // ಠ_ಠ if (-1==re.indexIn(tag)) { return source; } return exclude(source, tag, ""); } static void applyExtractRule(const UltimateLyricsProvider::Rule &rule, QString &content, const Song &song) { foreach (const UltimateLyricsProvider::RuleItem &item, rule) { if (item.second.isNull()) { content = extractXmlTag(content, doTagReplace(item.first, song)); } else { content = extract(content, doTagReplace(item.first, song), doTagReplace(item.second, song)); } } } static void applyExcludeRule(const UltimateLyricsProvider::Rule &rule, QString &content, const Song &song) { foreach (const UltimateLyricsProvider::RuleItem &item, rule) { if (item.second.isNull()) { content = excludeXmlTag(content, doTagReplace(item.first, song)); } else { content = exclude(content, doTagReplace(item.first, song), doTagReplace(item.second, song)); } } } static QString urlEncode(QString str) { str.replace(QLatin1Char('&'), QLatin1String("%26")); str.replace(QLatin1Char('?'), QLatin1String("%3f")); str.replace(QLatin1Char('+'), QLatin1String("%2b")); return str; } UltimateLyricsProvider::UltimateLyricsProvider() : enabled(true) , relevance(0) { } UltimateLyricsProvider::~UltimateLyricsProvider() { abort(); } QString UltimateLyricsProvider::displayName() const { QString n(name); n.replace("(POLISH)", tr("(Polish Translations)")); n.replace("(PORTUGUESE)", tr("(Portuguese Translations)")); return n; } void UltimateLyricsProvider::fetchInfo(int id, const Song &metadata) { const QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1().constData()); if (!codec) { emit lyricsReady(id, QString()); return; } QString artistFixed=metadata.basicArtist(); QString urlText(url); if (QLatin1String("lyrics.wikia.com")==name) { QUrl url(urlText); QUrlQuery query; query.addQueryItem(QLatin1String("artist"), artistFixed); query.addQueryItem(QLatin1String("song"), metadata.title); query.addQueryItem(QLatin1String("func"), QLatin1String("getSong")); query.addQueryItem(QLatin1String("fmt"), QLatin1String("xml")); url.setQuery(query); NetworkJob *reply = NetworkAccessManager::self()->get(url); requests[reply] = id; connect(reply, SIGNAL(finished()), this, SLOT(wikiMediaSearchResponse())); return; } songs.insert(id, metadata); // Fill in fields in the URL bool urlContainsDetails=urlText.contains(QLatin1Char('{')); if (urlContainsDetails) { doUrlReplace(constArtistArg, artistFixed, urlText); doUrlReplace(constArtistLowerArg, artistFixed.toLower(), urlText); doUrlReplace(constArtistLowerNoSpaceArg, noSpace(artistFixed.toLower()), urlText); doUrlReplace(constArtistFirstCharArg, firstChar(artistFixed), urlText); doUrlReplace(constAlbumArg, metadata.album, urlText); doUrlReplace(constAlbumLowerArg, metadata.album.toLower(), urlText); doUrlReplace(constAlbumLowerNoSpaceArg, noSpace(metadata.album.toLower()), urlText); doUrlReplace(constTitleArg, metadata.title, urlText); doUrlReplace(constTitleLowerArg, metadata.title.toLower(), urlText); doUrlReplace(constTitleCaseArg, titleCase(metadata.title), urlText); doUrlReplace(constYearArg, QString::number(metadata.year), urlText); doUrlReplace(constTrackNoArg, QString::number(metadata.track), urlText); } // For some reason Qt messes up the ? -> %3F and & -> %26 conversions - by placing 25 after the % // So, try and revert this... QUrl url(urlText); if (urlContainsDetails) { QByteArray data=url.toEncoded(); data.replace("%253F", "%3F"); data.replace("%253f", "%3f"); data.replace("%2526", "%26"); url=QUrl::fromEncoded(data, QUrl::StrictMode); } QNetworkRequest req(url); req.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0"); NetworkJob *reply = NetworkAccessManager::self()->get(req); requests[reply] = id; connect(reply, SIGNAL(finished()), this, SLOT(lyricsFetched())); } void UltimateLyricsProvider::abort() { QHash::ConstIterator it(requests.constBegin()); QHash::ConstIterator end(requests.constEnd()); for (; it!=end; ++it) { it.key()->cancelAndDelete(); } requests.clear(); songs.clear(); } void UltimateLyricsProvider::wikiMediaSearchResponse() { NetworkJob *reply = qobject_cast(sender()); if (!reply) { return; } int id = requests.take(reply); reply->deleteLater(); if (!reply->ok()) { emit lyricsReady(id, QString()); return; } QUrl url; QXmlStreamReader doc(reply->actualJob()); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("url")==doc.name()) { QString lyricsUrl=doc.readElementText(); if (!lyricsUrl.contains(QLatin1String("action=edit"))) { url=QUrl::fromEncoded(lyricsUrl.toUtf8()).toString(); } break; } } if (url.isValid()) { QString path=url.path(); QByteArray u=url.scheme().toLatin1()+"://"+url.host().toLatin1()+"/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles="; QByteArray titles=QUrl::toPercentEncoding(path.startsWith(QLatin1Char('/')) ? path.mid(1) : path).replace('+', "%2b"); NetworkJob *reply = NetworkAccessManager::self()->get(QUrl::fromEncoded(u+titles)); requests[reply] = id; connect(reply, SIGNAL(finished()), this, SLOT(wikiMediaLyricsFetched())); } else { emit lyricsReady(id, QString()); } } void UltimateLyricsProvider::wikiMediaLyricsFetched() { NetworkJob *reply = qobject_cast(sender()); if (!reply) { return; } int id = requests.take(reply); reply->deleteLater(); if (!reply->ok()) { emit lyricsReady(id, QString()); return; } const QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1().constData()); QString contents = codec->toUnicode(reply->readAll()).replace("
    ", "
    "); DBUG << name << "response" << contents; emit lyricsReady(id, extract(contents, QLatin1String("<lyrics>"), QLatin1String("</lyrics>"))); } void UltimateLyricsProvider::lyricsFetched() { NetworkJob *reply = qobject_cast(sender()); if (!reply) { return; } int id = requests.take(reply); reply->deleteLater(); Song song=songs.take(id); if (!reply->ok()) { //emit Finished(id); emit lyricsReady(id, QString()); return; } const QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1().constData()); const QString originalContent = codec->toUnicode(reply->readAll()).replace("
    ", "
    "); DBUG << name << "response" << originalContent; // Check for invalid indicators foreach (const QString &indicator, invalidIndicators) { if (originalContent.contains(indicator)) { //emit Finished(id); DBUG << name << "invalid"; emit lyricsReady(id, QString()); return; } } QString lyrics; // Apply extract rules foreach (const Rule& rule, extractRules) { QString content = originalContent; applyExtractRule(rule, content, song); #ifndef Q_OS_WIN content.replace(QLatin1String("\r"), QLatin1String("")); #endif content=content.trimmed(); if (!content.isEmpty()) { lyrics = content; break; } } // Apply exclude rules foreach (const Rule& rule, excludeRules) { applyExcludeRule(rule, lyrics, song); } lyrics=lyrics.trimmed(); lyrics.replace("
    \n", "
    "); lyrics.replace("
    \n", "
    "); DBUG << name << (lyrics.isEmpty() ? "empty" : "succeeded"); emit lyricsReady(id, lyrics); } void UltimateLyricsProvider::doUrlReplace(const QString &tag, const QString &value, QString &u) const { if (!u.contains(tag)) { return; } // Apply URL character replacement QString valueCopy(value); foreach (const UltimateLyricsProvider::UrlFormat& format, urlFormats) { QRegExp re("[" + QRegExp::escape(format.first) + "]"); valueCopy.replace(re, format.second); } u.replace(tag, urlEncode(valueCopy), Qt::CaseInsensitive); } cantata-2.2.0/context/ultimatelyricsprovider.h000066400000000000000000000054351316350454000216200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #ifndef ULTIMATELYRICSPROVIDER_H #define ULTIMATELYRICSPROVIDER_H #include "mpd-interface/song.h" #include #include #include #include #include class NetworkJob; class UltimateLyricsProvider : public QObject { Q_OBJECT public: static void enableDebug(); UltimateLyricsProvider(); virtual ~UltimateLyricsProvider(); typedef QPair RuleItem; typedef QList Rule; typedef QPair UrlFormat; void setName(const QString &n) { name = n; } void setUrl(const QString &u) { url = u; } void setCharset(const QString &c) { charset = c; } void setRelevance(int r) { relevance = r; } void addUrlFormat(const QString &replace, const QString &with) { urlFormats << UrlFormat(replace, with); } void addExtractRule(const Rule &rule) { extractRules << rule; } void addExcludeRule(const Rule &rule) { excludeRules << rule; } void addInvalidIndicator(const QString &indicator) { invalidIndicators << indicator; } QString getName() const { return name; } QString displayName() const; int getRelevance() const { return relevance; } void fetchInfo(int id, const Song &metadata); bool isEnabled() const { return enabled; } void setEnabled(bool e) { enabled = e; } void abort(); Q_SIGNALS: void lyricsReady(int id, const QString &data); private Q_SLOTS: void wikiMediaSearchResponse(); void wikiMediaLyricsFetched(); void lyricsFetched(); private: QString doTagReplace(QString str, const Song &song, bool doAll=true); void doUrlReplace(const QString &tag, const QString &value, QString &u) const; private: bool enabled; QHash requests; QMap songs; QString name; QString url; QString charset; int relevance; QList urlFormats; QList extractRules; QList excludeRules; QStringList invalidIndicators; }; #endif // ULTIMATELYRICSPROVIDER_H cantata-2.2.0/context/view.cpp000066400000000000000000000200141316350454000162660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "view.h" #include "support/spinner.h" #include "network/networkaccessmanager.h" #include "gui/settings.h" #include "widgets/textbrowser.h" #include "support/gtkstyle.h" #include "support/actioncollection.h" #include "support/action.h" #include "widgets/icons.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Uncomment this #define to have header labels an images centered. Thsi is disabled, as the image // centering only works if there is some text larger than 1 line to be displayed underneath :-( //#define CONTEXT_CENTERED static QString headerTag; QString View::subTag; QString View::encode(const QImage &img) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); img.save(&buffer, "PNG"); #ifdef CONTEXT_CENTERED return QString("").arg(QString(buffer.data().toBase64())); #else return QString("").arg(QString(buffer.data().toBase64())); #endif } void View::initHeaderTags() { bool small=Settings::self()->wikipediaIntroOnly() ; headerTag=small ? "h2" : "h1"; subTag=small ? "h3" : "h2"; } static TextBrowser * createView(QWidget *parent) { TextBrowser *text=new TextBrowser(parent); if (GtkStyle::isActive()) { text->verticalScrollBar()->setAttribute(Qt::WA_OpaquePaintEvent, false); } text->setOpenLinks(false); text->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); text->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); text->setFrameShape(QFrame::NoFrame); text->setReadOnly(true); text->viewport()->setAutoFillBackground(false); return text; } View::View(QWidget *parent, const QStringList &views) : QWidget(parent) , needToUpdate(false) , spinner(0) , selector(0) , stack(0) { QVBoxLayout *layout=new QVBoxLayout(this); header=new QLabel(this); if (views.isEmpty()) { TextBrowser *t=createView(this); texts.append(t); } else { stack=new QStackedWidget(this); selector=new SelectorLabel(this); selector->setUseArrow(true); foreach (const QString &v, views) { TextBrowser *t=createView(stack); selector->addItem(v, v); stack->addWidget(t); texts.append(t); } connect(selector, SIGNAL(activated(int)), stack, SLOT(setCurrentIndex(int))); connect(selector, SIGNAL(activated(int)), this, SIGNAL(viewChanged())); } header->setWordWrap(true); header->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); header->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard); layout->setMargin(2); layout->addWidget(header); if (views.isEmpty()) { layout->addWidget(texts.at(0)); } else { layout->addWidget(selector); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); layout->addWidget(stack); } layout->addItem(new QSpacerItem(1, fontMetrics().height()/4, QSizePolicy::Fixed, QSizePolicy::Fixed)); if (headerTag.isEmpty()) { initHeaderTags(); } cancelJobAction=new Action(Icons::self()->cancelIcon, tr("Cancel"), this); cancelJobAction->setEnabled(false); connect(cancelJobAction, SIGNAL(triggered()), SLOT(abort())); text=texts.at(0); } View::~View() { abort(); } void View::clear() { setHeader(stdHeader); foreach (TextBrowser *t, texts) { t->clear(); } } void View::setHeader(const QString &str) { #ifdef CONTEXT_CENTERED header->setText("<"+headerTag+" align=\"center\">"+str+""); #else header->setText("<"+headerTag+">"+str+""); #endif } void View::setPicSize(const QSize &sz) { foreach (TextBrowser *t, texts) { t->setPicSize(sz); } } QSize View::picSize() const { return text->picSize(); } QString View::createPicTag(const QImage &img, const QString &file) { if (!file.isEmpty() && QFile::exists(file)) { #ifdef CONTEXT_CENTERED return QString("").arg(file); #else return QString("").arg(file); #endif } if (img.isNull()) { return QString(); } // No filename given, or file does not exist - therefore encode & scale image. return encode(img.scaled(texts.at(0)->picSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void View::showEvent(QShowEvent *e) { if (needToUpdate) { update(currentSong, true); } needToUpdate=false; QWidget::showEvent(e); } void View::showSpinner(bool enableCancel) { if (!spinner) { spinner=new Spinner(this); spinner->setWidget(this); } if (!spinner->isActive()) { spinner->start(); if (enableCancel) { cancelJobAction->setEnabled(true); } } } void View::hideSpinner(bool disableCancel) { if (spinner) { spinner->stop(); if (disableCancel) { cancelJobAction->setEnabled(false); } } } void View::setEditable(bool e, int index) { TextBrowser *t=texts.at(index); t->setReadOnly(!e); t->viewport()->setAutoFillBackground(e); } void View::setPal(const QPalette &pal, const QColor &linkColor, const QColor &prevLinkColor) { foreach (TextBrowser *t, texts) { // QTextBrowser seems to save link colour within the HTML, so we need to manually // update this when the palette changes! QString old=t->toHtml(); t->setPal(pal); old=old.replace("color:"+prevLinkColor.name()+";", "color:"+linkColor.name()+";"); t->setHtml(old); } // header uses window/button text - so need to set these now... QPalette hdrPal=pal; hdrPal.setColor(QPalette::WindowText, pal.color(QPalette::Text)); hdrPal.setColor(QPalette::ButtonText, pal.color(QPalette::Text)); header->setPalette(hdrPal); if (selector) { selector->setPalette(hdrPal); } } void View::addEventFilter(QObject *obj) { installEventFilter(obj); foreach (TextBrowser *t, texts) { t->installEventFilter(obj); t->viewport()->installEventFilter(obj); } header->installEventFilter(obj); } void View::setZoom(int z) { foreach (TextBrowser *t, texts) { t->setZoom(z); } QFont f=header->font(); f.setPointSize(f.pointSize()+z); header->setFont(f); if (selector) { QFont f=selector->font(); f.setPointSize(f.pointSize()+z); selector->setFont(f); } } int View::getZoom() { return text->zoom(); } void View::setHtml(const QString &h, int index) { texts.at(index)->setText(QLatin1String("")+h+QLatin1String("")); } void View::abort() { } cantata-2.2.0/context/view.h000066400000000000000000000052371316350454000157450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef VIEW_H #define VIEW_H #include #include #include #include "mpd-interface/song.h" #include "widgets/selectorlabel.h" class QImage; class Spinner; class QNetworkReply; class QLayoutItem; class TextBrowser; class Action; class QStackedWidget; class QMenu; class View : public QWidget { Q_OBJECT public: static QString subTag; View(QWidget *p, const QStringList &views=QStringList()); virtual ~View(); static QString encode(const QImage &img); static QString subHeader(const QString &str) { return "<"+subTag+">"+str+""; } static void initHeaderTags(); void clear(); void setStandardHeader(const QString &h) { stdHeader=h; } void setHeader(const QString &str); void setPicSize(const QSize &sz); QSize picSize() const; QString createPicTag(const QImage &img, const QString &file); void showEvent(QShowEvent *e); void showSpinner(bool enableCancel=true); void hideSpinner(bool disableCancel=true); void setEditable(bool e, int index=0); void setPal(const QPalette &pal, const QColor &linkColor, const QColor &prevLinkColor); void addEventFilter(QObject *obj); void setZoom(int z); int getZoom(); virtual void update(const Song &s, bool force)=0; void setHtml(const QString &h, int index=0); int currentView() const { return selector ? selector->currentIndex() : -1; } void setCurrentView(int v) { selector->setCurrentIndex(v); } Q_SIGNALS: void viewChanged(); protected Q_SLOTS: virtual void abort(); protected: Song currentSong; QString stdHeader; QLabel *header; bool needToUpdate; Spinner *spinner; Action *cancelJobAction; SelectorLabel *selector; QStackedWidget *stack; TextBrowser *text; // short-cut to first text item... QList texts; }; #endif cantata-2.2.0/context/weblinks.xml000066400000000000000000000004561316350454000171600ustar00rootroot00000000000000 cantata-2.2.0/context/wikipediaengine.cpp000066400000000000000000000475341316350454000204700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "wikipediaengine.h" #include "network/networkaccessmanager.h" #include "gui/settings.h" #include "gui/covers.h" #include "config.h" #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void WikipediaEngine::enableDebug() { debugEnabled=true; } static const char * constModeProperty="mode"; static const char * constQueryProperty="query"; static QString wikipediaSpecialExport(const QString &lang) { static QMap links; if (links.isEmpty()) { links.insert(QLatin1String("de"), QString("Spezial:Exportieren")); links.insert(QLatin1String("ru"), QString("Служебная:Экспорт")); } QMap::ConstIterator it=links.find(lang); return "/"+(it==links.constEnd() ? QLatin1String("Special:Export") : it.value())+"/"; } static QString fixWikiLink(const QUrl &url) { QString lang=url.host().split(".").first(); QString path=url.path(); QString fixed(path); fixed=fixed.replace("/wiki"+wikipediaSpecialExport(lang), "/wiki/"); if (path==fixed) { QStringList parts=fixed.split("/", QString::SkipEmptyParts); if (parts.length()>1) { parts.removeAt(1); fixed=parts.join("/"); } } QUrl u(url); u.setPath(fixed); return u.toString(); } static QString strip(const QString &string, QString open, QString close, QString inner=QString()) { QString result; int next, /*lastLeft, */left = 0; int pos = string.indexOf(open, 0, Qt::CaseInsensitive); if (pos < 0) { return string; } if (inner.isEmpty()) { inner = open; } while (pos > -1) { result += string.mid(left, pos - left); // lastLeft = left; left = string.indexOf(close, pos); if (left < 0) { // opens, but doesn't close break; } else { next = pos; while (next > -1 && left > -1) { // search for inner iterations int count = 0; int lastNext = next; while ((next = string.indexOf(inner, next+inner.length())) < left && next > -1) { // count inner section openers lastNext = next; ++count; } next = lastNext; // set back next to last inside opener for next iteration if (!count) { // no inner sections, skip break; } for (int i = 0; i < count; ++i) { // shift section closers by inside section amount left = string.indexOf(close, left+close.length()); } // "continue" - search for next inner section } if (left < 0) { // section does not close, skip here break; } left += close.length(); // extend close to next search start } if (left < 0) { // section does not close, skip here break; } pos = string.indexOf(open, left); // search next 1st level section opener } if (left > -1) { // append last part result += string.mid(left); } return result; } static QString stripEmptySections(QString answer) { QStringList headers=QStringList() << "h3" << "h2" << "b"; foreach (const QString &h1, headers) { foreach (const QString &h2, headers) { int end=-1; do { end=answer.indexOf("<"+h2+">"); int realEnd=end+3+h1.length(); if (-1==end) { end=answer.indexOf("

    <"+h2+">"); realEnd=end+11+h1.length(); } if (-1!=end) { int start=answer.lastIndexOf("<"+h1+">", end); if (-1!=start) { answer=answer.left(start)+answer.mid(realEnd); } } } while(-1!=end); } } return answer; } static QString stripLastEmptySection(QString answer) { QStringList headers=QStringList() << "h3" << "h2" << "b"; bool modified=false; do { modified=false; foreach (const QString &h, headers) { if (answer.endsWith("") || answer.endsWith(" ") || answer.endsWith(" ")) { int start=answer.lastIndexOf("<"+h+">", answer.length()-4); if (-1!=start) { answer=answer.left(start); modified=true; } } } } while (modified); return answer; } static QString wikiToHtml(QString answer, bool introOnly, const QUrl &url) { QString u=fixWikiLink(url); int start = answer.indexOf('>', answer.indexOf(" start && e < end) { end = e; } e = answer.lastIndexOf(QRegExp("\n==\\s*Sources\\s*==")); if (e > start && e < end) { end = e; } e = answer.lastIndexOf(QRegExp("\n==\\s*Notes\\s*==")); if (e > start && e < end) { end = e; } e = answer.lastIndexOf(QRegExp("\n==\\s*References\\s*==")); if (e > start && e < end) { end = e; } e = answer.lastIndexOf(QRegExp("\n==\\s*External links\\s*==")); if (e > start && e < end) { end = e; } if (end < start) { end = answer.lastIndexOf(""); answer = strip(answer, ""); // strip comments answer.remove(QRegExp("]*/>")); // strip inline refereces answer = strip(answer, "", "", ""); answer.replace("\n\n", "
    "); answer.replace("( ; ", "("); // answer.replace("\n\n", "

    "); answer.replace(QRegExp("\\n'''([^\\n]*)'''\\n"), "


    \\1\n"); answer.replace(QRegExp("\\n\\{\\|[^\\n]*\\n"), "\n"); answer.replace(QRegExp("\\n\\|[^\\n]*\\n"), "\n"); answer.replace("\n*", "
    "); answer.replace("\n", ""); answer.replace("'''s ", "'s"); answer.replace("'''", "¬").replace(QRegExp("¬([^¬]*)¬"), "\\1"); answer.replace("''", "¬").replace(QRegExp("¬([^¬]*)¬"), "\\1"); if (!introOnly) { answer.replace("===", "¬").replace(QRegExp("¬([^¬]*)¬"), "

    \\1

    "); answer.replace("==", "¬").replace(QRegExp("¬([^¬]*)¬"), "

    \\1

    "); } answer.replace("&nbsp;", " "); answer.replace("–", "-"); answer.replace("
    ")) { answer+="
    "; } answer+=QString("
    %2").arg(u).arg(WikipediaEngine::constReadMorePlaceholder); } } else { answer.replace("
    ", ""); answer.replace("
    ", ""); answer.replace("

    =", "

    "); answer.replace("

    =", ""); answer.replace("br>;", "br>"); answer.replace("h2>;", "h2>"); answer.replace("h3>;", "h3>"); answer.replace("




    ", "

    "); answer.replace("



    ", "

    "); answer.replace("


    ", "

    "); // Remove track listings - we take these from MPD... QString listingText="

    "+QObject::tr("Track listing")+"

    "; start=answer.indexOf(listingText, 0, Qt::CaseInsensitive); if (-1!=start) { int end=answer.indexOf("

    ", start+listingText.length(), Qt::CaseInsensitive); if (start!=end) { answer=answer.left(start)+answer.mid(end); } } // Try to remove empty sections (that will have been reated because we removed tables!) answer=stripEmptySections(answer); answer=stripLastEmptySection(answer); } if (!introOnly) { if (!answer.endsWith("
    ")) { answer+="
    "; } answer+=QString("
    %2").arg(u).arg(WikipediaEngine::constOpenInBrowserPlaceholder); } return answer; } static inline QString getLang(const QUrl &url) { return url.host().remove(QLatin1String(".wikipedia.org")); } QStringList WikipediaEngine::preferredLangs; bool WikipediaEngine::introOnly=true; const QLatin1String WikipediaEngine::constReadMorePlaceholder("XXX_CONTEXT_READ_MORE_ON_WIKIPEDIA_XXX"); const QLatin1String WikipediaEngine::constOpenInBrowserPlaceholder("XXX_CONTEXT_OPEN_IN_BROWSER_WIKIPEDIA_XXX"); WikipediaEngine::WikipediaEngine(QObject *p) : ContextEngine(p) { if (preferredLangs.isEmpty()) { setPreferedLangs(Settings::self()->wikipediaLangs()); introOnly=Settings::self()->wikipediaIntroOnly(); } } void WikipediaEngine::setPreferedLangs(const QStringList &l) { preferredLangs=l; if (preferredLangs.isEmpty()) { preferredLangs.append("en"); } } QString WikipediaEngine::translateLinks(QString text) const { text=text.replace(constReadMorePlaceholder, QObject::tr("Read more on wikipedia")); text=text.replace(constOpenInBrowserPlaceholder, QObject::tr("Open in browser")); return text; } void WikipediaEngine::search(const QStringList &query, Mode mode) { titles.clear(); // if (Track==mode) { // emit searchResult(QString(), QString()); // return; // } requestTitles(fixQuery(query), mode, getPrefix(preferredLangs.first())); } void WikipediaEngine::requestTitles(const QStringList &query, Mode mode, const QString &lang) { cancel(); QUrl url("https://"+lang+".wikipedia.org/w/api.php"); QUrlQuery q; q.addQueryItem(QLatin1String("action"), QLatin1String("query")); q.addQueryItem(QLatin1String("list"), QLatin1String("search")); q.addQueryItem(QLatin1String("srsearch"), query.join(" ")); q.addQueryItem(QLatin1String("srprop"), QLatin1String("size")); q.addQueryItem(QLatin1String("srredirects"), QString::number(1)); q.addQueryItem(QLatin1String("srlimit"), QString::number(20)); q.addQueryItem(QLatin1String("format"), QLatin1String("xml")); url.setQuery(q); job=NetworkAccessManager::self()->get(url); job->setProperty(constModeProperty, (int)mode); job->setProperty(constQueryProperty, query); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(parseTitles())); } void WikipediaEngine::parseTitles() { DBUG << __FUNCTION__; NetworkJob *reply = getReply(sender()); if (!reply) { return; } QUrl url=reply->url(); QString hostLang=getLang(url); QByteArray data=reply->readAll(); if (!reply->ok() || data.isEmpty()) { DBUG << reply->errorString(); emit searchResult(QString(), QString()); return; } QStringList query = reply->property(constQueryProperty).toStringList(); Mode mode=(Mode)reply->property(constModeProperty).toInt(); QXmlStreamReader xml(data); while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); if (xml.isStartElement() && QLatin1String("search")==xml.name()) { while (xml.readNextStartElement()) { if (QLatin1String("p")==xml.name()) { if (xml.attributes().hasAttribute(QLatin1String("title"))) { titles << xml.attributes().value(QLatin1String("title")).toString(); } xml.skipCurrentElement(); } else { xml.skipCurrentElement(); } } } } if (titles.isEmpty()) { DBUG << "No titles"; QRegExp regex(QLatin1Char('^') + hostLang + QLatin1String(".*$")); int index = preferredLangs.indexOf(regex); if (-1!=index && index < preferredLangs.count()-1) { // use next preferred language as base for fetching langlinks since // the current one did not get any results we want. requestTitles(query, mode, getPrefix(preferredLangs.value(index+1))); } else { DBUG << "No more langs"; emit searchResult(QString(), QString()); } return; } DBUG << titles; getPage(query, mode, hostLang); } static int indexOf(const QStringList &l, const QString &s) { QString search=s.simplified(); for (int i=0; i replacements; replacements.insert(QLatin1String("."), QString()); // A.S.A.P. -> ASAP replacements.insert(QLatin1String("-"), QLatin1String("/")); // AC-DC -> AC/DC QMap::ConstIterator repEnd=replacements.constEnd(); while (!queryCopy.isEmpty()) { QString q=queryCopy.join(" "); queries.append(q); for (QMap::ConstIterator rep=replacements.constBegin(); rep!=repEnd; ++rep) { QString q2=q; q2.replace(rep.key(), rep.value()); if (q2!=q) { queries.append(q2); } } queryCopy.takeFirst(); } QStringList patterns; QStringList englishPatterns; switch (mode) { default: case Artist: patterns=tr("artist|band|singer|vocalist|musician", "Search pattern for an artist or band, separated by |").split("|", QString::SkipEmptyParts); englishPatterns=QString(QLatin1String("artist|band|singer|vocalist|musician")).split("|"); break; case Album: patterns=tr("album|score|soundtrack", "Search pattern for an album, separated by |").split("|", QString::SkipEmptyParts); englishPatterns=QString(QLatin1String("album|score|soundtrack")).split("|"); break; case Track: // patterns=trc("Search pattern for a song, separated by |", "song|track").split("|", QString::SkipEmptyParts); // englishPatterns=QString(QLatin1String("song|track")).split("|"); break; } foreach (const QString &eng, englishPatterns) { if (!patterns.contains(eng)) { patterns.append(eng); } } DBUG << "Titles" << titles; int index=-1; if ((mode==Album || mode==Track) && 2==query.count()) { DBUG << "Check track/album"; foreach (const QString &pattern, patterns) { QString q=query.at(1)+" ("+query.at(0)+" "+pattern+")"; DBUG << "Try" << q; index=indexOf(simplifiedTitles, q); if (-1!=index) { DBUG << "Matched with '$album/$track ($artist pattern)" << index << q; break; } } } if (-1==index) { foreach (const QString &q, queries) { DBUG << "Query" << q; // First check original query with one of the patterns... foreach (const QString &pattern, patterns) { index=indexOf(simplifiedTitles, q+" ("+pattern+")"); if (-1!=index) { DBUG << "Matched with pattern" << index << QString(q+" ("+pattern+")"); break; } } if (-1==index) { // Try without any pattern... index=indexOf(simplifiedTitles, q); if (-1!=index) { DBUG << "Matched without pattern" << index << q; } } if (-1!=index) { break; } } } // TODO: If we fail to find a match, prompt user??? if (-1==index) { DBUG << "Failed to find match"; emit searchResult(QString(), QString()); return; } const QString title=titles.takeAt(index); if (QLatin1String("List of CJK Unified Ideographs")==title) { DBUG << "Unicode list?"; emit searchResult(QString(), QString()); return; } QUrl url; url.setScheme(QLatin1String("https")); url.setHost(lang+".wikipedia.org"); url.setPath("/wiki"+wikipediaSpecialExport(lang)+title); job=NetworkAccessManager::self()->get(url); job->setProperty(constModeProperty, (int)mode); job->setProperty(constQueryProperty, query); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(parsePage())); } void WikipediaEngine::parsePage() { DBUG << __FUNCTION__; NetworkJob *reply = getReply(sender()); if (!reply) { return; } QByteArray data=reply->readAll(); if (!reply->ok() || data.isEmpty()) { DBUG << "Empty/error"; emit searchResult(QString(), QString()); return; } QString answer(QString::fromUtf8(data)); //DBUG << "Anser" << answer; QUrl url=reply->url(); QString hostLang=getLang(url); QStringList query=reply->property(constQueryProperty).toStringList(); Mode mode=(Mode)reply->property(constModeProperty).toInt(); if (answer.contains(QLatin1String("{{disambiguation}}")) || answer.contains(QLatin1String("{{disambig}}"))) { // tr??? getPage(query, mode, hostLang); return; } if (answer.isEmpty()) { emit searchResult(QString(), QString()); return; } QString resp=wikiToHtml(answer, introOnly, reply->url()); if (introOnly && resp.isEmpty()) { resp=wikiToHtml(answer, false, reply->url()); } // For track results, ensure response contains artist name! if (Track==mode && !resp.contains(query.at(0), Qt::CaseInsensitive) && !resp.contains(Covers::fixArtist(query.at(0)), Qt::CaseInsensitive)) { getPage(query, mode, hostLang); } else { emit searchResult(resp, hostLang); } } cantata-2.2.0/context/wikipediaengine.h000066400000000000000000000037551316350454000201320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef WIKIPEDIA_ENGINE_H #define WIKIPEDIA_ENGINE_H #include "contextengine.h" #include class WikipediaEngine : public ContextEngine { Q_OBJECT public: static void enableDebug(); WikipediaEngine(QObject *p); static const QLatin1String constReadMorePlaceholder; static const QLatin1String constOpenInBrowserPlaceholder; static void setPreferedLangs(const QStringList &l); static const QStringList & getPreferedLangs() { return preferredLangs; } static void setIntroOnly(bool v) { introOnly=v; } QString translateLinks(QString text) const; QStringList getLangs() const { return getPreferedLangs(); } QString getPrefix(const QString &key) const { return key.split(QLatin1Char(':')).back(); } public Q_SLOTS: void search(const QStringList &query, Mode mode); private: void requestTitles(const QStringList &query, Mode mode, const QString &lang); void getPage(const QStringList &query, Mode mode, const QString &lang); private Q_SLOTS: void parseTitles(); void parsePage(); private: static QStringList preferredLangs; static bool introOnly; QStringList titles; }; #endif cantata-2.2.0/context/wikipediasettings.cpp000066400000000000000000000164771316350454000210650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "wikipediasettings.h" #include "wikipediaengine.h" #include "network/networkaccessmanager.h" #include "support/icon.h" #include "support/spinner.h" #include "gui/settings.h" #include "qtiocompressor/qtiocompressor.h" #include "support/utils.h" #include "support/action.h" #include "support/thread.h" #include #include #include static const QString constOldFileName=QLatin1String("wikipedia-available.xml.gz"); static const QString constFileName=QLatin1String("languages.xml.gz"); QString WikipediaSettings::constSubDir=QLatin1String("wikipedia"); static QString localeFile() { return Utils::cacheDir(WikipediaSettings::constSubDir, true)+constFileName; } WikipediaLoader::WikipediaLoader() : QObject(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } WikipediaLoader::~WikipediaLoader() { thread->stop(); } void WikipediaLoader::load(const QByteArray &data) { QStringList preferred=WikipediaEngine::getPreferedLangs(); QXmlStreamReader xml(data); while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); if( xml.isStartElement() && QLatin1String("iw")==xml.name()) { const QXmlStreamAttributes &a = xml.attributes(); if (a.hasAttribute(QLatin1String("prefix")) && a.hasAttribute(QLatin1String("language")) && a.hasAttribute(QLatin1String("url"))) { // The urlPrefix is the lang code infront of the wikipedia host // url. It is mostly the same as the "prefix" attribute but in // some weird cases they differ, so we can't just use "prefix". QString prefix=a.value(QLatin1String("prefix")).toString(); QString urlPrefix=QUrl(a.value(QLatin1String("url")).toString()).host().remove(QLatin1String(".wikipedia.org")); emit entry(prefix, urlPrefix, a.value(QLatin1String("language")).toString(), preferred.indexOf(prefix+":"+urlPrefix)); } } } emit finished(); } WikipediaSettings::WikipediaSettings(QWidget *p) : ToggleList(p) , state(Initial) , job(0) , spinner(0) , loader(0) { label->setText(tr("Choose the wikipedia languages you want to use when searching for artist and album information.")); reload=new Action(tr("Reload"), this); connect(reload, SIGNAL(triggered()), this, SLOT(getLangs())); available->addAction(reload); available->setContextMenuPolicy(Qt::ActionsContextMenu); } WikipediaSettings::~WikipediaSettings() { if (loader) { loader->deleteLater(); } } void WikipediaSettings::showEvent(QShowEvent *e) { if (Initial==state) { state=Loading; QByteArray data; QString fileName=localeFile(); if (QFile::exists(fileName)) { QFile f(fileName); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { data=compressor.readAll(); } } if (data.isEmpty()) { getLangs(); } else { showSpinner(); parseLangs(data); } } QWidget::showEvent(e); } void WikipediaSettings::load() { } void WikipediaSettings::save() { if (Loaded!=state) { return; } QStringList pref; for (int i=0; icount(); ++i) { pref.append(selected->item(i)->data(Qt::UserRole).toString()); } if (pref.isEmpty()) { pref.append("en:en"); } Settings::self()->saveWikipediaLangs(pref); WikipediaEngine::setPreferedLangs(pref); } void WikipediaSettings::cancel() { if (job) { disconnect(job, SIGNAL(finished()), this, SLOT(parseLangs())); job->deleteLater(); job=0; } } void WikipediaSettings::getLangs() { state=Loading; showSpinner(); available->clear(); selected->clear(); reload->setEnabled(false); cancel(); QUrl url("http://en.wikipedia.org/w/api.php"); QUrlQuery q; q.addQueryItem(QLatin1String("action"), QLatin1String("query")); q.addQueryItem(QLatin1String("meta"), QLatin1String("siteinfo")); q.addQueryItem(QLatin1String("siprop"), QLatin1String("interwikimap")); q.addQueryItem(QLatin1String("sifilteriw"), QLatin1String("local")); q.addQueryItem(QLatin1String("format"), QLatin1String("xml")); url.setQuery(q); job=NetworkAccessManager::self()->get(url); connect(job, SIGNAL(finished()), this, SLOT(parseLangs())); } void WikipediaSettings::parseLangs() { NetworkJob *reply = qobject_cast(sender()); if (!reply) { return; } reload->setEnabled(true); reply->deleteLater(); if (reply!=job) { return; } job=0; QByteArray data=reply->readAll(); parseLangs(data); QFile f(localeFile()); QtIOCompressor compressor(&f); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { compressor.write(data); } } void WikipediaSettings::parseLangs(const QByteArray &data) { prefMap.clear(); if (!loader) { loader=new WikipediaLoader(); connect(loader, SIGNAL(entry(QString,QString,QString,int)), SLOT(addEntry(QString,QString,QString,int))); connect(loader, SIGNAL(finished()), SLOT(loaderFinished())); connect(this, SIGNAL(load(QByteArray)), loader, SLOT(load(QByteArray))); } emit load(data); } void WikipediaSettings::addEntry(const QString &prefix, const QString &urlPrefix, const QString &lang, int prefIndex) { QString entry=prefix+":"+urlPrefix; QListWidgetItem *item = new QListWidgetItem(-1==prefIndex ? available : selected); item->setText(QString("[%1] %2").arg(prefix).arg(lang)); item->setData(Qt::UserRole, entry); if (-1!=prefIndex) { prefMap[prefIndex]=item; } } void WikipediaSettings::loaderFinished() { QMap::ConstIterator it(prefMap.constBegin()); QMap::ConstIterator end(prefMap.constEnd()); for (; it!=end; ++it) { int row=selected->row(it.value()); if (row!=it.key()) { selected->insertItem(it.key(), selected->takeItem(row)); } } hideSpinner(); state=Loaded; } void WikipediaSettings::showSpinner() { if (!spinner) { spinner=new Spinner(available); spinner->setWidget(available); } spinner->start(); } void WikipediaSettings::hideSpinner() { if (spinner) { spinner->stop(); } } cantata-2.2.0/context/wikipediasettings.h000066400000000000000000000043131316350454000205140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef WIKIPEDIA_SETTINGS_H #define WIKIPEDIA_SETTINGS_H #include "togglelist.h" #include class NetworkJob; class QShowEvent; class QListWidgetItem; class Spinner; class Action; class Thread; class WikipediaLoader : public QObject { Q_OBJECT public: WikipediaLoader(); virtual ~WikipediaLoader(); public Q_SLOTS: void load(const QByteArray &data); Q_SIGNALS: void entry(const QString &prefix, const QString &urlPrefix, const QString &lang, int prefIndex); void finished(); private: Thread *thread; }; class WikipediaSettings : public ToggleList { Q_OBJECT enum State { Initial, Loading, Loaded }; public: static QString constSubDir; WikipediaSettings(QWidget *p); virtual ~WikipediaSettings(); void load(); void save(); void cancel(); void showEvent(QShowEvent *e); Q_SIGNALS: void load(const QByteArray &data); private Q_SLOTS: void getLangs(); void parseLangs(); void addEntry(const QString &prefix, const QString &urlPrefix, const QString &lang, int prefIndex); void loaderFinished(); private: void parseLangs(const QByteArray &data); void showSpinner(); void hideSpinner(); private: State state; NetworkJob *job; Spinner *spinner; Action *reload; QMap prefMap; WikipediaLoader *loader; }; #endif cantata-2.2.0/db/000077500000000000000000000000001316350454000135145ustar00rootroot00000000000000cantata-2.2.0/db/librarydb.cpp000066400000000000000000001125361316350454000162020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "librarydb.h" #include #include #include #include #include #include #include static const int constSchemaVersion=5; bool LibraryDb::dbgEnabled=false; #define DBUG if (dbgEnabled) qWarning() << metaObject()->className() << __FUNCTION__ << (void *)this const QLatin1String LibraryDb::constFileExt(".sql"); const QLatin1String LibraryDb::constNullGenre("-"); LibraryDb::AlbumSort LibraryDb::toAlbumSort(const QString &str) { for (int i=0; ib.lastModified; } static bool songSort(const Song &a, const Song &b) { if (a.disc!=b.disc) { return a.discb.lastModified; //} static QString artistSort(const Song &s) { if (s.useComposer() && !s.composer().isEmpty()) { return s.composer(); } if (!s.artistSortString().isEmpty()) { return s.artistSortString(); } return Song::sortString(s.albumArtist()); } static QString albumSort(const Song &s) { if (!s.albumSort().isEmpty()) { return s.albumSort(); } return Song::sortString(s.album); } // Code taken from Clementine's LibraryQuery class SqlQuery { public: SqlQuery(const QString &colSpec, QSqlDatabase &database) : db(database) , fts(false) , columSpec(colSpec) , limit(0) { } void addWhere(const QString &column, const QVariant &value, const QString &op="=") { // ignore 'literal' for IN if (!op.compare("IN", Qt::CaseInsensitive)) { QStringList final; foreach(const QString &singleValue, value.toStringList()) { final.append("?"); boundValues << singleValue; } whereClauses << QString("%1 IN (" + final.join(",") + ")").arg(column); } else { // Do integers inline - sqlite seems to get confused when you pass integers // to bound parameters if (QVariant::Int==value.type()) { whereClauses << QString("%1 %2 %3").arg(column, op, value.toString()); } else if ("genre"==column) { QString clause("("); for (int i=0; i0) { sql+=" LIMIT "+QString::number(limit); } query=QSqlQuery(sql, db); foreach (const QVariant &value, boundValues) { query.addBindValue(value); } return query.exec(); } bool next() { return query.next(); } QString executedQuery() const { return query.executedQuery(); } int size() const { return query.size(); } QVariant value(int col) const { return query.value(col); } const QSqlQuery & realQuery() const { return query; } private: QSqlDatabase &db; QSqlQuery query; bool fts; QString columSpec; QStringList whereClauses; QVariantList boundValues; QString order; int limit; }; LibraryDb::LibraryDb(QObject *p, const QString &name) : QObject(p) , dbName(name) , currentVersion(0) , newVersion(0) , db(0) , insertSongQuery(0) { DBUG; } LibraryDb::~LibraryDb() { reset(); } void LibraryDb::clear() { if (db) { DBUG; erase(); currentVersion=0; init(dbFileName); } } void LibraryDb::erase() { reset(); if (!dbFileName.isEmpty() && QFile::exists(dbFileName)) { QFile::remove(dbFileName); } } enum SongFields { SF_file, SF_artist , SF_artistId, SF_albumArtist, SF_artistSort, SF_composer, SF_album, SF_albumId, SF_albumSort, SF_title, SF_genre1, SF_genre2, SF_genre3, SF_genre4, SF_track, SF_disc, SF_time, SF_year, SF_origYear, SF_type, SF_lastModified }; bool LibraryDb::init(const QString &dbFile) { if (dbFile!=dbFileName) { reset(); dbFileName=dbFile; } if (db) { return true; } DBUG << dbFile << dbName; currentVersion=0; db=new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", dbName.isEmpty() ? QLatin1String(QSqlDatabase::defaultConnection) : dbName)); if (!db || !db->isValid()) { emit error(tr("Database error - please check Qt SQLite driver is installed")); return false; } db->setDatabaseName(dbFile); DBUG << (void *)db; if (!db->open()) { delete db; db=0; DBUG << "Failed to open"; return false; } if (!createTable("versions(collection integer, schema integer)")) { DBUG << "Failed to create versions table"; return false; } QSqlQuery query("select collection, schema from versions", *db); int schemaVersion=0; if (query.next()) { currentVersion=query.value(0).toUInt(); schemaVersion=query.value(1).toUInt(); } if (schemaVersion>0 && schemaVersion!=constSchemaVersion) { DBUG << "Scheme version changed"; currentVersion=0; erase(); return init(dbFile); } if (0==currentVersion || (schemaVersion>0 && schemaVersion!=constSchemaVersion)) { QSqlQuery(*db).exec("delete from versions"); QSqlQuery(*db).exec("insert into versions (collection, schema) values(0, "+QString::number(constSchemaVersion)+")"); } DBUG << "Current version" << currentVersion; // NOTE: The order here MUST match SongFields enum above!!! if (createTable("songs (" "file text, " "artist text, " "artistId text, " "albumArtist text, " "artistSort text, " "composer text, " "album text, " "albumId text, " "albumSort text, " "title text, " "genre1 text, " "genre2 text, " "genre3 text, " "genre4 text, " "track integer, " "disc integer, " "time integer, " "year integer, " "origYear integer, " "type integer, " "lastModified integer, " "primary key (file))")) { QSqlQuery fts(*db); if (!fts.exec("create virtual table if not exists songs_fts using fts4(fts_artist, fts_artistId, fts_album, fts_albumId, fts_title, tokenize=unicode61)")) { DBUG << "Failed to create FTS table" << fts.lastError().text() << "trying again with simple tokenizer"; if (!fts.exec("create virtual table if not exists songs_fts using fts4(fts_artist, fts_artistId, fts_album, fts_albumId, fts_title, tokenize=simple)")) { DBUG << "Failed to create FTS table" << fts.lastError().text(); } } } else { DBUG << "Failed to create songs table"; return false; } emit libraryUpdated(); DBUG << "Created"; return true; } void LibraryDb::insertSong(const Song &s) { if (!insertSongQuery) { insertSongQuery=new QSqlQuery(*db); insertSongQuery->prepare("insert into songs(file, artist, artistId, albumArtist, artistSort, composer, album, albumId, albumSort, title, genre1, genre2, genre3, genre4, track, disc, time, year, origYear, type, lastModified) " "values(:file, :artist, :artistId, :albumArtist, :artistSort, :composer, :album, :albumId, :albumSort, :title, :genre1, :genre2, :genre3, :genre4, :track, :disc, :time, :year, :origYear, :type, :lastModified)"); } QString albumId=s.albumId(); insertSongQuery->bindValue(":file", s.file); insertSongQuery->bindValue(":artist", s.artist); insertSongQuery->bindValue(":artistId", s.artistOrComposer()); insertSongQuery->bindValue(":albumArtist", s.albumartist); insertSongQuery->bindValue(":artistSort", artistSort(s)); insertSongQuery->bindValue(":composer", s.composer()); insertSongQuery->bindValue(":album", s.album==albumId ? QString() : s.album); insertSongQuery->bindValue(":albumId", albumId); insertSongQuery->bindValue(":albumSort", albumSort(s)); insertSongQuery->bindValue(":title", s.title); for (int i=0; ibindValue(":genre"+QString::number(i+1), s.genres[i].isEmpty() ? constNullGenre : s.genres[i]); } insertSongQuery->bindValue(":track", s.track); insertSongQuery->bindValue(":disc", s.disc); insertSongQuery->bindValue(":time", s.time); insertSongQuery->bindValue(":year", s.year); insertSongQuery->bindValue(":origYear", s.origYear); insertSongQuery->bindValue(":type", s.type); insertSongQuery->bindValue(":lastModified", s.lastModified); if (!insertSongQuery->exec()) { qWarning() << "insert failed" << insertSongQuery->lastError().text() << newVersion << s.file; } } QList LibraryDb::getGenres() { DBUG; QMap > map; if (0!=currentVersion && db) { QString queryStr("distinct "); for (int i=0; i genres; QMap >::ConstIterator it=map.constBegin(); QMap >::ConstIterator end=map.constEnd(); for (; it!=end; ++it) { DBUG << it.key(); genres.append(Genre(it.key(), it.value().size())); } qSort(genres); return genres; } QList LibraryDb::getArtists(const QString &genre) { DBUG << genre; QMap sortMap; QMap albumMap; if (0!=currentVersion && db) { SqlQuery query("distinct artistId, albumId, artistSort", *db); query.setFilter(filter); if (!genre.isEmpty()) { query.addWhere("genre", genre); } else if (!genreFilter.isEmpty()) { query.addWhere("genre", genreFilter); } query.exec(); DBUG << query.executedQuery(); while (query.next()) { QString artist=query.value(0).toString(); albumMap[artist]++; sortMap[artist]=query.value(2).toString(); } } QList artists; QMap::ConstIterator it=albumMap.constBegin(); QMap::ConstIterator end=albumMap.constEnd(); for (; it!=end; ++it) { // DBUG << it.key(); artists.append(Artist(it.key(), sortMap[it.key()], it.value())); } qSort(artists); return artists; } QList LibraryDb::getAlbums(const QString &artistId, const QString &genre, AlbumSort sort) { timer.start(); DBUG << artistId << genre; QList albums; if (0!=currentVersion && db) { bool wantModified=AS_Modified==sort; bool wantArtist=artistId.isEmpty(); QString queryString="album, albumId, albumSort, artist, albumArtist, composer"; for (int i=0; i entries; QMap > albumIdArtists; // MAp of albumId -> albumartists/composers while (query.next()) { count++; int col=0; QString album=query.value(col++).toString(); QString albumId=query.value(col++).toString(); QString albumSort=query.value(col++).toString(); Song s; s.artist=query.value(col++).toString(); s.albumartist=query.value(col++).toString(); s.setComposer(query.value(col++).toString()); s.album=album.isEmpty() ? albumId : album; for (int i=0; i::iterator it=entries.find(key); if (it==entries.end()) { entries.insert(key, Album(album.isEmpty() ? albumId : album, albumId, albumSort, artist, artistSort, year, 1, time, lastModified, haveUniqueId)); } else { Album &al=it.value(); if (wantModified) { al.lastModified=qMax(al.lastModified, lastModified); } al.year=qMax(al.year, year); al.duration+=time; al.trackCount++; } if (haveUniqueId) { QMap >::iterator aIt = albumIdArtists.find(key); if (aIt == albumIdArtists.end()) { albumIdArtists.insert(key, QSet() << artist); } else { aIt.value().insert(artist); } } } QMap >::ConstIterator aIt = albumIdArtists.constBegin(); QMap >::ConstIterator aEnd = albumIdArtists.constEnd(); for(; aIt!=aEnd; ++aIt) { if (aIt.value().count()>1) { QStringList artists = aIt.value().toList(); artists.sort(); Album &al = entries.find(aIt.key()).value(); al.artist = artists.join(", "); al.artistSort = QString(); } } albums=entries.values(); DBUG << count << albums.count(); } DBUG << "After select" << timer.elapsed(); switch(sort) { case AS_AlArYr: qSort(albums.begin(), albums.end(), albumsSortAlArYr); break; case AS_AlYrAr: qSort(albums.begin(), albums.end(), albumsSortAlYrAr); break; case AS_ArAlYr: qSort(albums.begin(), albums.end(), albumsSortArAlYr); break; case AS_ArYrAl: qSort(albums.begin(), albums.end(), albumsSortArYrAl); break; case AS_YrAlAr: qSort(albums.begin(), albums.end(), albumsSortYrAlAr); break; case AS_YrArAl: qSort(albums.begin(), albums.end(), albumsSortYrArAl); break; case AS_Modified: qSort(albums.begin(), albums.end(), albumsSortModified); break; default: break; } DBUG << "After sort" << timer.elapsed(); return albums; } QList LibraryDb::getTracks(const QString &artistId, const QString &albumId, const QString &genre, AlbumSort sort, bool useFilter) { DBUG << artistId << albumId << genre << sort; QList songs; if (0!=currentVersion && db) { SqlQuery query("*", *db); if (useFilter) { query.setFilter(filter); } if (!artistId.isEmpty()) { query.addWhere("artistId", artistId); } if (!albumId.isEmpty()) { query.addWhere("albumId", albumId); } if (!genre.isEmpty()) { query.addWhere("genre", genre); } else if (!genreFilter.isEmpty()) { query.addWhere("genre", genreFilter); } query.exec(); DBUG << query.executedQuery(); while (query.next()) { songs.append(getSong(query.realQuery())); } } switch(sort) { case AS_AlArYr: qSort(songs.begin(), songs.end(), songsSortAlAr); break; case AS_ArAlYr: qSort(songs.begin(), songs.end(), songsSortArAl); break; // case AS_Year: // qSort(songs.begin(), songs.end(), songsSortYrAlAr); // break; // case AS_Modified: // qSort(songs.begin(), songs.end(), songsSortModified); // break; default: break; } return songs; } QList LibraryDb::getTracks(int rowFrom, int count) { QList songList; if (db) { SqlQuery query("*", *db); query.addWhere("rowid", rowFrom, ">"); query.addWhere("rowid", rowFrom+count, "<="); query.addWhere("type", 0); query.exec(); DBUG << query.executedQuery(); while (query.next()) { songList.append(getSong(query.realQuery())); } } return songList; } int LibraryDb::trackCount() { if (!db) { return 0; } SqlQuery query("(count())", *db); query.addWhere("type", 0); query.exec(); DBUG << query.executedQuery(); return query.next() ? query.value(0).toInt() : 0; } QList LibraryDb::songs(const QStringList &files, bool allowPlaylists) const { QList songList; if (0!=currentVersion && db) { foreach (const QString &f, files) { SqlQuery query("*", *db); query.addWhere("file", f); query.exec(); DBUG << query.executedQuery(); if (query.next()) { Song song=getSong(query.realQuery()); if (allowPlaylists || Song::Playlist!=song.type) { songList.append(song); } } } } return songList; } QList LibraryDb::getAlbumsWithArtist(const QString &artist) { QList albums; if (0!=currentVersion && db) { SqlQuery query("distinct album, albumId, albumSort", *db); query.addWhere("artist", artist); query.exec(); DBUG << query.executedQuery(); while (query.next()) { QString album=query.value(0).toString(); QString albumId=query.value(1).toString(); albums.append(Album(album.isEmpty() ? albumId : album, albumId, query.value(2).toString(), artist)); } } qSort(albums.begin(), albums.end(), albumsSortArAlYr); return albums; } LibraryDb::Album LibraryDb::getRandomAlbum(const QString &genre, const QString &artist) { Album al; if (0!=currentVersion && db) { SqlQuery query("artistId, albumId", *db); query.setOrder("random()"); query.setLimit(1); if (!artist.isEmpty()) { query.addWhere("artistId", artist); } if (!genre.isEmpty()) { query.addWhere("genre", genre); } else if (!genreFilter.isEmpty()) { query.addWhere("genre", genreFilter); } query.exec(); DBUG << query.executedQuery(); if (query.next()) { al.artist=query.value(0).toString(); al.id=query.value(1).toString(); } } return al; } LibraryDb::Album LibraryDb::getRandomAlbum(const QStringList &genres, const QStringList &artists) { if (genres.isEmpty() && artists.isEmpty()) { return getRandomAlbum(QString(), QString()); } QList albums; foreach (const QString &genre, genres) { albums.append(getRandomAlbum(genre, QString())); } foreach (const QString &artist, artists) { albums.append(getRandomAlbum(QString(), artist)); } if (albums.isEmpty()) { return Album(); } return albums.at(Utils::random(albums.count())); } QSet LibraryDb::get(const QString &type) { if (detailsCache.contains(type)) { return detailsCache[type]; } QSet set; if (!db) { return set; } QStringList columns; bool isGenre="genre"==type; bool isAlbum="album"==type; if (isGenre) { for (int i=0; i &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres) { artists=get("artist"); albumArtists=get("albumArtist"); composers=get("composer"); albums=get("album"); genres=get("genre"); } bool LibraryDb::songExists(const Song &song) { if (!db) { return false; } SqlQuery query("file", *db); query.addWhere("artistId", song.artistOrComposer()); query.addWhere("albumId", song.albumId()); query.addWhere("title", song.title); query.addWhere("track", song.track); query.exec(); return query.next(); } bool LibraryDb::setFilter(const QString &f, const QString &genre) { QString newFilter=f.trimmed().toLower(); if (!f.isEmpty()) { QStringList strings(newFilter.split(QRegExp("\\s+"))); static QList replaceChars=QList() << QLatin1Char('(') << QLatin1Char(')') << QLatin1Char('"') << QLatin1Char(':') << QLatin1Char('-'); QStringList tokens; foreach (QString str, strings) { foreach (const QLatin1Char ch, replaceChars) { str.replace(ch, '?'); } if (str.length()>0) { tokens.append(str+QLatin1String("* ")); } } newFilter=tokens.join(" "); DBUG << newFilter; } bool modified=newFilter!=filter || genre!=genreFilter; filter=newFilter; genreFilter=genre; return modified; } void LibraryDb::updateStarted(time_t ver) { DBUG << (void *)db; if (!db) { return; } newVersion=ver; timer.start(); db->transaction(); if (currentVersion>0) { clearSongs(false); } } void LibraryDb::insertSongs(QList *songs) { DBUG << (int)(songs ? songs->size() : -1); if (!songs) { return; } foreach (const Song &s, *songs) { insertSong(s); } delete songs; } void LibraryDb::updateFinished() { if (!db) { return; } DBUG << timer.elapsed(); DBUG << "update fts" << timer.elapsed(); QSqlQuery(*db).exec("insert into songs_fts(fts_artist, fts_artistId, fts_album, fts_albumId, fts_title) " "select artist, artistId, album, albumId, title from songs"); QSqlQuery(*db).exec("update versions set collection ="+QString::number(newVersion)); DBUG << "commit" << timer.elapsed(); db->commit(); currentVersion=newVersion; DBUG << "complete" << timer.elapsed(); emit libraryUpdated(); } void LibraryDb::abortUpdate() { if (db) { db->rollback(); } } bool LibraryDb::createTable(const QString &q) { if (!db) { return false; } QSqlQuery query(*db); if (!query.exec("create table if not exists "+q)) { qWarning() << "Failed to create table" << query.lastError().text(); return false; } return true; } Song LibraryDb::getSong(const QSqlQuery &query) { Song s; s.file=query.value(SF_file).toString(); s.artist=query.value(SF_artist).toString(); s.albumartist=query.value(SF_albumArtist).toString(); s.setComposer(query.value(SF_composer).toString()); s.album=query.value(SF_album).toString(); QString val=query.value(SF_albumId).toString(); if (s.album.isEmpty()) { s.album=val; val=QString(); } if (!val.isEmpty() && val!=s.album) { s.setMbAlbumId(val); } s.title=query.value(SF_title).toString(); for (int i=SF_genre1; itransaction(); } QSqlQuery(*db).exec("delete from songs"); QSqlQuery(*db).exec("delete from songs_fts"); detailsCache.clear(); if (startTransaction) { db->commit(); } } cantata-2.2.0/db/librarydb.h000066400000000000000000000122231316350454000156370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LIBRARY_DB_H #define LIBRARY_DB_H #include #include #include #include #include "mpd-interface/song.h" #include class QSqlDatabase; class QSqlQuery; class LibraryDb : public QObject { Q_OBJECT public: static void enableDebug() { dbgEnabled=true; } static bool debugEnabled() { return dbgEnabled; } static const QLatin1String constFileExt; static const QLatin1String constNullGenre; enum AlbumSort { AS_AlArYr, AS_AlYrAr, AS_ArAlYr, AS_ArYrAl, AS_YrAlAr, AS_YrArAl, AS_Modified, AS_Count }; static AlbumSort toAlbumSort(const QString &str); static QString albumSortStr(AlbumSort m); struct Genre { Genre(const QString &n=QString(), int ac=0) : name(n), artistCount(ac) { } QString name; int artistCount; bool operator<(const Genre &o) const { if (constNullGenre==name) { return constNullGenre!=o.name; } return name.localeAwareCompare(o.name)<0; } }; struct Artist { Artist(const QString &n=QString(), const QString &s=QString(), int ac=0) : name(n), sort(s), albumCount(ac) { } QString name; QString sort; int albumCount; bool operator<(const Artist &o) const { const QString &field=sort.isEmpty() ? name : sort; const QString &ofield=o.sort.isEmpty() ? o.name : o.sort; return field.localeAwareCompare(ofield)<0; } }; struct Album { Album(const QString &n=QString(), const QString &i=QString(), const QString &s=QString(), const QString &a=QString(), const QString &as=QString(), int y=0, int tc=0, int d=0, int lm=0, bool onlyUseId=false) : name(n), id(i), sort(s), artist(a), artistSort(as), year(y), trackCount(tc), duration(d), lastModified(lm), identifyById(onlyUseId) { } QString name; QString id; QString sort; QString artist; QString artistSort; int year; int trackCount; int duration; int lastModified; bool identifyById; // Should we jsut use albumId to locate tracks - Issue #1025 }; LibraryDb(QObject *p, const QString &name); ~LibraryDb(); void clear(); void erase(); virtual bool init(const QString &dbFile); void insertSong(const Song &s); QList getGenres(); QList getArtists(const QString &genre=QString()); QList getAlbums(const QString &artistId=QString(), const QString &genre=QString(), AlbumSort sort=AS_YrAlAr); QList getTracks(const QString &artistId, const QString &albumId, const QString &genre=QString(), AlbumSort sort=AS_YrAlAr, bool useFilter=true); QList getTracks(int rowFrom, int count); int trackCount(); QList songs(const QStringList &files, bool allowPlaylists=false) const; QList getAlbumsWithArtist(const QString &artist); Album getRandomAlbum(const QString &genre, const QString &artist); Album getRandomAlbum(const QStringList &genres, const QStringList &artists); QSet get(const QString &type); void getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres); bool songExists(const Song &song); bool setFilter(const QString &f, const QString &genre=QString()); const QString & getFilter() const { return filter; } int getCurrentVersion() const { return currentVersion; } Q_SIGNALS: void libraryUpdated(); void error(const QString &str); public Q_SLOTS: void updateStarted(time_t ver); void insertSongs(QList *songs); virtual void updateFinished(); void abortUpdate(); protected: bool createTable(const QString &q); static Song getSong(const QSqlQuery &query); protected: virtual void reset(); void clearSongs(bool startTransaction=true); protected: static bool dbgEnabled; QString dbName; QString dbFileName; time_t currentVersion; time_t newVersion; QSqlDatabase *db; QSqlQuery *insertSongQuery; QElapsedTimer timer; QString filter; QString genreFilter; QMap > detailsCache; }; #endif cantata-2.2.0/db/mpdlibrarydb.cpp000066400000000000000000000121251316350454000166740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mpdlibrarydb.h" #include "support/globalstatic.h" #include "support/utils.h" #include "mpd-interface/mpdconnection.h" #include "gui/settings.h" #include #include #include #include #include #define DBUG if (LibraryDb::debugEnabled()) qWarning() << metaObject()->className() << __FUNCTION__ static const QLatin1String constDirName("library"); static QString databaseName(const MPDConnectionDetails &details) { QString fileName=(!details.isLocal() ? details.hostname+'_'+QString::number(details.port) : details.hostname)+LibraryDb::constFileExt; fileName.replace('/', '_'); fileName.replace('~', '_'); return Utils::dataDir(constDirName, true)+fileName; } void MpdLibraryDb::removeUnusedDbs() { QSet existing; QList connections=Settings::self()->allConnections(); QString dirPath=Utils::dataDir(constDirName, false); if (dirPath.isEmpty()) { return; } foreach (const MPDConnectionDetails &conn, connections) { existing.insert(databaseName(conn).mid(dirPath.length())); } QFileInfoList files=QDir(dirPath).entryInfoList(QStringList() << "*"+LibraryDb::constFileExt, QDir::Files); foreach (const QFileInfo &file, files) { if (!existing.contains(file.fileName())) { QFile::remove(file.absoluteFilePath()); } } } MpdLibraryDb::MpdLibraryDb(QObject *p) : LibraryDb(p, "MPD") , loading(false) , coverQuery(0) , albumIdOnlyCoverQuery(0) , artistImageQuery(0) { connect(MPDConnection::self(), SIGNAL(updatingLibrary(time_t)), this, SLOT(updateStarted(time_t))); connect(MPDConnection::self(), SIGNAL(librarySongs(QList*)), this, SLOT(insertSongs(QList*))); connect(MPDConnection::self(), SIGNAL(updatedLibrary()), this, SLOT(updateFinished())); connect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues))); connect(this, SIGNAL(loadLibrary()), MPDConnection::self(), SLOT(loadLibrary())); connect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged(MPDConnectionDetails))); DBUG; } MpdLibraryDb::~MpdLibraryDb() { } Song MpdLibraryDb::getCoverSong(const QString &artistId, const QString &albumId) { DBUG << artistId << albumId; if (0!=currentVersion && db) { QSqlQuery *query=0; if (albumId.isEmpty()) { if (!artistImageQuery) { artistImageQuery=new QSqlQuery(*db); artistImageQuery->prepare("select * from songs where artistId=:artistId limit 1;"); } query=artistImageQuery; } else if (artistId.isEmpty()) { if (!albumIdOnlyCoverQuery) { albumIdOnlyCoverQuery=new QSqlQuery(*db); albumIdOnlyCoverQuery->prepare("select * from songs where albumId=:albumId limit 1;"); } albumIdOnlyCoverQuery->bindValue(":albumId", albumId); query=albumIdOnlyCoverQuery; } else { if (!coverQuery) { coverQuery=new QSqlQuery(*db); coverQuery->prepare("select * from songs where artistId=:artistId and albumId=:albumId limit 1;"); } coverQuery->bindValue(":albumId", albumId); query=coverQuery; } query->bindValue(":artistId", artistId); query->exec(); DBUG << "coverquery" << query->executedQuery() << query->size(); while (query->next()) { return getSong(*query); } } return Song(); } void MpdLibraryDb::connectionChanged(const MPDConnectionDetails &details) { QString dbFile=databaseName(details); if (dbFile!=dbFileName) { init(dbFile); } else { emit libraryUpdated(); } } void MpdLibraryDb::reset() { delete coverQuery; delete artistImageQuery; coverQuery=0; artistImageQuery=0; LibraryDb::reset(); } void MpdLibraryDb::updateFinished() { loading=false; LibraryDb::updateFinished(); } void MpdLibraryDb::statsUpdated(const MPDStatsValues &stats) { if (!loading && stats.dbUpdate>currentVersion) { DBUG << stats.dbUpdate << currentVersion; loading=true; emit loadLibrary(); } } cantata-2.2.0/db/mpdlibrarydb.h000066400000000000000000000033131316350454000163400ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MPD_LIBRARY_DB_H #define MPD_LIBRARY_DB_H #include #include #include #include #include "librarydb.h" #include "config.h" #include struct MPDStatsValues; struct MPDConnectionDetails; class QSqlDatabase; class QSqlQuery; class QSettings; class MpdLibraryDb : public LibraryDb { Q_OBJECT public: static void removeUnusedDbs(); MpdLibraryDb(QObject *p=0); ~MpdLibraryDb(); Song getCoverSong(const QString &artistId, const QString &albumId=QString()); Q_SIGNALS: void loadLibrary(); public Q_SLOTS: void connectionChanged(const MPDConnectionDetails &details); void statsUpdated(const MPDStatsValues &stats); private: void reset(); void updateFinished(); private: bool loading; QSqlQuery *coverQuery; QSqlQuery *albumIdOnlyCoverQuery; QSqlQuery *artistImageQuery; }; #endif cantata-2.2.0/db/onlinedb.cpp000066400000000000000000000065511316350454000160210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinedb.h" #include #include static const QString subDir("online"); OnlineDb::OnlineDb(const QString &serviceName, QObject *p) : LibraryDb(p, serviceName) , insertCoverQuery(0) , getCoverQuery(0) { } OnlineDb::~OnlineDb() { } bool OnlineDb::init(const QString &dbFile) { LibraryDb::init(dbFile); createTable("covers(artistId text, albumId text, url text)"); createTable("stats(artists integer)"); return true; } void OnlineDb::create() { if (!db) { init(Utils::dataDir(subDir, true)+dbName+".sql"); } } void OnlineDb::startUpdate() { updateStarted(currentVersion+1); if (!db) { return; } QSqlQuery(*db).exec("delete from covers"); QSqlQuery(*db).exec("drop index genre_idx"); } void OnlineDb::endUpdate() { updateFinished(); } void OnlineDb::insertStats(int numArtists) { if (!db) { return; } QSqlQuery(*db).exec("delete from stats"); QSqlQuery(*db).exec("insert into stats(artists) values("+QString::number(numArtists)+")"); } void OnlineDb::reset() { delete insertCoverQuery; delete getCoverQuery; insertCoverQuery=0; getCoverQuery=0; LibraryDb::reset(); } void OnlineDb::storeCoverUrl(const QString &artistId, const QString &albumId, const QString &url) { if (!db) { return; } if (!insertCoverQuery) { insertCoverQuery=new QSqlQuery(*db); insertCoverQuery->prepare("insert into covers(artistId, albumId, url) " "values(:artistId, :albumId, :url)"); } insertCoverQuery->bindValue(":artistId", artistId); insertCoverQuery->bindValue(":albumId", albumId); insertCoverQuery->bindValue(":url", url); insertCoverQuery->exec(); } int OnlineDb::getStats() { if (!db) { return -1; } QSqlQuery q(*db); q.exec("select artists from stats"); if (q.next()) { return q.value(0).toInt(); } return -1; } QString OnlineDb::getCoverUrl(const QString &artistId, const QString &albumId) { if (0!=currentVersion && db) { if (!getCoverQuery) { getCoverQuery=new QSqlQuery(*db); getCoverQuery->prepare("select url from covers where artistId=:artistId and albumId=:albumId limit 1;"); } getCoverQuery->bindValue(":artistId", artistId); getCoverQuery->bindValue(":albumId", albumId); getCoverQuery->exec(); while (getCoverQuery->next()) { return getCoverQuery->value(0).toString(); } } return QString(); } cantata-2.2.0/db/onlinedb.h000066400000000000000000000027441316350454000154660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_DB_H #define ONLINE_DB_H #include "librarydb.h" class OnlineDb : public LibraryDb { Q_OBJECT public: OnlineDb(const QString &serviceName, QObject *p=0); ~OnlineDb(); virtual bool init(const QString &dbFile); void create(); QString getCoverUrl(const QString &artistId, const QString &albumId); int getStats(); public Q_SLOTS: void startUpdate(); void endUpdate(); void storeCoverUrl(const QString &artistId, const QString &albumId, const QString &url); void insertStats(int numArtists); private: void reset(); private: QSqlQuery *insertCoverQuery; QSqlQuery *getCoverQuery; }; #endif cantata-2.2.0/dbus/000077500000000000000000000000001316350454000140645ustar00rootroot00000000000000cantata-2.2.0/dbus/gnomemediakeys.cpp000066400000000000000000000127301316350454000175740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gnomemediakeys.h" #include "settingsdaemoninterface.h" #include "mediakeysinterface.h" #include #include #include #include #include #include static const char * constOrigService = "org.gnome.SettingsDaemon"; static const char * constNewService = "org.gnome.SettingsDaemon.MediaKeys"; static const char * constDaemonPath = "/org/gnome/SettingsDaemon"; static const char * constMediaKeysPath = "/org/gnome/SettingsDaemon/MediaKeys"; GnomeMediaKeys::GnomeMediaKeys(QObject *p) : MultiMediaKeysInterface(p) , daemon(0) , mk(0) , watcher(0) { } bool GnomeMediaKeys::activate() { if (mk) { return true; } if (daemonIsRunning()) { grabKeys(); return true; } return false; } void GnomeMediaKeys::deactivate() { if (mk) { releaseKeys(); disconnectDaemon(); if (watcher) { watcher->deleteLater(); watcher=0; } } } bool GnomeMediaKeys::daemonIsRunning() { // Check if the service is available if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(constNewService) && !QDBusConnection::sessionBus().interface()->isServiceRegistered(constOrigService)) { //...not already started, so attempt to start! QDBusConnection::sessionBus().interface()->startService(constOrigService); // ?? if (!daemon) { daemon = new OrgGnomeSettingsDaemonInterface(constOrigService, constDaemonPath, QDBusConnection::sessionBus(), this); connect(daemon, SIGNAL(PluginActivated(QString)), this, SLOT(pluginActivated(QString))); daemon->Start(); return false; } } return true; } void GnomeMediaKeys::releaseKeys() { if (mk) { mk->ReleaseMediaPlayerKeys(QCoreApplication::applicationName()); disconnect(mk, SIGNAL(MediaPlayerKeyPressed(QString,QString)), this, SLOT(keyPressed(QString,QString))); mk->deleteLater(); mk=0; } } void GnomeMediaKeys::grabKeys() { QStringList services { constNewService, constOrigService }; for (const auto &service: services) { if (QDBusConnection::sessionBus().interface()->isServiceRegistered(service)) { if (!mk) { mk = new OrgGnomeSettingsDaemonMediaKeysInterface(service, constMediaKeysPath, QDBusConnection::sessionBus(), this); } QDBusPendingReply<> reply = mk->GrabMediaPlayerKeys(QCoreApplication::applicationName(), QDateTime::currentDateTime().toTime_t()); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(reply, this); connect(callWatcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(registerFinished(QDBusPendingCallWatcher*))); if (!watcher) { watcher = new QDBusServiceWatcher(this); watcher->setConnection(QDBusConnection::sessionBus()); watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); connect(watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), this, SLOT(serviceOwnerChanged(QString, QString, QString))); } serviceName = service; break; } } } void GnomeMediaKeys::disconnectDaemon() { if (daemon) { disconnect(daemon, SIGNAL(PluginActivated(QString)), this, SLOT(pluginActivated(QString))); daemon->deleteLater(); daemon=0; } } void GnomeMediaKeys::serviceOwnerChanged(const QString &name, const QString &, const QString &) { if (name==serviceName) { releaseKeys(); disconnectDaemon(); if (daemonIsRunning()) { grabKeys(); } } } void GnomeMediaKeys::registerFinished(QDBusPendingCallWatcher *watcher) { QDBusMessage reply = watcher->reply(); watcher->deleteLater(); if (QDBusMessage::ErrorMessage!=reply.type()) { connect(mk, SIGNAL(MediaPlayerKeyPressed(QString, QString)), this, SLOT(keyPressed(QString,QString))); disconnectDaemon(); } } void GnomeMediaKeys::keyPressed(const QString &app, const QString &key) { if (QCoreApplication::applicationName()!=app) { return; } if (QLatin1String("Play")==key) { emit playPause(); } else if (QLatin1String("Stop")==key) { emit stop(); } else if (QLatin1String("Next")==key) { emit next(); } else if (QLatin1String("Previous")==key) { emit previous(); } } void GnomeMediaKeys::pluginActivated(const QString &name) { if (QLatin1String("media-keys")==name) { grabKeys(); } } cantata-2.2.0/dbus/gnomemediakeys.h000066400000000000000000000034401316350454000172370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef GNOME_MEDIA_KEYS_H #define GNOME_MEDIA_KEYS_H #include "gui/multimediakeysinterface.h" class OrgGnomeSettingsDaemonInterface; class OrgGnomeSettingsDaemonMediaKeysInterface; class QDBusPendingCallWatcher; class QDBusServiceWatcher; class GnomeMediaKeys : public MultiMediaKeysInterface { Q_OBJECT public: GnomeMediaKeys(QObject *p); bool activate(); void deactivate(); private: bool daemonIsRunning(); void releaseKeys(); void grabKeys(); void disconnectDaemon(); private Q_SLOTS: void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void registerFinished(QDBusPendingCallWatcher *watcher); void keyPressed(const QString &app, const QString &key); void pluginActivated(const QString &name); private: OrgGnomeSettingsDaemonInterface *daemon; OrgGnomeSettingsDaemonMediaKeysInterface *mk; QDBusServiceWatcher *watcher; QString serviceName; }; #endif cantata-2.2.0/dbus/mpd.cantata.xml000066400000000000000000000013551316350454000170040ustar00rootroot00000000000000 cantata-2.2.0/dbus/mpris.cpp000066400000000000000000000201631316350454000157240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mpris.h" #include "mpd-interface/mpdconnection.h" #include "playeradaptor.h" #include "rootadaptor.h" #include "config.h" #include "gui/currentcover.h" static inline qlonglong convertTime(int t) { return t*1000000; } Mpris::Mpris(QObject *p) : QObject(p) , pos(-1) { QDBusConnection::sessionBus().registerService("org.mpris.MediaPlayer2.cantata"); new PlayerAdaptor(this); new MediaPlayer2Adaptor(this); QDBusConnection::sessionBus().registerObject("/org/mpris/MediaPlayer2", this, QDBusConnection::ExportAdaptors); connect(this, SIGNAL(setRandom(bool)), MPDConnection::self(), SLOT(setRandom(bool))); connect(this, SIGNAL(setRepeat(bool)), MPDConnection::self(), SLOT(setRepeat(bool))); connect(this, SIGNAL(setSeekId(qint32, quint32)), MPDConnection::self(), SLOT(setSeekId(qint32, quint32))); connect(this, SIGNAL(setVolume(int)), MPDConnection::self(), SLOT(setVolume(int))); // connect(MPDConnection::self(), SIGNAL(currentSongUpdated(const Song &)), this, SLOT(updateCurrentSong(const Song &))); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus())); connect(CurrentCover::self(), SIGNAL(coverFile(const QString &)), this, SLOT(updateCurrentCover(const QString &))); } Mpris::~Mpris() { QDBusConnection::sessionBus().unregisterService("org.mpris.MediaPlayer2.cantata"); } void Mpris::Pause() { if (MPDState_Playing==MPDStatus::self()->state()) { StdActions::self()->playPauseTrackAction->trigger(); } } void Mpris::Play() { MPDStatus * const status = MPDStatus::self(); if (status->playlistLength() && MPDState_Playing!=status->state()) { StdActions::self()->playPauseTrackAction->trigger(); } } void Mpris::SetPosition(const QDBusObjectPath &trackId, qlonglong pos) { if (trackId.path()==currentTrackId()) { emit setSeekId(-1, pos/1000000); } } QString Mpris::PlaybackStatus() const { switch(MPDStatus::self()->state()) { case MPDState_Playing: return QLatin1String("Playing"); case MPDState_Paused: return QLatin1String("Paused"); default: case MPDState_Stopped: return QLatin1String("Stopped"); } } qlonglong Mpris::Position() const { // Cant use MPDStatus, as we dont poll for track position, but use a timer instead! //return MPDStatus::self()->timeElapsed(); return convertTime(MPDStatus::self()->guessedElapsed()); } void Mpris::updateStatus() { QVariantMap map; if (MPDStatus::self()->repeat()!=status.repeat) { map.insert("LoopStatus", LoopStatus()); } if (MPDStatus::self()->random()!=status.random) { map.insert("Shuffle", Shuffle()); } if (MPDStatus::self()->volume()!=status.volume) { map.insert("Volume", Volume()); } if (MPDStatus::self()->state()!=status.state || MPDStatus::self()->playlistLength()!=status.playlistLength) { map.insert("CanGoNext", CanGoNext()); map.insert("CanGoPrevious", CanGoPrevious()); } if (MPDStatus::self()->state()!=status.state) { map.insert("PlaybackStatus", PlaybackStatus()); //map.insert("CanPlay", CanPlay()); //map.insert("CanPause", CanPause()); map.insert("CanSeek", CanSeek()); } if (MPDStatus::self()->timeElapsed()!=status.timeElapsed) { map.insert("Position", convertTime(MPDStatus::self()->timeElapsed())); } if (!map.isEmpty() || MPDStatus::self()->songId()!=status.songId) { if (!map.contains("Position")) { map.insert("Position", convertTime(MPDStatus::self()->timeElapsed())); } map.insert("Metadata", Metadata()); signalUpdate(map); } status=MPDStatus::self()->getValues(); } void Mpris::updateCurrentCover(const QString &fileName) { if (fileName!=currentCover) { currentCover=fileName; signalUpdate("Metadata", Metadata()); } } void Mpris::updateCurrentSong(const Song &song) { currentSong = song; signalUpdate("Metadata", Metadata()); } QVariantMap Mpris::Metadata() const { QVariantMap metadataMap; if ((!currentSong.title.isEmpty() && !currentSong.artist.isEmpty()) || (currentSong.isStandardStream() && !currentSong.name().isEmpty())) { metadataMap.insert("mpris:trackid", currentTrackId()); QString artist=currentSong.artist; QString album=currentSong.album; QString title=currentSong.title; if (currentSong.isStandardStream()) { if (artist.isEmpty()) { artist=currentSong.name(); } else if (album.isEmpty()) { album=currentSong.name(); } if (title.isEmpty()) { title=tr("(Stream)"); } } if (currentSong.time>0) { metadataMap.insert("mpris:length", convertTime(currentSong.time)); } if (!album.isEmpty()) { metadataMap.insert("xesam:album", album); } if (!currentSong.albumartist.isEmpty() && currentSong.albumartist!=currentSong.artist) { metadataMap.insert("xesam:albumArtist", QStringList() << currentSong.albumartist); } if (!artist.isEmpty()) { metadataMap.insert("xesam:artist", QStringList() << artist); } if (!title.isEmpty()) { metadataMap.insert("xesam:title", title); } if (!currentSong.genres[0].isEmpty()) { metadataMap.insert("xesam:genre", QStringList() << currentSong.genres[0]); } if (currentSong.track>0) { metadataMap.insert("xesam:trackNumber", currentSong.track); } if (currentSong.disc>0) { metadataMap.insert("xesam:discNumber", currentSong.disc); } if (currentSong.year>0) { metadataMap.insert("xesam:contentCreated", QString("%04d").arg(currentSong.year)); } if (!currentSong.file.isEmpty()) { if (currentSong.isNonMPD()) { metadataMap.insert("xesam:url", currentSong.file); } else if (MPDConnection::self()->getDetails().dirReadable) { QString mpdDir=MPDConnection::self()->getDetails().dir; if (!mpdDir.isEmpty()) { metadataMap.insert("xesam:url", "file://"+mpdDir+currentSong.file); } } } if (!currentCover.isEmpty()) { metadataMap.insert("mpris:artUrl", "file://"+currentCover); } } return metadataMap; } void Mpris::Raise() { emit showMainWindow(); } void Mpris::signalUpdate(const QString &property, const QVariant &value) { QVariantMap map; map.insert(property, value); signalUpdate(map); } void Mpris::signalUpdate(const QVariantMap &map) { if (map.isEmpty()) { return; } QDBusMessage signal = QDBusMessage::createSignal("/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged"); QVariantList args = QVariantList() << "org.mpris.MediaPlayer2.Player" << map << QStringList(); signal.setArguments(args); QDBusConnection::sessionBus().send(signal); } QString Mpris::currentTrackId() const { return QString("/org/mpris/MediaPlayer2/Track/%1").arg(QString::number(currentSong.id)); } cantata-2.2.0/dbus/mpris.h000066400000000000000000000126131316350454000153720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MPRIS_H #define MPRIS_H #include #include #include #include #include "mpd-interface/song.h" #include "mpd-interface/mpdstatus.h" #include "gui/stdactions.h" class QDBusObjectPath; class Mpris : public QObject { Q_OBJECT // org.mpris.MediaPlayer2.Player Q_PROPERTY( double Rate READ Rate WRITE SetRate ) Q_PROPERTY( qlonglong Position READ Position ) Q_PROPERTY( double MinimumRate READ MinimumRate ) Q_PROPERTY( double MaximumRate READ MaximumRate ) Q_PROPERTY( bool CanControl READ CanControl ) Q_PROPERTY( bool CanPlay READ CanPlay ) Q_PROPERTY( bool CanPause READ CanPause ) Q_PROPERTY( bool CanSeek READ CanSeek ) Q_PROPERTY( bool CanGoNext READ CanGoNext ) Q_PROPERTY( bool CanGoPrevious READ CanGoPrevious ) Q_PROPERTY( QString PlaybackStatus READ PlaybackStatus ) Q_PROPERTY( QString LoopStatus READ LoopStatus WRITE SetLoopStatus ) Q_PROPERTY( bool Shuffle READ Shuffle WRITE SetShuffle ) Q_PROPERTY( QVariantMap Metadata READ Metadata ) Q_PROPERTY( double Volume READ Volume WRITE SetVolume ) // org.mpris.MediaPlayer2 Q_PROPERTY( bool CanQuit READ CanQuit ) Q_PROPERTY( bool CanRaise READ CanRaise ) Q_PROPERTY( QString DesktopEntry READ DesktopEntry ) Q_PROPERTY( bool HasTrackList READ HasTrackList ) Q_PROPERTY( QString Identity READ Identity ) Q_PROPERTY( QStringList SupportedMimeTypes READ SupportedMimeTypes ) Q_PROPERTY( QStringList SupportedUriSchemes READ SupportedUriSchemes ) public: Mpris(QObject *p); virtual ~Mpris(); // org.mpris.MediaPlayer2.Player void Next() { StdActions::self()->nextTrackAction->trigger(); } void Previous() { StdActions::self()->prevTrackAction->trigger(); } void Pause(); void PlayPause() { StdActions::self()->playPauseTrackAction->trigger(); } void Stop() { StdActions::self()->stopPlaybackAction->trigger(); } void StopAfterCurrent() { StdActions::self()->stopAfterCurrentTrackAction->trigger(); } void Play(); void Seek(qlonglong pos) { emit setSeekId(-1, pos/1000000); } void SetPosition(const QDBusObjectPath &trackId, qlonglong pos); void OpenUri(const QString &) { } QString PlaybackStatus() const; QString LoopStatus() { return MPDStatus::self()->repeat() ? QLatin1String("Playlist") : QLatin1String("None"); } void SetLoopStatus(const QString &s) { emit setRepeat(QLatin1String("None")!=s); } QVariantMap Metadata() const; int Rate() const { return 1.0; } void SetRate(double) { } bool Shuffle() { return MPDStatus::self()->random(); } void SetShuffle(bool s) { emit setRandom(s); } double Volume() const { return MPDStatus::self()->volume()/100.0; } void SetVolume(double v) { emit setVolume(v*100); } qlonglong Position() const; double MinimumRate() const { return 1.0; } double MaximumRate() const { return 1.0; } bool CanControl() const { return true; } bool CanPlay() const { return true; } bool CanPause() const { return true; } bool CanSeek() const { return -1!=MPDStatus::self()->songId() && !currentSong.isCdda() && !currentSong.isStandardStream() && currentSong.time>5; } bool CanGoNext() const { return MPDState_Stopped!=MPDStatus::self()->state() && MPDStatus::self()->playlistLength()>1; } bool CanGoPrevious() const { return MPDState_Stopped!=MPDStatus::self()->state() && MPDStatus::self()->playlistLength()>1; } // org.mpris.MediaPlayer2 bool CanQuit() const { return true; } bool CanRaise() const { return true; } bool HasTrackList() const { return false; } QString Identity() const { return QLatin1String("Cantata"); } QString DesktopEntry() const { return QLatin1String("cantata"); } QStringList SupportedUriSchemes() const { return QStringList(); } QStringList SupportedMimeTypes() const { return QStringList(); } public: void updateCurrentSong(const Song &song); public Q_SLOTS: void Raise(); void Quit() { QApplication::quit(); } Q_SIGNALS: // org.mpris.MediaPlayer2.Player void setRandom(bool toggle); void setRepeat(bool toggle); void setSeekId(qint32 songId, quint32 time); void setVolume(int vol); void showMainWindow(); public Q_SLOTS: void updateCurrentCover(const QString &fileName); private Q_SLOTS: void updateStatus(); private: void signalUpdate(const QString &property, const QVariant &value); void signalUpdate(const QVariantMap &map); QString currentTrackId() const; private: MPDStatusValues status; QString currentCover; Song currentSong; int pos; }; #endif cantata-2.2.0/dbus/notify.cpp000066400000000000000000000100101316350454000160700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "notify.h" #include "notificationsinterface.h" #include #include #include #include #include QDBusArgument& operator<< (QDBusArgument &arg, const QImage &image) { if (image.isNull()) { // Sometimes this gets called with a null QImage for no obvious reason. arg.beginStructure(); arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray(); arg.endStructure(); return arg; } QImage scaled = image.scaledToHeight(128, Qt::SmoothTransformation).convertToFormat(QImage::Format_ARGB32); #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN // ABGR -> ARGB QImage i = scaled.rgbSwapped(); #else // ABGR -> GBAR QImage i(scaled.size(), scaled.format()); for (int y = 0; y < i.height(); ++y) { QRgb *p = (QRgb*) scaled.scanLine(y); QRgb *q = (QRgb*) i.scanLine(y); QRgb *end = p + scaled.width(); while (p < end) { *q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p)); p++; q++; } } #endif arg.beginStructure(); arg << i.width(); arg << i.height(); arg << i.bytesPerLine(); arg << i.hasAlphaChannel(); int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3); arg << i.depth() / channels; arg << channels; arg << QByteArray(reinterpret_cast(i.bits()), i.byteCount()); arg.endStructure(); return arg; } const QDBusArgument& operator>> (const QDBusArgument &arg, QImage &image) { // This is needed to link but shouldn't be called. Q_ASSERT(0); Q_UNUSED(image) return arg; } static const int constTimeout=5000; Notify::Notify(QObject *p) : QObject(p) , lastId(0) { qDBusRegisterMetaType(); iface=new OrgFreedesktopNotificationsInterface(OrgFreedesktopNotificationsInterface::staticInterfaceName(), "/org/freedesktop/Notifications", QDBusConnection::sessionBus()); } void Notify::show(const QString &title, const QString &text, const QImage &img) { QVariantMap hints; if (!img.isNull()) { hints["image_data"] = QVariant(img); } int id = 0; if (lastTime.secsTo(QDateTime::currentDateTime()) * 1000 < constTimeout) { // Reuse the existing popup if it's still open. The reason we don't always // reuse the popup is because the notification daemon on KDE4 won't re-show // the bubble if it's already gone to the tray. See issue #118 id = lastId; } QDBusPendingReply reply = iface->Notify(QCoreApplication::applicationName(), id, "cantata", title, text, QStringList(), hints, constTimeout); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(callFinished(QDBusPendingCallWatcher*))); } void Notify::callFinished(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; watcher->deleteLater(); if (reply.isError()) { return; } uint id = reply.value(); if (id != 0) { lastId = id; lastTime = QDateTime::currentDateTime(); } } cantata-2.2.0/dbus/notify.h000066400000000000000000000030221316350454000155420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NOTIFY_H #define NOTIFY_H #include #include class QImage; class QDBusPendingCallWatcher; class QDBusArgument; class OrgFreedesktopNotificationsInterface; QDBusArgument& operator<< (QDBusArgument &arg, const QImage &image); const QDBusArgument& operator>> (const QDBusArgument &arg, QImage &image); class Notify : public QObject { Q_OBJECT public: Notify(QObject *p); virtual ~Notify() { } void show(const QString &title, const QString &text, const QImage &img); private Q_SLOTS: void callFinished(QDBusPendingCallWatcher *watcher); private: QDateTime lastTime; int lastId; OrgFreedesktopNotificationsInterface *iface; }; #endif cantata-2.2.0/dbus/org.freedesktop.Notifications.xml000066400000000000000000000024431316350454000225220ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.freedesktop.PowerManagement.Inhibit.xml000066400000000000000000000013461316350454000243700ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.freedesktop.UPower.xml000066400000000000000000000030441316350454000211300ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.freedesktop.login1.xml000066400000000000000000000154621316350454000211070ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.gnome.SettingsDaemon.MediaKeys.xml000066400000000000000000000011331316350454000232740ustar00rootroot00000000000000 " cantata-2.2.0/dbus/org.gnome.SettingsDaemon.xml000066400000000000000000000006471316350454000214330ustar00rootroot00000000000000 " cantata-2.2.0/dbus/org.kde.Solid.PowerManagement.PolicyAgent.xml000066400000000000000000000010211316350454000245070ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.mpris.MediaPlayer2.Player.xml000066400000000000000000000033311316350454000222360ustar00rootroot00000000000000 cantata-2.2.0/dbus/org.mpris.MediaPlayer2.root.xml000066400000000000000000000013371316350454000217710ustar00rootroot00000000000000 cantata-2.2.0/dbus/powermanagement.cpp000066400000000000000000000101201316350454000177530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "powermanagement.h" #include "support/globalstatic.h" #include "inhibitinterface.h" #include "policyagentinterface.h" #include "upowerinterface.h" #include "login1interface.h" #include "mpd-interface/mpdstatus.h" GLOBAL_STATIC(PowerManagement, instance) PowerManagement::PowerManagement() : inhibitSuspendWhilstPlaying(false) , cookie(-1) { policy = new OrgKdeSolidPowerManagementPolicyAgentInterface(OrgKdeSolidPowerManagementPolicyAgentInterface::staticInterfaceName(), QLatin1String("/org/kde/Solid/PowerManagement/PolicyAgent"), QDBusConnection::sessionBus(), this); inhibit = new OrgFreedesktopPowerManagementInhibitInterface(OrgFreedesktopPowerManagementInhibitInterface::staticInterfaceName(), QLatin1String("/org/freedesktop/PowerManagement/Inhibit"), QDBusConnection::sessionBus(), this); upower = new OrgFreedesktopUPowerInterface(OrgFreedesktopUPowerInterface::staticInterfaceName(), QLatin1String("/org/freedesktop/UPower"), QDBusConnection::systemBus(), this); login1 = new OrgFreedesktopLogin1ManagerInterface("org.freedesktop.login1", QLatin1String("/org/freedesktop/login1"), QDBusConnection::systemBus(), this); connect(upower, SIGNAL(Resuming()), this, SIGNAL(resuming())); connect(login1, SIGNAL(PrepareForSleep(bool)), this, SLOT(prepareForSleep(bool))); } void PowerManagement::setInhibitSuspend(bool i) { if (i==inhibitSuspendWhilstPlaying) { return; } inhibitSuspendWhilstPlaying=i; if (inhibitSuspendWhilstPlaying) { connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(mpdStatusUpdated())); if (MPDState_Playing==MPDStatus::self()->state()) { beginSuppressingSleep(); } } else { disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(mpdStatusUpdated())); stopSuppressingSleep(); } } void PowerManagement::beginSuppressingSleep() { if (-1!=cookie) { return; } QString reason=tr("Cantata is playing a track"); QDBusReply reply; if (policy->isValid()) { reply = policy->AddInhibition((uint)1, QCoreApplication::applicationName(), reason); } else { // Fallback to the fd.o Inhibit interface reply = inhibit->Inhibit(QCoreApplication::applicationName(), reason); } cookie=reply.isValid() ? reply : -1; } void PowerManagement::stopSuppressingSleep() { if (-1==cookie) { return; } if (policy->isValid()) { policy->ReleaseInhibition(cookie); } else { // Fallback to the fd.o Inhibit interface inhibit->UnInhibit(cookie); } cookie=-1; } void PowerManagement::mpdStatusUpdated() { if (inhibitSuspendWhilstPlaying) { if (MPDState_Playing==MPDStatus::self()->state()) { beginSuppressingSleep(); } else { stopSuppressingSleep(); } } } void PowerManagement::prepareForSleep(bool s) { if (!s) { emit resuming(); } } cantata-2.2.0/dbus/powermanagement.h000066400000000000000000000033451316350454000174330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef POWERMANAGEMENT_H #define POWERMANAGEMENT_H #include #include class OrgKdeSolidPowerManagementPolicyAgentInterface; class OrgFreedesktopPowerManagementInhibitInterface; class OrgFreedesktopUPowerInterface; class OrgFreedesktopLogin1ManagerInterface; class PowerManagement : public QObject { Q_OBJECT public: static PowerManagement * self(); PowerManagement(); void setInhibitSuspend(bool i); void beginSuppressingSleep(); void stopSuppressingSleep(); Q_SIGNALS: void resuming(); private Q_SLOTS: void mpdStatusUpdated(); void prepareForSleep(bool s); private: bool inhibitSuspendWhilstPlaying; int cookie; OrgKdeSolidPowerManagementPolicyAgentInterface *policy; OrgFreedesktopPowerManagementInhibitInterface *inhibit; OrgFreedesktopUPowerInterface *upower; OrgFreedesktopLogin1ManagerInterface *login1; }; #endif cantata-2.2.0/devices/000077500000000000000000000000001316350454000145515ustar00rootroot00000000000000cantata-2.2.0/devices/actiondialog.cpp000066400000000000000000001032001316350454000177060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "actiondialog.h" #include "models/devicesmodel.h" #include "device.h" #include "support/utils.h" #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "gui/settings.h" #include "models/musiclibraryproxymodel.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdconnection.h" #include "encoders.h" #include "support/messagebox.h" #include "filejob.h" #include "freespaceinfo.h" #include "widgets/icons.h" #include "config.h" #include "tags/tags.h" #include "widgets/treeview.h" #ifdef ENABLE_ONLINE_SERVICES____TODO #include "models/onlineservicesmodel.h" #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT #include "replaygain/rgdialog.h" #endif #include #include #ifdef QT_QTDBUS_FOUND #include #endif #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0 static int iCount=0; int ActionDialog::instanceCount() { return iCount; } enum Pages { PAGE_SIZE_CALC, PAGE_INSUFFICIENT_SIZE, PAGE_START, PAGE_ERROR, PAGE_SKIP, PAGE_PROGRESS }; ActionDialog::ActionDialog(QWidget *parent) : Dialog(parent) , spaceRequired(0) , mpdConfigured(false) , currentDev(0) , songDialog(0) { iCount++; setButtons(Ok|Cancel); setDefaultButton(Cancel); setAttribute(Qt::WA_DeleteOnClose); QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); errorIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(64, 64)); skipIcon->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(64, 64)); configureSourceButton->setIcon(Icons::self()->configureIcon); configureDestButton->setIcon(Icons::self()->configureIcon); connect(configureSourceButton, SIGNAL(clicked()), SLOT(configureSource())); connect(configureDestButton, SIGNAL(clicked()), SLOT(configureDest())); connect(this, SIGNAL(update()), MPDConnection::self(), SLOT(update())); // connect(songCount, SIGNAL(leftClickedUrl()), SLOT(showSongs())); #ifdef QT_QTDBUS_FOUND unityMessage=QDBusMessage::createSignal("/Cantata", "com.canonical.Unity.LauncherEntry", "Update"); #endif } ActionDialog::~ActionDialog() { iCount--; updateUnity(true); } void ActionDialog::controlInfoLabel(Device *dev) { DeviceOptions opts; if (sourceIsAudioCd) { opts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); } else if (sourceUdi.isEmpty()) { opts=dev->options(); } if (opts.transcoderCodec.isEmpty()) { codecLabel->setVisible(false); codec->setVisible(false); } else { codecLabel->setVisible(true); codec->setVisible(true); Encoders::Encoder encoder=Encoders::getEncoder(opts.transcoderCodec); if (Encoders::getEncoder(opts.transcoderCodec).codec.isEmpty()) { codec->setText(tr("INVALID")); } else if (encoder.values.count()>1) { int settingIndex=0; bool increase=encoder.values.at(0).valueopts.transcoderValue) || (!increase && s.valuesetText(QString("%1 (%2)").arg(encoder.name).arg(encoder.values.at(settingIndex).descr)+ (sourceIsAudioCd && opts.transcoderWhenDifferent ? QLatin1String("")+tr("(When different)") : QString())); } else { codec->setText(encoder.name+ (sourceIsAudioCd && opts.transcoderWhenDifferent ? QLatin1String("")+tr("(When different)") : QString())); } } } void ActionDialog::deviceRenamed() { Device *dev=getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi); if (!dev) { return; } if (Remove==mode || (Copy==mode && !sourceUdi.isEmpty())) { sourceLabel->setText(QLatin1String("")+dev->data()+QLatin1String("")); } else { destinationLabel->setText(QLatin1String("")+dev->data()+QLatin1String("")); } } void ActionDialog::updateSongCountLabel() { QSet artists; QSet albums; foreach (const Song &s, songsToAction) { artists.insert(s.albumArtist()); albums.insert(s.albumArtist()+"--"+s.album); } songCount->setText(tr("Artists:%1, Albums:%2, Songs:%3").arg(artists.count()).arg(albums.count()).arg(songsToAction.count())); } void ActionDialog::controlInfoLabel() { controlInfoLabel(getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi)); } void ActionDialog::calcFileSize() { Device *dev=0; int toCalc=qMin(50, songsToCalcSize.size()); for (int i=0; igetDetails().dir+s.file).size(); } else { if (!dev) { dev=getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi); } if (!dev) { deleteLater(); return; } if (QFile::exists(dev->path()+s.file)) { // FS device... size=QFileInfo(dev->path()+s.file).size(); } } } if (size>0) { spaceRequired+=size; } if (!haveVariousArtists && s.isVariousArtists()) { haveVariousArtists=true; } } fileSizeProgress->setValue(fileSizeProgress->value()+toCalc); if (!songsToCalcSize.isEmpty()) { QTimer::singleShot(0, this, SLOT(calcFileSize())); } else { qint64 spaceAvailable=0; double usedCapacity=0.0; QString capacityString; //bool isFromOnline=sourceUdi.startsWith(OnlineServicesModel::constUdiPrefix); if (!dev) { dev=getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi); } if (!dev) { deleteLater(); return; } if (sourceUdi.isEmpty()) { // If source UDI is empty, then we are copying from MPD->device, so need device free space. spaceAvailable=dev->freeSpace(); capacityString=dev->capacityString(); usedCapacity=dev->usedCapacity(); } else { FreeSpaceInfo inf=FreeSpaceInfo(MPDConnection::self()->getDetails().dir); spaceAvailable=inf.size()-inf.used(); usedCapacity=(inf.used()*1.0)/(inf.size()*1.0); capacityString=tr("%1 free").arg(Utils::formatByteSize(inf.size()-inf.used())); } bool enoughSpace=spaceAvailable>spaceRequired; #ifdef ENABLE_REMOTE_DEVICES if (!enoughSpace && sourceUdi.isEmpty() && 0==spaceAvailable && usedCapacity<0.0 && Device::RemoteFs==dev->devType()) { enoughSpace=true; } #endif if (enoughSpace || (sourceUdi.isEmpty() && Encoders::getAvailable().count())) { QString mpdCfgName=MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name); overwrite->setChecked(Settings::self()->overwriteSongs()); sourceLabel->setText(QLatin1String("")+(sourceUdi.isEmpty() ? tr("Local Music Library") : sourceIsAudioCd ? tr("Audio CD") : dev->data())+QLatin1String("")); destinationLabel->setText(QLatin1String("")+(destUdi.isEmpty() ? tr("Local Music Library") : dev->data())+QLatin1String("")); namingOptions.load(mpdCfgName, true); capacity->update(capacityString, (usedCapacity*100)+0.5); bool destIsDev=sourceUdi.isEmpty(); mpdConfigured=DeviceOptions::isConfigured(mpdCfgName, true); configureDestLabel->setVisible((destIsDev && !dev->isConfigured()) || (!destIsDev && !mpdConfigured)); //configureSourceButton->setVisible(!isFromOnline); //configureSourceLabel->setVisible(!isFromOnline && ((!destIsDev && !dev->isConfigured()) || (destIsDev && !mpdConfigured))); configureSourceButton->setVisible(false); configureSourceLabel->setVisible(false); songCount->setVisible(!sourceIsAudioCd); songCountLabel->setVisible(!sourceIsAudioCd); if (!sourceIsAudioCd) { updateSongCountLabel(); } if (enoughSpace) { setPage(PAGE_START); } else { setPage(PAGE_INSUFFICIENT_SIZE); sizeInfoIcon->setPixmap(*(skipIcon->pixmap())); sizeInfoText->setText(tr("There is insufficient space left on the destination device.\n\n" "The selected songs consume %1, but there is only %2 left.\n" "The songs will need to be transcoded to a smaller filesize in order to be successfully copied.") .arg(Utils::formatByteSize(spaceRequired)) .arg(Utils::formatByteSize(spaceAvailable))); } } else { setPage(PAGE_INSUFFICIENT_SIZE); setButtons(Cancel); sizeInfoIcon->setPixmap(*(errorIcon->pixmap())); sizeInfoText->setText(tr("There is insufficient space left on the destination.\n\n" "The selected songs consume %1, but there is only %2 left.") .arg(Utils::formatByteSize(spaceRequired)) .arg(Utils::formatByteSize(spaceAvailable))); } } } void ActionDialog::sync(const QString &devId, const QList &libSongs, const QList &devSongs) { // If only copying one way, then just use standard copying... bool toLib=libSongs.isEmpty(); if (toLib || devSongs.isEmpty()) { copy(toLib ? devId : QString(), toLib ? QString() : devId, toLib ? devSongs : libSongs); setCaption(toLib ? tr("Copy Songs To Library") : tr("Copy Songs To Device")); return; } init(toLib ? devId : QString(), toLib ? QString() : devId, toLib ? devSongs : libSongs, Sync); Device *dev=getDevice(devId); if (!dev) { deleteLater(); return; } progressBar->setRange(0, (libSongs.count()+devSongs.count())*100); sourceIsAudioCd=false; controlInfoLabel(dev); fileSizeProgress->setMinimum(0); fileSizeProgress->setMaximum(songsToAction.size()); songsToCalcSize=songsToAction; syncSongs=devSongs; setCaption(toLib ? tr("Copy Songs To Library") : tr("Copy Songs To Device")); setPage(PAGE_SIZE_CALC); show(); calcFileSize(); } void ActionDialog::copy(const QString &srcUdi, const QString &dstUdi, const QList &songs) { init(srcUdi, dstUdi, songs, Copy); Device *dev=getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi); if (!dev) { deleteLater(); return; } sourceIsAudioCd=Device::AudioCd==dev->devType(); controlInfoLabel(dev); fileSizeProgress->setMinimum(0); fileSizeProgress->setMaximum(songs.size()); songsToCalcSize=songs; setPage(PAGE_SIZE_CALC); show(); calcFileSize(); } void ActionDialog::remove(const QString &udi, const QList &songs) { init(udi, QString(), songs, Remove); codecLabel->setVisible(false); codec->setVisible(false); songCountLabel->setVisible(false); songCount->setVisible(false); QString baseDir; if (udi.isEmpty()) { baseDir=MPDConnection::self()->getDetails().dir; } else { Device *dev=getDevice(udi); if (!dev) { deleteLater(); return; } baseDir=dev->path(); } setPage(PAGE_PROGRESS); foreach (const Song &s, songsToAction) { dirsToClean.insert(baseDir+Utils::getDir(s.file)); } show(); doNext(); } void ActionDialog::init(const QString &srcUdi, const QString &dstUdi, const QList &songs, Mode m) { resize(500, 160); sourceUdi=srcUdi; destUdi=dstUdi; sourceIsAudioCd=false; songsToAction=songs; mode=m; setCaption(Copy==mode || Sync==mode ? tr("Copy Songs") : tr("Delete Songs")); qSort(songsToAction); progressLabel->setText(QString()); progressBar->setValue(0); progressBar->setRange(0, (Copy==mode || Sync==mode ? songsToAction.count() : (songsToAction.count()+1))*100); autoSkip=false; performingAction=false; paused=false; actionedSongs.clear(); skippedSongs.clear(); currentPercent=0; currentDev=0; count=0; #ifdef ENABLE_REPLAYGAIN_SUPPORT albumsWithoutRgTags.clear(); #endif updateUnity(false); } void ActionDialog::slotButtonClicked(int button) { switch(stack->currentIndex()) { case PAGE_SIZE_CALC: Dialog::slotButtonClicked(button); break; case PAGE_INSUFFICIENT_SIZE: if (Ok==button) { setPage(PAGE_START); } else { Dialog::slotButtonClicked(button); } break; case PAGE_START: switch (button) { case Ok: if (haveVariousArtists && ((configureDestLabel->isVisible() && sourceUdi.isEmpty() && // Only warn if copying FROM library MessageBox::No==MessageBox::warningYesNo(this, tr("You have not configured the destination device.\n\n" "Continue with the default settings?"), tr("Not Configured"), GuiItem(tr("Use Defaults")), StdGuiItem::cancel())) || (configureSourceLabel->isVisible() && !sourceUdi.isEmpty() && // Only warn if copying TO library MessageBox::No==MessageBox::warningYesNo(this, tr("You have not configured the source device.\n\n" "Continue with the default settings?"), tr("Not Configured"), GuiItem(tr("Use Defaults")), StdGuiItem::cancel())) ) ) { return; } Settings::self()->saveOverwriteSongs(overwrite->isChecked()); setPage(PAGE_PROGRESS); // hideSongs(); doNext(); break; case Cancel: refreshLibrary(); reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: Dialog::slotButtonClicked(button); break; } break; case PAGE_SKIP: setPage(PAGE_PROGRESS); switch(button) { case User1: skippedSongs.append(currentSong); incProgress(); doNext(); break; case User2: autoSkip=true; incProgress(); doNext(); break; case User3: songsToAction.prepend(origCurrentSong); doNext(); break; default: refreshLibrary(); reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; } break; case PAGE_ERROR: refreshLibrary(); reject(); break; case PAGE_PROGRESS: paused=true; if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Are you sure you wish to stop?"), tr("Stop"), StdGuiItem::stop(), StdGuiItem::cont())) { Device *dev=0; if(Copy==mode || Sync==mode) { dev=getDevice(sourceUdi.isEmpty() ? destUdi : sourceUdi, false); } else if (!sourceUdi.isEmpty()) { // Must be a delete... dev=getDevice(sourceUdi, false); } if (dev) { if (Close==button) { // Close is only enabled when saving cache... disconnect(dev, SIGNAL(cacheSaved()), this, SLOT(cacheSaved())); } else { dev->abortJob(); } } if (Close!=button) { refreshLibrary(); } reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); } else if (!performingAction && PAGE_PROGRESS==stack->currentIndex()) { paused=false; incProgress(); doNext(); } } } Device * ActionDialog::getDevice(const QString &udi, bool logErrors) { #ifdef ENABLE_ONLINE_SERVICES____TODO if (udi.startsWith(OnlineServicesModel::constUdiPrefix)) { return OnlineServicesModel::self()->device(udi); } #endif Device *dev=DevicesModel::self()->device(udi); if (!logErrors) { return dev; } QString error; if (!dev) { error=tr("Device has been removed!"); } else if (!dev->isConnected()) { error=tr("Device is not connected!"); } else if (!dev->isIdle()) { error=tr("Device is busy?"); } else if (currentDev && dev!=currentDev) { error=tr("Device has been changed?"); } if (error.isEmpty()) { return dev; } if (isVisible()) { setPage(PAGE_ERROR, StringPairList(), error); } else { MessageBox::error(parentWidget(), error); } return 0; } void ActionDialog::doNext() { currentPercent=0; if (songsToAction.isEmpty() && Sync==mode && !syncSongs.isEmpty()) { songsToAction=syncSongs; syncSongs.clear(); sourceUdi=destUdi; destUdi=QString(); setCaption(tr("Copy Songs To Library")); } if (songsToAction.count()) { currentSong=origCurrentSong=songsToAction.takeFirst(); if(Copy==mode || Sync==mode) { bool copyToDev=sourceUdi.isEmpty(); Device *dev=getDevice(copyToDev ? destUdi : sourceUdi); if (dev) { if (!currentDev) { connect(dev, SIGNAL(actionStatus(int, bool)), this, SLOT(actionStatus(int, bool))); connect(dev, SIGNAL(progress(int)), this, SLOT(jobPercent(int))); currentDev=dev; } performingAction=true; if (copyToDev) { destFile=dev->path()+dev->options().createFilename(currentSong); currentSong.file=MPDConnection::self()->getDetails().dir+currentSong.filePath(); dev->addSong(currentSong, overwrite->isChecked(), !copiedCovers.contains(Utils::getDir(destFile))); } else { Song copy=currentSong; if (dev->options().fixVariousArtists && currentSong.isVariousArtists()) { Device::fixVariousArtists(QString(), copy, false); } QString fileName=namingOptions.createFilename(copy); destFile=MPDConnection::self()->getDetails().dir+fileName; dev->copySongTo(currentSong, fileName, overwrite->isChecked(), !copiedCovers.contains(Utils::getDir(destFile))); } } } else { if (sourceUdi.isEmpty()) { performingAction=true; currentSong.file=MPDConnection::self()->getDetails().dir+currentSong.file; removeSong(currentSong); } else { Device *dev=getDevice(sourceUdi); if (dev) { if (dev!=currentDev) { connect(dev, SIGNAL(actionStatus(int)), this, SLOT(actionStatus(int))); currentDev=dev; } performingAction=true; dev->removeSong(currentSong); } } } progressLabel->setText(formatSong(currentSong, false)); } else if (Remove==mode && dirsToClean.count()) { Device *dev=sourceUdi.isEmpty() ? 0 : DevicesModel::self()->device(sourceUdi); if (sourceUdi.isEmpty() || dev) { progressLabel->setText(tr("Clearing unused folders")); if (dev) { dev->cleanDirs(dirsToClean); } else { cleanDirs(); } } dirsToClean.clear(); } else { if (!refreshLibrary()) { emit completed(); accept(); #ifdef ENABLE_REPLAYGAIN_SUPPORT if (Copy==mode && !albumsWithoutRgTags.isEmpty() && sourceIsAudioCd) { QWidget *pw=parentWidget(); if (MessageBox::Yes==MessageBox::questionYesNo(pw, tr("Calculate ReplayGain for ripped tracks?"), tr("ReplayGain"), GuiItem(tr("Calculate")), StdGuiItem::no())) { RgDialog *dlg=new RgDialog(pw); QList songs; DeviceOptions opts; opts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); Encoders::Encoder encoder=Encoders::getEncoder(opts.transcoderCodec); foreach (const Song &s, actionedSongs) { if (albumsWithoutRgTags.contains(s.album)) { Song song=s; song.file=encoder.changeExtension(namingOptions.createFilename(s)); songs.append(song); } } dlg->show(songs, QString(), true); } } #endif } } } void ActionDialog::actionStatus(int status, bool copiedCover) { int origStatus=status; bool wasSkip=false; if (Device::Ok!=status && Device::NotConnected!=status && autoSkip) { skippedSongs.append(currentSong); wasSkip=true; status=Device::Ok; } switch (status) { case Device::Ok: performingAction=false; if (Device::Ok==origStatus) { if (!wasSkip) { actionedSongs.append(currentSong); #ifdef ENABLE_REPLAYGAIN_SUPPORT if (Copy==mode && sourceIsAudioCd && !albumsWithoutRgTags.contains(currentSong.album) && Tags::readReplaygain(destFile).isEmpty()) { albumsWithoutRgTags.insert(currentSong.album); } #endif } if (copiedCover) { copiedCovers.insert(Utils::getDir(destFile)); } } if (!paused) { incProgress(); doNext(); } break; case Device::FileExists: setPage(PAGE_SKIP, formatSong(currentSong, true), tr("The destination filename already exists!")); break; case Device::SongExists: setPage(PAGE_SKIP, formatSong(currentSong), tr("Song already exists!")); break; case Device::SongDoesNotExist: setPage(PAGE_SKIP, formatSong(currentSong), tr("Song does not exist!")); break; case Device::DirCreationFaild: setPage(PAGE_SKIP, formatSong(currentSong, true), tr("Failed to create destination folder!
    Please check you have sufficient permissions.")); break; case Device::SourceFileDoesNotExist: setPage(PAGE_SKIP, formatSong(currentSong, true), tr("Source file no longer exists?")); break; case Device::Failed: setPage(PAGE_SKIP, formatSong(currentSong), Copy==mode || Sync==mode ? tr("Failed to copy.") : tr("Failed to delete.")); break; case Device::NotConnected: setPage(PAGE_ERROR, formatSong(currentSong), tr("Not connected to device.")); break; case Device::CodecNotAvailable: setPage(PAGE_ERROR, formatSong(currentSong), tr("Selected codec is not available.")); break; case Device::TranscodeFailed: setPage(PAGE_SKIP, formatSong(currentSong), tr("Transcoding failed.")); break; case Device::FailedToCreateTempFile: setPage(PAGE_ERROR, formatSong(currentSong), tr("Failed to create temporary file.
    (Required for transcoding to MTP devices.)")); break; case Device::ReadFailed: setPage(PAGE_SKIP, formatSong(currentSong), tr("Failed to read source file.")); break; case Device::WriteFailed: setPage(PAGE_SKIP, formatSong(currentSong), tr("Failed to write to destination file.")); break; case Device::NoSpace: setPage(PAGE_SKIP, formatSong(currentSong), tr("No space left on device.")); break; case Device::FailedToUpdateTags: setPage(PAGE_SKIP, formatSong(currentSong), tr("Failed to update metadata.")); break; case Device::DownloadFailed: setPage(PAGE_SKIP, formatSong(currentSong), tr("Failed to download track.")); break; case Device::FailedToLockDevice: setPage(PAGE_ERROR, formatSong(currentSong), tr("Failed to lock device.")); break; case Device::Cancelled: break; default: break; } } void ActionDialog::configureDest() { configureDestLabel->setVisible(false); configure(destUdi); } void ActionDialog::configureSource() { configureSourceLabel->setVisible(false); configure(sourceUdi); } void ActionDialog::configure(const QString &udi) { if (udi.isEmpty()) { DevicePropertiesDialog *dlg=new DevicePropertiesDialog(this); connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &))); if (!mpdConfigured) { connect(dlg, SIGNAL(cancelled()), SLOT(saveProperties())); } dlg->setCaption(tr("Local Music Library Properties")); dlg->show(MPDConnection::self()->getDetails().dir, namingOptions, DevicePropertiesWidget::Prop_Basic|DevicePropertiesWidget::Prop_FileName|(sourceIsAudioCd ? DevicePropertiesWidget::Prop_Encoder : 0)); connect(dlg, SIGNAL(destroyed()), SLOT(controlInfoLabel())); } else { Device *dev=DevicesModel::self()->device(udi); if (dev) { dev->configure(this); connect(dev, SIGNAL(configurationChanged()), SLOT(controlInfoLabel())); connect(dev, SIGNAL(renamed()), SLOT(deviceRenamed())); } } } void ActionDialog::saveProperties(const QString &path, const DeviceOptions &opts) { Q_UNUSED(path) namingOptions=opts; namingOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true, sourceIsAudioCd); mpdConfigured=true; } void ActionDialog::saveProperties() { namingOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true, sourceIsAudioCd); mpdConfigured=true; } void ActionDialog::setPage(int page, const StringPairList &msg, const QString &header) { stack->setCurrentIndex(page); switch(page) { case PAGE_SIZE_CALC: fileSizeActionLabel->startAnimation(); setButtons(Cancel); break; case PAGE_INSUFFICIENT_SIZE: fileSizeActionLabel->stopAnimation(); setButtons(Ok|Cancel); break; case PAGE_START: fileSizeActionLabel->stopAnimation(); setButtons(Ok|Cancel); if (Sync==mode) { slotButtonClicked(Ok); } break; case PAGE_PROGRESS: actionLabel->startAnimation(); setButtons(Cancel); break; case PAGE_SKIP: actionLabel->stopAnimation(); skipText->setText(msg, QLatin1String("")+tr("Error")+QLatin1String("
    ")+header+ (header.isEmpty() ? QString() : QLatin1String("

    "))); if (songsToAction.count()) { setButtons(Cancel|User1|User2|User3); setButtonText(User1, tr("Skip")); setButtonText(User2, tr("Auto Skip")); } else { setButtons(Cancel|User3); } setButtonText(User3, tr("Retry")); break; case PAGE_ERROR: actionLabel->stopAnimation(); stack->setCurrentIndex(PAGE_ERROR); errorText->setText(msg, QLatin1String("")+tr("Error")+QLatin1String("
    ")+header+ (header.isEmpty() ? QString() : QLatin1String("

    "))); setButtons(Cancel); break; } } ActionDialog::StringPairList ActionDialog::formatSong(const Song &s, bool showFiles) { StringPairList str; str.append(StringPair(tr("Artist:"), s.albumArtist())); str.append(StringPair(tr("Album:"), s.album)); str.append(StringPair(tr("Track:"), s.trackAndTitleStr())); if (showFiles) { if (Copy==mode || Sync==mode) { str.append(StringPair(tr("Source file:"), DevicesModel::fixDevicePath(s.filePath()))); str.append(StringPair(tr("Destination file:"), DevicesModel::fixDevicePath(destFile))); } else { str.append(StringPair(tr("File:"), DevicesModel::fixDevicePath(s.filePath()))); } } return str; } bool ActionDialog::refreshLibrary() { actionLabel->stopAnimation(); if (!actionedSongs.isEmpty()) { if (Sync==mode) { emit update(); Device *dev=DevicesModel::self()->device(sourceUdi.isEmpty() ? destUdi : sourceUdi); if (dev) { connect(dev, SIGNAL(cacheSaved()), this, SLOT(cacheSaved())); dev->saveCache(); progressLabel->setText(tr("Saving cache")); setButtons(Close); return true; } } else if ( (Copy==mode && !sourceUdi.isEmpty()) || (Remove==mode && sourceUdi.isEmpty()) ) { // MusicLibraryModel::self()->checkForNewSongs(); // AlbumsModel::self()->update(MusicLibraryModel::self()->root()); emit update(); } else if ( (Copy==mode && sourceUdi.isEmpty()) || (Remove==mode && !sourceUdi.isEmpty()) ) { Device *dev=DevicesModel::self()->device(sourceUdi.isEmpty() ? destUdi : sourceUdi); if (dev) { connect(dev, SIGNAL(cacheSaved()), this, SLOT(cacheSaved())); dev->saveCache(); progressLabel->setText(tr("Saving cache")); setButtons(Close); return true; } } } return false; } void ActionDialog::removeSong(const Song &s) { if (!QFile::exists(s.file)) { actionStatus(Device::SourceFileDoesNotExist); return; } DeleteJob *job=new DeleteJob(s.file, true); connect(job, SIGNAL(result(int)), SLOT(removeSongResult(int))); job->start(); } void ActionDialog::removeSongResult(int status) { FileJob::finished(sender()); if (Device::Ok!=status) { actionStatus(status); } else { // MusicLibraryModel::self()->removeSongFromList(currentSong); // DirViewModel::self()->removeFileFromList(currentSong.file); actionStatus(Device::Ok); } } void ActionDialog::cleanDirs() { CleanJob *job=new CleanJob(dirsToClean, MPDConnection::self()->getDetails().dir, QString()); connect(job, SIGNAL(result(int)), SLOT(cleanDirsResult(int))); connect(job, SIGNAL(percent(int)), SLOT(jobPercent(int))); job->start(); } void ActionDialog::cleanDirsResult(int status) { FileJob::finished(sender()); actionStatus(status); } void ActionDialog::jobPercent(int percent) { if (percent!=currentPercent) { progressBar->setValue((100*count)+percent); updateUnity(false); currentPercent=percent; if (PAGE_PROGRESS==stack->currentIndex()) { progressLabel->setText(formatSong(currentSong, false)); } } } void ActionDialog::incProgress() { count++; progressBar->setValue(100*count); updateUnity(false); } void ActionDialog::updateUnity(bool finished) { #ifdef QT_QTDBUS_FOUND QList args; double progress = finished || progressBar->maximum()<1 ? 0.0 : (progressBar->value()/(progressBar->maximum()*1.0)); bool showProgress = progress>-0.1 && progress < 100.0 && !finished; QMap props; props["count-visible"]=!finished && songsToAction.count()>0; props["count"]=(long long)songsToAction.count(); props["progress-visible"]=showProgress; props["progress"]=showProgress ? progress : 0.0; args.append("application://cantata.desktop"); args.append(props); unityMessage.setArguments(args); QDBusConnection::sessionBus().send(unityMessage); #else Q_UNUSED(finished) #endif } void ActionDialog::cacheSaved() { emit completed(); accept(); } cantata-2.2.0/devices/actiondialog.h000066400000000000000000000075151316350454000173670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ACTION_DIALOG_H #define ACTION_DIALOG_H #include "support/dialog.h" #include "mpd-interface/song.h" #include "device.h" #include "config.h" #include "ui_actiondialog.h" #include #ifdef QT_QTDBUS_FOUND #include #endif #include #include class SongListDialog; class ActionDialog : public Dialog, Ui::ActionDialog { Q_OBJECT enum Mode { Copy, Remove, Sync }; typedef QPair StringPair; typedef QList StringPairList; public: static int instanceCount(); ActionDialog(QWidget *parent); virtual ~ActionDialog(); void sync(const QString &devId, const QList &libSongs, const QList &devSongs); void copy(const QString &srcUdi, const QString &dstUdi, const QList &songs); void remove(const QString &udi, const QList &songs); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void update(); void completed(); private Q_SLOTS: void calcFileSize(); void configureSource(); void configureDest(); void saveProperties(const QString &path, const DeviceOptions &opts); void saveProperties(); void actionStatus(int status, bool copiedCover=false); void doNext(); void removeSongResult(int status); void cleanDirsResult(int status); void jobPercent(int percent); void cacheSaved(); void controlInfoLabel(); void deviceRenamed(); private: void updateSongCountLabel(); void controlInfoLabel(Device *dev); Device * getDevice(const QString &udi, bool logErrors=true); void configure(const QString &udi); void init(const QString &srcUdi, const QString &dstUdi, const QList &songs, Mode m); void slotButtonClicked(int button); void setPage(int page, const StringPairList &msg=StringPairList(), const QString &header=QString()); StringPairList formatSong(const Song &s, bool showFiles=false); bool refreshLibrary(); void removeSong(const Song &s); void cleanDirs(); void incProgress(); void updateUnity(bool finished); private: Mode mode; qint64 spaceRequired; bool sourceIsAudioCd; QString sourceUdi; QString destUdi; QList songsToCalcSize; QList songsToAction; QList skippedSongs; QList actionedSongs; QList syncSongs; QSet dirsToClean; QSet copiedCovers; unsigned long count; int currentPercent; // Percentage of current song Song origCurrentSong; Song currentSong; bool autoSkip; bool paused; bool performingAction; bool haveVariousArtists; bool mpdConfigured; Device *currentDev; QString destFile; DeviceOptions namingOptions; #ifdef ENABLE_REPLAYGAIN_SUPPORT QSet albumsWithoutRgTags; #endif #ifdef QT_QTDBUS_FOUND QDBusMessage unityMessage; #endif SongListDialog *songDialog; friend class SongListDialog; }; #endif cantata-2.2.0/devices/actiondialog.ui000066400000000000000000000364731316350454000175620ustar00rootroot00000000000000 ActionDialog 0 0 580 186 0 0 0 0 0 0 0 0 0 0 0 64 64 64 64 Qt::Horizontal QSizePolicy::Fixed 16 20 Calculating size of files to be copied, please wait... 24 0 0 0 0 0 0 64 64 64 64 Qt::Horizontal QSizePolicy::Fixed 16 20 true QFormLayout::AllNonFixedFieldsGrow 0 0 0 0 Copy songs from: Configure true true (Needs configuring) Qt::Horizontal 40 20 Copy songs to: Configure true true (Needs configuring) Qt::Horizontal 40 20 0 0 Destination format: Overwrite songs To copy: 0 0 0 0 0 0 0 0 64 64 64 64 Qt::Horizontal QSizePolicy::Fixed 16 20 0 0 0 0 0 0 64 64 64 64 Qt::Horizontal QSizePolicy::Fixed 16 20 0 0 0 0 0 0 64 64 64 64 Qt::Horizontal QSizePolicy::Fixed 16 20 24 CapacityBar QProgressBar
    support/capacitybar.h
    ActionLabel QLabel
    widgets/actionlabel.h
    UrlNoteLabel QLabel
    widgets/notelabel.h
    SplitLabelWidget QLabel
    devices/splitlabelwidget.h
    configureDestButton overwrite
    cantata-2.2.0/devices/albumdetails.ui000066400000000000000000000135441316350454000175650ustar00rootroot00000000000000 AlbumDetails 0 0 527 507 0 0 0 Album Details QFormLayout::ExpandingFieldsGrow Artist: artist Composer: composer Title: title Genre: genre Year: year Disc: disc Single artist 0 0 Tracks false false true false true Track Artist Title BuddyLabel QLabel
    support/buddylabel.h
    CompletionCombo QComboBox
    widgets/completioncombo.h
    EmnptySpinBox QSpinBox
    widgets/emptyspinbox.h
    artist composer title genre year disc singleArtist tracks
    cantata-2.2.0/devices/albumdetailsdialog.cpp000066400000000000000000000322011316350454000211010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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 detailexampleSong. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "albumdetailsdialog.h" #include "audiocddevice.h" #include "models/musiclibraryitemsong.h" #include "support/messagebox.h" #include "support/inputdialog.h" #include "models/devicesmodel.h" #include "models/mpdlibrarymodel.h" #include "cdalbum.h" #include "widgets/icons.h" #include "gui/coverdialog.h" #include "widgets/basicitemdelegate.h" #include "support/lineedit.h" #include #include #include #include enum Columns { COL_TRACK, COL_ARTIST, COL_TITLE }; class EditorDelegate : public BasicItemDelegate { public: EditorDelegate(QObject *parent=0) : BasicItemDelegate(parent) { } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option) if (COL_TRACK==index.column()) { QSpinBox *editor = new QSpinBox(parent); editor->setMinimum(0); editor->setMaximum(500); return editor; } else { parent->setProperty("cantata-delegate", true); return new LineEdit(parent); } } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QStyledItemDelegate::sizeHint(option, index)+QSize(0, 4); } void setEditorData(QWidget *editor, const QModelIndex &index) const { if (COL_TRACK==index.column()) { static_cast(editor)->setValue(index.model()->data(index, Qt::EditRole).toInt()); } else { static_cast(editor)->setText(index.model()->data(index, Qt::EditRole).toString()); } } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (COL_TRACK==index.column()) { model->setData(index, static_cast(editor)->value(), Qt::EditRole); } else { model->setData(index, static_cast(editor)->text().trimmed(), Qt::EditRole); } } void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index) editor->setGeometry(option.rect); } }; static int iCount=0; int AlbumDetailsDialog::instanceCount() { return iCount; } AlbumDetailsDialog::AlbumDetailsDialog(QWidget *parent) : Dialog(parent, "AlbumDetailsDialog") , pressed(false) { iCount++; setButtons(User1|Ok|Cancel); setCaption(tr("Audio CD")); setAttribute(Qt::WA_DeleteOnClose); QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); QSet artists; QSet albumArtists; QSet composers; QSet albums; QSet genres; MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres); QStringList strings=albumArtists.toList(); strings.sort(); artist->clear(); artist->insertItems(0, strings); strings=composers.toList(); strings.sort(); composer->clear(); composer->insertItems(0, strings); strings=albums.toList(); strings.sort(); title->clear(); title->insertItems(0, strings); strings=genres.toList(); strings.sort(); genre->clear(); genre->insertItems(0, strings); QMenu *toolsMenu=new QMenu(this); toolsMenu->addAction(tr("Apply \"Various Artists\" Workaround"), this, SLOT(applyVa())); toolsMenu->addAction(tr("Revert \"Various Artists\" Workaround"), this, SLOT(revertVa())); toolsMenu->addAction(tr("Capitalize"), this, SLOT(capitalise())); toolsMenu->addAction(tr("Adjust Track Numbers"), this, SLOT(adjustTrackNumbers())); setButtonMenu(User1, toolsMenu, InstantPopup); setButtonGuiItem(User1, GuiItem(tr("Tools"), "tools-wizard")); connect(singleArtist, SIGNAL(toggled(bool)), SLOT(hideArtistColumn(bool))); resize(600, 600); int size=fontMetrics().height()*5; cover->setMinimumSize(size, size); cover->setMaximumSize(size, size); setCover(); cover->installEventFilter(this); tracks->setItemDelegate(new EditorDelegate(tracks)); } AlbumDetailsDialog::~AlbumDetailsDialog() { iCount--; } void AlbumDetailsDialog::show(AudioCdDevice *dev) { udi=dev->id(); artist->setText(dev->albumArtist()); composer->setText(dev->albumComposer()); title->setText(dev->albumName()); genre->setText(dev->albumGenre()); disc->setValue(dev->albumDisc()); year->setValue(dev->albumYear()); tracks->clear(); QSet artists; artists.insert(dev->albumArtist()); QList songs; foreach (const MusicLibraryItem *i, dev->childItems()) { songs.append(static_cast(i)->song()); } qSort(songs); foreach (const Song &s, songs) { QTreeWidgetItem *item=new QTreeWidgetItem(tracks); update(item, s); artists.insert(s.artist); item->setFlags(item->flags()|Qt::ItemIsEditable); } singleArtist->setChecked(1==artists.count()); coverImage=dev->cover(); if (!coverImage.img.isNull()) { cover->setPixmap(QPixmap::fromImage(coverImage.img.scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); } Dialog::show(); } void AlbumDetailsDialog::slotButtonClicked(int button) { switch (button) { case Ok: { Device *dev=DevicesModel::self()->device(udi); if (dev && Device::AudioCd==dev->devType()) { CdAlbum cdAlbum=getAlbum(); for(int i=0; itopLevelItemCount(); ++i) { cdAlbum.tracks.append(toSong(tracks->topLevelItem(i), cdAlbum)); } static_cast(dev)->setDetails(cdAlbum); static_cast(dev)->setCover(coverImage); } accept(); break; } case Cancel: reject(); break; default: break; } if (Ok==button) { accept(); } Dialog::slotButtonClicked(button); } void AlbumDetailsDialog::hideArtistColumn(bool hide) { tracks->header()->setSectionHidden(COL_ARTIST, hide); } void AlbumDetailsDialog::applyVa() { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Apply \"Various Artists\" workaround?")+ QLatin1String("\n\n")+ tr("This will set 'Album artist' and 'Artist' to " "\"Various Artists\", and set 'Title' to " "\"TrackArtist - TrackTitle\""), tr("Apply \"Various Artists\" Workaround"), StdGuiItem::apply(), StdGuiItem::cancel())) { return; } CdAlbum album=getAlbum(); for(int i=0; itopLevelItemCount(); ++i) { QTreeWidgetItem *itm=tracks->topLevelItem(i); Song s=toSong(itm, album); if (s.fixVariousArtists()) { update(itm, s); } } } void AlbumDetailsDialog::revertVa() { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Revert \"Various Artists\" workaround?")+ QLatin1String("\n\n")+ tr("Where the 'Album artist' is the same as 'Artist' " "and the 'Title' is of the format \"TrackArtist - TrackTitle\", " "'Artist' will be taken from 'Title' and 'Title' itself will be " "set to just the title. e.g. \n" "If 'Title' is \"Wibble - Wobble\", then 'Artist' will be set to " "\"Wibble\" and 'Title' will be set to \"Wobble\""), tr("Revert \"Various Artists\" Workaround"), GuiItem(tr("Revert")), StdGuiItem::cancel())) { return; } CdAlbum album=getAlbum(); for(int i=0; itopLevelItemCount(); ++i) { QTreeWidgetItem *itm=tracks->topLevelItem(i); Song s=toSong(itm, album); if (s.revertVariousArtists()) { update(itm, s); } } } void AlbumDetailsDialog::capitalise() { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'?"), tr("Capitalize"), GuiItem(tr("Capitalize")), StdGuiItem::cancel())) { return; } CdAlbum album=getAlbum(); for(int i=0; itopLevelItemCount(); ++i) { QTreeWidgetItem *itm=tracks->topLevelItem(i); Song s=toSong(itm, album); if (s.capitalise()) { update(itm, s); } } } void AlbumDetailsDialog::adjustTrackNumbers() { bool ok=false; int adj=InputDialog::getInteger(tr("Adjust Track Numbers"), tr("Adjust track number by:"), 0, -500, 500, 1, 10, &ok, this); if (!ok || 0==adj) { return; } CdAlbum album=getAlbum(); for(int i=0; itopLevelItemCount(); ++i) { QTreeWidgetItem *itm=tracks->topLevelItem(i); Song s=toSong(itm, album); s.track+=adj; update(itm, s); } } enum Roles { Role_Id = Qt::UserRole, Role_File, Role_Time }; Song AlbumDetailsDialog::toSong(QTreeWidgetItem *i, const CdAlbum &album) { Song s; s.albumartist=album.artist; s.album=album.name; s.genres[0]=album.genre; s.year=album.year; s.disc=album.disc; s.artist=singleArtist->isChecked() ? s.albumartist : i->text(COL_ARTIST); if (!album.composer.isEmpty()) { s.setComposer(album.composer); } s.title=i->text(COL_TITLE); s.track=i->text(COL_TRACK).toInt(); s.id=i->data(0, Role_Id).toInt(); s.file=i->data(0, Role_File).toString(); s.time=i->data(0, Role_Time).toInt(); s.fillEmptyFields(); return s; } CdAlbum AlbumDetailsDialog::getAlbum() const { CdAlbum cdAlbum; cdAlbum.artist=artist->text().trimmed(); cdAlbum.composer=composer->text().trimmed(); cdAlbum.name=title->text().trimmed(); cdAlbum.disc=disc->value(); cdAlbum.year=year->value(); cdAlbum.genre=genre->text().trimmed(); if (cdAlbum.artist.isEmpty()) { cdAlbum.artist=Song::unknown(); } if (cdAlbum.name.isEmpty()) { cdAlbum.name=Song::unknown(); } return cdAlbum; } void AlbumDetailsDialog::update(QTreeWidgetItem *i, const Song &s) { i->setText(COL_TRACK, QString::number(s.track)); i->setText(COL_ARTIST, s.artist); i->setText(COL_TITLE, s.title); i->setData(0, Role_Id, s.id); i->setData(0, Role_File, s.file); i->setData(0, Role_Time, s.time); } void AlbumDetailsDialog::setCover() { int iconSize=cover->size().width()<=128 ? 128 : 256; cover->setPixmap(Icons::self()->albumIcon(iconSize).pixmap(iconSize, iconSize).scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } bool AlbumDetailsDialog::eventFilter(QObject *object, QEvent *event) { switch(event->type()) { case QEvent::MouseButtonPress: if (Qt::LeftButton==static_cast(event)->button()) { pressed=true; } break; case QEvent::MouseButtonRelease: if (pressed && Qt::LeftButton==static_cast(event)->button()) { if (0==CoverDialog::instanceCount()) { CoverDialog *dlg=new CoverDialog(this); connect(dlg, SIGNAL(selectedCover(QImage, QString)), this, SLOT(coverSelected(QImage, QString))); Song s; s.file=AudioCdDevice::coverUrl(udi); s.artist=artist->text().trimmed(); s.album=title->text().trimmed(); s.type=Song::Cdda; dlg->show(s, coverImage); } } pressed=false; break; default: break; } return QObject::eventFilter(object, event); } void AlbumDetailsDialog::coverSelected(const QImage &img, const QString &fileName) { coverImage=Covers::Image(img, fileName); if (!coverImage.img.isNull()) { cover->setPixmap(QPixmap::fromImage(coverImage.img.scaled(cover->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); } } cantata-2.2.0/devices/albumdetailsdialog.h000066400000000000000000000035331316350454000205540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALBUMDETAILSDIALOG_H #define ALBUMDETAILSDIALOG_H #include "ui_albumdetails.h" #include "support/dialog.h" #include "mpd-interface/song.h" #include "gui/covers.h" class AudioCdDevice; class QTreeWidgetItem; struct CdAlbum; class AlbumDetailsDialog : public Dialog, Ui::AlbumDetails { Q_OBJECT public: static int instanceCount(); AlbumDetailsDialog(QWidget *parent); virtual ~AlbumDetailsDialog(); void show(AudioCdDevice *dev); private Q_SLOTS: void hideArtistColumn(bool hide); void applyVa(); void revertVa(); void capitalise(); void adjustTrackNumbers(); void coverSelected(const QImage &img, const QString &fileName); private: void slotButtonClicked(int button); Song toSong(QTreeWidgetItem *i, const CdAlbum &album); void update(QTreeWidgetItem *i, const Song &s); void setCover(); bool eventFilter(QObject *object, QEvent *event); CdAlbum getAlbum() const; private: QString udi; bool pressed; Covers::Image coverImage; }; #endif cantata-2.2.0/devices/audiocddevice.cpp000066400000000000000000000307471316350454000200600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "audiocddevice.h" #ifdef CDDB_FOUND #include "cddbinterface.h" #endif #ifdef MUSICBRAINZ5_FOUND #include "musicbrainz.h" #endif #include "models/musiclibraryitemsong.h" #include "models/mpdlibrarymodel.h" #include "models/playqueuemodel.h" #include "support/utils.h" #include "extractjob.h" #include "mpd-interface/mpdconnection.h" #include "gui/covers.h" #include "gui/settings.h" #include #include #include #include "solid-lite/block.h" const QLatin1String AudioCdDevice::constAnyDev("-"); QString AudioCdDevice::coverUrl(QString udi) { udi.replace(" ", "_"); udi.replace("\n", "_"); udi.replace("\t", "_"); udi.replace("/", "_"); udi.replace(":", "_"); return Song::constCddaProtocol+udi; } QString AudioCdDevice::getDevice(const QUrl &url) { if (QLatin1String("cdda")==url.scheme()) { QUrlQuery q(url); if (q.hasQueryItem("dev")) { return q.queryItemValue("dev"); } return constAnyDev; } QString path=url.path(); if (path.startsWith("/run/user/")) { const QString marker=QLatin1String("/gvfs/cdda:host="); int pos=path.lastIndexOf(marker); if (-1!=pos) { return QLatin1String("/dev/")+path.mid(pos+marker.length()); } } return QString(); } AudioCdDevice::AudioCdDevice(MusicLibraryModel *m, Solid::Device &dev) : Device(m, dev, false, true) #ifdef CDDB_FOUND , cddb(0) #endif #ifdef MUSICBRAINZ5_FOUND , mb(0) #endif , year(0) , disc(0) , time(0xFFFFFFFF) , lookupInProcess(false) , autoPlay(false) { icn=Icon("media-optical"); drive=dev.parent().as(); Solid::Block *block=dev.as(); if (block) { device=block->device(); } else { // With UDisks2 we cannot get block from device :-( QStringList parts=dev.udi().split("/", QString::SkipEmptyParts); if (!parts.isEmpty()) { parts=parts.last().split(":"); if (!parts.isEmpty()) { device="/dev/"+parts.first(); } } } if (!device.isEmpty()) { static bool registeredTypes=false; if (!registeredTypes) { qRegisterMetaType("CdAlbum"); qRegisterMetaType >("QList"); registeredTypes=true; } devPath=Song::constCddaProtocol+device+QChar('/'); #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND connectService(Settings::self()->useCddb()); #else connectService(true); #endif detailsString=tr("Reading disc"); setStatusMessage(detailsString); lookupInProcess=true; connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(setCover(const Song &, const QImage &, const QString &))); emit lookup(Settings::self()->cdAuto()); } } AudioCdDevice::~AudioCdDevice() { #ifdef CDDB_FOUND if (cddb) { cddb->deleteLater(); cddb=0; } #endif #ifdef MUSICBRAINZ5_FOUND if (mb) { mb->deleteLater(); mb=0; } #endif // Remove any downloaded cover image... if (!coverImage.fileName.isEmpty() && coverImage.fileName.startsWith(Utils::cacheDir(Covers::constCddaCoverDir, false))) { QFile::remove(coverImage.fileName); } } void AudioCdDevice::dequeue() { QList tracks; foreach (const MusicLibraryItem *item, childItems()) { if (MusicLibraryItem::Type_Song==item->itemType()) { Song song=static_cast(item)->song(); song.file=path()+song.file; tracks.append(song); } } if (!tracks.isEmpty()) { PlayQueueModel::self()->remove(tracks); } } bool AudioCdDevice::isAudioDevice(const QString &dev) const { return constAnyDev==dev || device==dev; } void AudioCdDevice::connectService(bool useCddb) { #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND if (cddb && !useCddb) { cddb->deleteLater(); cddb=0; } if (mb && useCddb) { mb->deleteLater(); mb=0; } #else Q_UNUSED(useCddb) #endif #ifdef CDDB_FOUND if (!cddb #ifdef MUSICBRAINZ5_FOUND && useCddb #endif ) { cddb=new CddbInterface(device); connect(cddb, SIGNAL(error(QString)), this, SIGNAL(error(QString))); connect(cddb, SIGNAL(initialDetails(CdAlbum)), this, SLOT(setDetails(CdAlbum))); connect(cddb, SIGNAL(matches(const QList &)), SLOT(cdMatches(const QList &))); connect(this, SIGNAL(lookup(bool)), cddb, SLOT(lookup(bool))); } #endif #ifdef MUSICBRAINZ5_FOUND if (!mb #ifdef CDDB_FOUND && !useCddb #endif ) { mb=new MusicBrainz(device); connect(mb, SIGNAL(error(QString)), this, SIGNAL(error(QString))); connect(mb, SIGNAL(initialDetails(CdAlbum)), this, SLOT(setDetails(CdAlbum))); connect(mb, SIGNAL(matches(const QList &)), SLOT(cdMatches(const QList &))); connect(this, SIGNAL(lookup(bool)), mb, SLOT(lookup(bool))); } #endif } void AudioCdDevice::rescan(bool useCddb) { if (!device.isEmpty()) { connectService(useCddb); lookupInProcess=true; emit lookup(true); } } void AudioCdDevice::toggle() { if (drive) { stop(); drive->eject(); PlayQueueModel::self()->removeCantataStreams(true); } } void AudioCdDevice::stop() { } void AudioCdDevice::copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover) { jobAbortRequested=false; if (!isConnected()) { emit actionStatus(NotConnected); return; } needToFixVa=opts.fixVariousArtists && s.isVariousArtists(); if (!overwrite) { Song check=s; if (needToFixVa) { Device::fixVariousArtists(QString(), check, false); } if (MpdLibraryModel::self()->songExists(check)) { emit actionStatus(SongExists); return; } } DeviceOptions mpdOpts; mpdOpts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); Encoders::Encoder encoder=Encoders::getEncoder(mpdOpts.transcoderCodec); if (encoder.codec.isEmpty()) { emit actionStatus(CodecNotAvailable); return; } QString source=device; QString baseDir=MPDConnection::self()->getDetails().dir; currentDestFile=encoder.changeExtension(baseDir+musicPath); QDir dir(Utils::getDir(currentDestFile)); if (!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), baseDir)) { emit actionStatus(DirCreationFaild); return; } currentSong=s; ExtractJob *job=new ExtractJob(encoder, mpdOpts.transcoderValue, source, currentDestFile, currentSong, copyCover ? coverImage.fileName : QString()); connect(job, SIGNAL(result(int)), SLOT(copySongToResult(int))); connect(job, SIGNAL(percent(int)), SLOT(percent(int))); job->start(); } quint32 AudioCdDevice::totalTime() { if (0xFFFFFFFF==time) { time=0; foreach (MusicLibraryItem *i, childItems()) { time+=static_cast(i)->song().time; } } return time; } void AudioCdDevice::percent(int pc) { if (jobAbortRequested && 100!=pc) { FileJob *job=qobject_cast(sender()); if (job) { job->stop(); } return; } emit progress(pc); } void AudioCdDevice::copySongToResult(int status) { ExtractJob *job=qobject_cast(sender()); FileJob::finished(job); if (jobAbortRequested) { if (job && job->wasStarted() && QFile::exists(currentDestFile)) { QFile::remove(currentDestFile); } return; } if (Ok!=status) { emit actionStatus(status); } else { currentSong.file=currentDestFile.mid(MPDConnection::self()->getDetails().dir.length()); QString origPath; if (MPDConnection::self()->isMopidy()) { origPath=currentSong.file; currentSong.file=Song::encodePath(currentSong.file); } if (needToFixVa) { currentSong.revertVariousArtists(); } Utils::setFilePerms(currentDestFile); // MusicLibraryModel::self()->addSongToList(currentSong); // DirViewModel::self()->addFileToList(origPath.isEmpty() ? currentSong.file : origPath, // origPath.isEmpty() ? QString() : currentSong.file); emit actionStatus(Ok, job && job->coverCopied()); } } static const int constBytesPerSecond=44100*4; void AudioCdDevice::setDetails(const CdAlbum &a) { bool differentAlbum=album!=a.name || artist!=a.artist; lookupInProcess=false; setData(a.artist); album=a.name; artist=a.artist; composer=a.composer; genre=a.genre; year=a.year; disc=a.disc; update=new MusicLibraryItemRoot(); int totalDuration=0; foreach (Song s, a.tracks) { totalDuration+=s.time; s.size=s.time*constBytesPerSecond; update->append(new MusicLibraryItemSong(s, update)); } setStatusMessage(QString()); detailsString=tr("%n Tracks (%1)", "", a.tracks.count()).arg(Utils::formatTime(totalDuration)); emit updating(id(), false); if (differentAlbum && !a.isDefault) { Song s; s.artist=s.albumartist=artist; s.album=album; s.file=AudioCdDevice::coverUrl(id()); s.title=id(); s.type=Song::Cdda; Covers::Image img=Covers::self()->requestImage(s, true); if (!img.img.isNull()) { setCover(img); } } if (autoPlay) { autoPlay=false; playTracks(); } else { updateDetails(); } } void AudioCdDevice::cdMatches(const QList &albums) { lookupInProcess=false; if (1==albums.count()) { setDetails(albums.at(0)); } else if (albums.count()>1) { // More than 1 match, so prompt user! emit matches(id(), albums); } } void AudioCdDevice::setCover(const Covers::Image &img) { coverImage=img; updateStatus(); } void AudioCdDevice::scaleCoverPix(int size) const { if (!coverImage.img.isNull()) { if (scaledCover.width()!=size && scaledCover.height()!=size) { scaledCover=QPixmap::fromImage(coverImage.img.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } } } void AudioCdDevice::setCover(const Song &song, const QImage &img, const QString &file) { if (song.isCdda() && song.albumartist==artist && song.album==album) { setCover(Covers::Image(img, file)); } } void AudioCdDevice::autoplay() { if (childCount()) { playTracks(); } else { autoPlay=true; } } void AudioCdDevice::playTracks() { QList tracks; foreach (const MusicLibraryItem *item, childItems()) { if (MusicLibraryItem::Type_Song==item->itemType()) { Song song=static_cast(item)->song(); song.file=path()+song.file; tracks.append(song); } } if (!tracks.isEmpty()) { emit play(tracks); } } void AudioCdDevice::updateDetails() { QList tracks; foreach (const MusicLibraryItem *item, childItems()) { if (MusicLibraryItem::Type_Song==item->itemType()) { Song song=static_cast(item)->song(); song.file=path()+song.file; tracks.append(song); } } if (!tracks.isEmpty()) { emit updatedDetails(tracks); } } cantata-2.2.0/devices/audiocddevice.h000066400000000000000000000074631316350454000175240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AUDIOCDDEVICE_H #define AUDIOCDDEVICE_H #include "device.h" #include "gui/covers.h" #include "http/httpserver.h" #include "solid-lite/opticaldrive.h" #include class CddbInterface; class MusicBrainz; struct CdAlbum; class AudioCdDevice : public Device { Q_OBJECT public: enum Service { SrvNone, SrvCddb, SrvMusicBrainz }; static const QLatin1String constAnyDev; static QString coverUrl(QString id); static QString getDevice(const QUrl &url); AudioCdDevice(MusicLibraryModel *m, Solid::Device &dev); virtual ~AudioCdDevice(); void dequeue(); QImage image() const { return cover().img; } bool isAudioDevice(const QString &dev) const; bool supportsDisconnect() const { return 0!=drive; } bool isConnected() const { return !device.isEmpty(); } void rescan(bool useCddb); bool isRefreshing() const { return lookupInProcess; } void toggle(); void stop(); QString path() const { return devPath; } void addSong(const Song &, bool, bool) { } void copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover); void removeSong(const Song &) { } void cleanDirs(const QSet &) { } double usedCapacity() { return 1.0; } QString capacityString() { return detailsString; } qint64 freeSpace() { return 1.0; } DevType devType() const { return AudioCd; } void saveOptions() { } QString subText() { return album; } quint32 totalTime(); bool canPlaySongs() const { return HttpServer::self()->isAlive(); } QString albumName() const { return album; } QString albumArtist() const { return artist; } QString albumComposer() const { return composer; } QString albumGenre() const { return genre; } int albumDisc() const { return disc; } int albumYear() const { return year; } const Covers::Image & cover() const { return coverImage; } void setCover(const Covers::Image &img); const QPixmap & coverPix() const { return scaledCover; } void scaleCoverPix(int size) const; void autoplay(); Q_SIGNALS: void lookup(bool full); void matches(const QString &u, const QList &); public Q_SLOTS: void percent(int pc); void copySongToResult(int status); void setDetails(const CdAlbum &a); void cdMatches(const QList &albums); void setCover(const Song &song, const QImage &img, const QString &file); private: void connectService(bool useCddb); void playTracks(); void updateDetails(); private: Service srv; Solid::OpticalDrive *drive; #ifdef CDDB_FOUND CddbInterface *cddb; #endif #ifdef MUSICBRAINZ5_FOUND MusicBrainz *mb; #endif QString detailsString; QString album; QString artist; QString composer; QString genre; QString device; QString devPath; int year; int disc; quint32 time; bool lookupInProcess; Covers::Image coverImage; mutable QPixmap scaledCover; bool autoPlay; }; #endif cantata-2.2.0/devices/audiocdsettings.cpp000066400000000000000000000050561316350454000204540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "audiocdsettings.h" #include "gui/settings.h" #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; AudioCdSettings::AudioCdSettings(QWidget *p) : QWidget(p) { setupUi(this); #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND cdLookup->addItem(tr("CDDB"), true); cdLookup->addItem(tr("MusicBrainz"), false); #else REMOVE(cdLookup) REMOVE(cdLookupLabel) #endif #if !defined CDDB_FOUND REMOVE(cddbHost) REMOVE(cddbHostLabel) REMOVE(cddbPort) REMOVE(cddbPortLabel) #endif } void AudioCdSettings::load() { cdAuto->setChecked(Settings::self()->cdAuto()); #if defined CDDB_FOUND cddbHost->setText(Settings::self()->cddbHost()); cddbPort->setValue(Settings::self()->cddbPort()); #endif paranoiaFull->setChecked(Settings::self()->paranoiaFull()); paranoiaNeverSkip->setChecked(Settings::self()->paranoiaNeverSkip()); #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND for (int i=0; icount(); ++i) { if (cdLookup->itemData(i).toBool()==Settings::self()->useCddb()) { cdLookup->setCurrentIndex(i); break; } } #endif } void AudioCdSettings::save() { Settings::self()->saveCdAuto(cdAuto->isChecked()); #if defined CDDB_FOUND Settings::self()->saveCddbHost(cddbHost->text().trimmed()); Settings::self()->saveCddbPort(cddbPort->value()); #endif Settings::self()->saveParanoiaFull(paranoiaFull->isChecked()); Settings::self()->saveParanoiaNeverSkip(paranoiaNeverSkip->isChecked()); #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND Settings::self()->saveUseCddb(cdLookup->itemData(cdLookup->currentIndex()).toBool()); #endif } cantata-2.2.0/devices/audiocdsettings.h000066400000000000000000000021661316350454000201200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AUDIOCDSETTINGS_H #define AUDIOCDSETTINGS_H #include "ui_audiocdsettings.h" class AudioCdSettings : public QWidget, private Ui::AudioCdSettings { Q_OBJECT public: AudioCdSettings(QWidget *p); virtual ~AudioCdSettings() { } void load(); void save(); }; #endif cantata-2.2.0/devices/audiocdsettings.ui000066400000000000000000000075051316350454000203100ustar00rootroot00000000000000 AudioCdSettings 0 0 506 462 0 0 0 0 Album and Track Information Retrieval QFormLayout::ExpandingFieldsGrow Initially look up via: cdLookup CDDB Host: cddbHost CDDB Port: cddbPort 1 65535 Lookup information as soon as CD is inserted Audio Extraction Full paranoia mode (best quality) Never skip on read error Qt::Vertical 20 3 BuddyLabel QLabel
    support/buddylabel.h
    LineEdit QLineEdit
    support/lineedit.h
    cantata-2.2.0/devices/avahi/000077500000000000000000000000001316350454000156415ustar00rootroot00000000000000cantata-2.2.0/devices/avahi/CMakeLists.txt000066400000000000000000000007441316350454000204060ustar00rootroot00000000000000set(AVAHI_SRCS avahi.cpp avahiservice.cpp) SET(AVAHI_MOC_HDRS avahi.h avahiservice.h) qt5_add_dbus_interfaces(AVAHI_SRCS org.freedesktop.Avahi.Server.xml ) qt5_add_dbus_interfaces(AVAHI_SRCS org.freedesktop.Avahi.ServiceBrowser.xml) qt5_add_dbus_interfaces(AVAHI_SRCS org.freedesktop.Avahi.ServiceResolver.xml) QT5_WRAP_CPP( AVAHI_MOC_SRCS ${AVAHI_MOC_HDRS} ) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${QTINCLUDES}) add_library(avahi STATIC ${AVAHI_MOC_SRCS} ${AVAHI_SRCS}) cantata-2.2.0/devices/avahi/avahi.cpp000066400000000000000000000061431316350454000174410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "avahi.h" #include "avahiservice.h" #include "serverinterface.h" #include "servicebrowserinterface.h" #include #include #include #ifdef ENABLE_KDE_SUPPORT #include K_GLOBAL_STATIC(Avahi, instance) #endif static bool isLocalDomain(const QString &d) { return QLatin1String("local")==d.section('.', -1, -1).toLower(); } QString Avahi::domainToDNS(const QString &domain) { return isLocalDomain(domain) ? domain : QUrl::toAce(domain); } static const QLatin1String constServiceType("_smb._tcp"); Avahi * Avahi::self() { #ifdef ENABLE_KDE_SUPPORT return instance; #else static Avahi *instance=0; if(!instance) { instance=new Avahi; } return instance; #endif } Avahi::Avahi() { org::freedesktop::Avahi::Server server("org.freedesktop.Avahi", "/", QDBusConnection::systemBus()); QDBusReply reply=server.ServiceBrowserNew(-1, -1, constServiceType, domainToDNS(QString()), 0); if (reply.isValid()) { service=new OrgFreedesktopAvahiServiceBrowserInterface("org.freedesktop.Avahi", reply.value().path(), QDBusConnection::systemBus()); connect(service, SIGNAL(ItemNew(int,int,QString,QString,QString,uint)), SLOT(addService(int,int,QString,QString,QString,uint))); connect(service, SIGNAL(ItemRemove(int,int,QString,QString,QString,uint)), SLOT(removeService(int,int,QString,QString,QString,uint))); } } AvahiService * Avahi::getService(const QString &name) { return services.contains(name) ? services[name] : 0; } void Avahi::addService(int, int, const QString &name, const QString &type, const QString &domain, uint) { if (isLocalDomain(domain) && !services.contains(name)) { AvahiService *srv=new AvahiService(name, type, domain); services.insert(name, srv); connect(srv, SIGNAL(serviceResolved(QString)), this, SIGNAL(serviceAdded(QString))); } } void Avahi::removeService(int, int, const QString &name, const QString &, const QString &domain, uint) { if (isLocalDomain(domain) && services.contains(name)) { services[name]->deleteLater(); disconnect(services[name], SIGNAL(serviceResolved(QString)), this, SIGNAL(serviceAdded(QString))); services.remove(name); emit serviceRemoved(name); } } cantata-2.2.0/devices/avahi/avahi.h000066400000000000000000000032241316350454000171030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AVAHI_H #define AVAHI_H #include #include //class OrgFreedesktopAvahiDomainBrowserInterface; class OrgFreedesktopAvahiServiceBrowserInterface; class AvahiService; class Avahi : public QObject { Q_OBJECT public: static QString domainToDNS(const QString &domain); static Avahi *self(); Avahi(); AvahiService * getService(const QString &name); Q_SIGNALS: void serviceAdded(const QString &); void serviceRemoved(const QString &); private Q_SLOTS: void addService(int, int, const QString &name, const QString &type, const QString &domain, uint); void removeService(int, int, const QString &name, const QString &type, const QString &domain, uint); private: OrgFreedesktopAvahiServiceBrowserInterface *service; QMap services; }; #endif cantata-2.2.0/devices/avahi/avahiservice.cpp000066400000000000000000000060401316350454000210160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "avahi.h" #include "avahiservice.h" #include "serverinterface.h" #include "serviceresolverinterface.h" Q_DECLARE_METATYPE(QList) AvahiService::AvahiService(const QString &n, const QString &type, const QString &domain) : name(n) , port(0) { static bool registeredTypes=false; if (!registeredTypes) { qDBusRegisterMetaType >(); registeredTypes=true; } org::freedesktop::Avahi::Server server("org.freedesktop.Avahi", "/", QDBusConnection::systemBus()); QDBusReply reply=server.ServiceResolverNew(-1, -1, name, type, Avahi::domainToDNS(domain), -1, 8 /*AVAHI_LOOKUP_NO_ADDRESS|AVAHI_LOOKUP_NO_TXT*/); if (reply.isValid()) { resolver=new OrgFreedesktopAvahiServiceResolverInterface("org.freedesktop.Avahi", reply.value().path(), QDBusConnection::systemBus()); connect(resolver, SIGNAL(Found(int,int,const QString &,const QString &,const QString &,const QString &, int, const QString &,ushort,const QList&, uint)), this, SLOT(resolved(int,int,const QString &,const QString &,const QString &,const QString &, int, const QString &,ushort, const QList&, uint))); connect(resolver, SIGNAL(Failure(QString)), this, SLOT(error(QString))); } } AvahiService::~AvahiService() { stop(); } void AvahiService::resolved(int, int, const QString &name, const QString &, const QString &, const QString &h, int, const QString &, ushort p, const QList &, uint) { Q_UNUSED(name) port=p; host=h; stop(); emit serviceResolved(name); } void AvahiService::error(const QString &) { stop(); } void AvahiService::stop() { if (resolver) { resolver->Free(); resolver->deleteLater(); disconnect(resolver, SIGNAL(Found(int,int,const QString &,const QString &,const QString &,const QString &, int, const QString &,ushort,const QList&, uint)), this, SLOT(resolved(int,int,const QString &,const QString &,const QString &,const QString &, int, const QString &,ushort, const QList&, uint))); disconnect(resolver, SIGNAL(Failure(QString)), this, SLOT(error(QString))); resolver=0; } } cantata-2.2.0/devices/avahi/avahiservice.h000066400000000000000000000032441316350454000204660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AVAHISERVICE_H #define AVAHISERVICE_H #include class OrgFreedesktopAvahiServiceResolverInterface; class AvahiService : public QObject { Q_OBJECT public: AvahiService(const QString &n, const QString &type, const QString &domain); virtual ~AvahiService(); const QString & getHost() const { return host; } ushort getPort() const { return port; } private: void stop(); Q_SIGNALS: void serviceResolved(const QString &); private Q_SLOTS: void resolved(int, int, const QString &name, const QString &, const QString &domain, const QString &h, int, const QString &, ushort p, const QList &txt, uint); void error(const QString &msg); private: QString name; QString host; ushort port; OrgFreedesktopAvahiServiceResolverInterface *resolver; }; #endif cantata-2.2.0/devices/avahi/org.freedesktop.Avahi.Server.OLD.xml000066400000000000000000000170451316350454000244040ustar00rootroot00000000000000 cantata-2.2.0/devices/avahi/org.freedesktop.Avahi.Server.xml000066400000000000000000000170451316350454000237670ustar00rootroot00000000000000 cantata-2.2.0/devices/avahi/org.freedesktop.Avahi.ServiceBrowser.xml000066400000000000000000000033371316350454000254640ustar00rootroot00000000000000 cantata-2.2.0/devices/avahi/org.freedesktop.Avahi.ServiceResolver.OLD.xml000066400000000000000000000036661316350454000262640ustar00rootroot00000000000000 cantata-2.2.0/devices/avahi/org.freedesktop.Avahi.ServiceResolver.xml000066400000000000000000000036661316350454000256470ustar00rootroot00000000000000 cantata-2.2.0/devices/cdalbum.h000066400000000000000000000025021316350454000163300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CDALBUM_H #define CDALBUM_H #include #include #include "mpd-interface/song.h" struct CdAlbum { CdAlbum() : isDefault(false), year(0), disc(0) { } bool isNull() const { return 0==year && 0==disc && tracks.isEmpty() && name.isEmpty() && artist.isEmpty() && composer.isEmpty() && genre.isEmpty(); } bool isDefault; QString name; QString artist; QString composer; QString genre; int year; int disc; QList tracks; }; #endif cantata-2.2.0/devices/cddbinterface.cpp000066400000000000000000000245331316350454000200410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "cddbinterface.h" #include "gui/settings.h" #include "network/networkproxyfactory.h" #include "support/thread.h" #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #include #elif defined(__linux__) #include #endif static struct CddbInterfaceCleanup { ~CddbInterfaceCleanup() { libcddb_shutdown(); } } cleanup; QString CddbInterface::dataTrack() { return tr("Data Track"); } CddbInterface::CddbInterface(const QString &device) : dev(device) , disc(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } CddbInterface::~CddbInterface() { thread->stop(); if (disc) { cddb_disc_destroy(disc); } } static CdAlbum toAlbum(cddb_disc_t *disc, const CdAlbum &initial=CdAlbum()) { CdAlbum album; if (!disc) { return album; } album.name=QString::fromUtf8(cddb_disc_get_title(disc)); album.artist=QString::fromUtf8(cddb_disc_get_artist(disc)); album.genre=QString::fromUtf8(cddb_disc_get_genre(disc)); album.year=cddb_disc_get_year(disc); int numTracks=cddb_disc_get_track_count(disc); if (numTracks>0) { for (int t=0; t=initial.tracks.count()) { break; } else { track.time=initial.tracks.at(t).time; album.tracks.append(track); } } } // Ensure we always have same number of tracks... if (!initial.isNull() && album.tracks.count()= 0 && 0==ioctl(fd, CDIOREADTOCHEADER, &th)) { disc = cddb_disc_new(); if (disc) { te.address_format = CD_LBA_FORMAT; for (int i=th.starting_track; i<=th.ending_track; i++) { te.track = i; if (0==ioctl(fd, CDIOREADTOCENTRY, &te)) { cddb_track_t *track = cddb_track_new(); if (track) { cddb_track_set_frame_offset(track, te.entry.addr.lba + SECONDS_TO_FRAMES(2)); cddb_track_set_title(track, te.entry.control&0x04 ? dataTrack().toUtf8().constData() : tr("Track %1").arg(i).toUtf8().constData()); cddb_track_set_artist(track, unknown.constData()); cddb_disc_add_track(disc, track); } } } te.track = 0xAA; if (0==ioctl(fd, CDIOREADTOCENTRY, &te)) { cddb_disc_set_length(disc, (ntohl(te.entry.addr.lba)+SECONDS_TO_FRAMES(2))/SECONDS_TO_FRAMES(1)); } } } #elif defined(__linux__) struct cdrom_tochdr th; struct cdrom_tocentry te; int status = ioctl(fd, CDROM_DISC_STATUS, CDSL_CURRENT); if ((CDS_AUDIO==status || CDS_MIXED==status) && 0==ioctl(fd, CDROMREADTOCHDR, &th)) { disc = cddb_disc_new(); if (disc) { te.cdte_format = CDROM_LBA; for (int i=th.cdth_trk0; i<=th.cdth_trk1; i++) { te.cdte_track = i; if (0==ioctl(fd, CDROMREADTOCENTRY, &te)) { cddb_track_t *track = cddb_track_new(); if (track) { cddb_track_set_frame_offset(track, te.cdte_addr.lba + SECONDS_TO_FRAMES(2)); cddb_track_set_title(track, te.cdte_ctrl&CDROM_DATA_TRACK ? dataTrack().toUtf8().constData() : tr("Track %1").arg(i).toUtf8().constData()); cddb_track_set_artist(track, unknown.constData()); cddb_disc_add_track(disc, track); } } } te.cdte_track = CDROM_LEADOUT; if (0==ioctl(fd, CDROMREADTOCENTRY, &te)) { cddb_disc_set_length(disc, (te.cdte_addr.lba+SECONDS_TO_FRAMES(2))/SECONDS_TO_FRAMES(1)); } } } #endif if (disc) { cddb_disc_set_artist(disc, unknown.constData()); cddb_disc_set_title(disc, unknown.constData()); cddb_disc_set_genre(disc, unknown.constData()); cddb_disc_calc_discid(disc); } close(fd); initial=toAlbum(disc); initial.isDefault=true; emit initialDetails(initial); } class CddbConnection { public: CddbConnection(cddb_disc_t *d) : disc(0) { connection = cddb_new(); if (connection) { cddb_cache_disable(connection); cddb_set_server_name(connection, Settings::self()->cddbHost().toLatin1().constData()); cddb_set_server_port(connection, Settings::self()->cddbPort()); disc=cddb_disc_clone(d); QUrl url; url.setHost(Settings::self()->cddbHost()); url.setPort(Settings::self()->cddbPort()); QList proxies=NetworkProxyFactory::self()->queryProxy(QNetworkProxyQuery(url)); foreach (const QNetworkProxy &p, proxies) { if (QNetworkProxy::HttpProxy==p.type() && 0!=p.port()) { cddb_set_http_proxy_server_name(connection, p.hostName().toLatin1().constData()); cddb_set_http_proxy_server_port(connection, p.port()); cddb_http_proxy_enable(connection); break; } } } } ~CddbConnection() { if (disc) { cddb_disc_destroy(disc); } if (connection) { cddb_destroy(connection); } } operator bool() const { return 0!=connection; } int query() { return cddb_query(connection, disc); } int read() { return cddb_read(connection, disc); } const char * error() { return cddb_error_str(cddb_errno(connection)); } int trackCount() { return cddb_disc_get_track_count(disc); } int next() { return cddb_query_next(connection, disc); } CdAlbum toAlbum(const CdAlbum &initial) { return ::toAlbum(disc, initial); } private: cddb_conn_t *connection; cddb_disc_t *disc; }; void CddbInterface::lookup(bool full) { bool isInitial=!disc; if (!disc) { readDisc(); } if (!disc || !full) { // Errors already logged in readDisc return; } CddbConnection cddb(disc); if (!cddb) { emit error(tr("Failed to create CDDB connection")); return; } if (!checkConnection()) { emit error(tr("Failed to contact CDDB server, please check CDDB and network settings")); return; } if (cddb.query()<1) { if (!isInitial) { emit error(tr("No matches found in CDDB")); } return; } QList m; for (;;) { if (!cddb.read()) { emit error(tr("CDDB error: %1", cddb.error())); return; } int numTracks=cddb.trackCount(); if (numTracks<=0) { continue; } CdAlbum album=cddb.toAlbum(initial); if (!album.tracks.isEmpty()) { m.append(album); } if (!cddb.next()) { break; } } if (m.isEmpty()) { if (!isInitial) { emit error(tr("No matches found in CDDB")); } } else if (isInitial) { emit initialDetails(m.first()); } else { emit matches(m); } } bool CddbInterface::checkConnection() { QUdpSocket socket(this); socket.connectToHost(Settings::self()->cddbHost(), Settings::self()->cddbPort(), QIODevice::ReadOnly); bool ok=socket.waitForConnected(2000); socket.close(); return ok; } cantata-2.2.0/devices/cddbinterface.h000066400000000000000000000027741316350454000175110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CANTATA_CDDB_H #define CANTATA_CDDB_H #include #include #include "config.h" #include "cdalbum.h" class Thread; typedef struct cddb_disc_s cddb_disc_t; class CddbInterface : public QObject { Q_OBJECT public: static QString dataTrack(); CddbInterface(const QString &device); ~CddbInterface(); public Q_SLOTS: void lookup(bool full); Q_SIGNALS: void error(const QString &error); void initialDetails(const CdAlbum &); void matches(const QList &); private: void readDisc(); bool checkConnection(); private: Thread *thread; QString dev; cddb_disc_t *disc; CdAlbum initial; }; #endif cantata-2.2.0/devices/cddbselectiondialog.cpp000066400000000000000000000056061316350454000212460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "cddbselectiondialog.h" #include #include #include #include CddbSelectionDialog::CddbSelectionDialog(QWidget *parent) : Dialog(parent, "CddbSelectionDialog") { QWidget *wid = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(wid); combo=new QComboBox(wid); QLabel *label=new QLabel(tr("Multiple matches were found. " "Please choose the relevant one from below:"), wid); tracks = new QTreeWidget(wid); tracks->setAlternatingRowColors(true); tracks->setRootIsDecorated(false); tracks->setUniformRowHeights(true); tracks->setItemsExpandable(false); tracks->setAllColumnsShowFocus(true); tracks->setHeaderLabels(QStringList() << tr("Artist") << tr("Title")); tracks->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); label->setWordWrap(true); layout->addWidget(label); layout->addWidget(combo); layout->addWidget(tracks); setCaption(tr("Disc Selection")); setMainWidget(wid); setButtons(Ok); connect(combo, SIGNAL(currentIndexChanged(int)), SLOT(updateTracks())); } int CddbSelectionDialog::select(const QList &albums) { combo->clear(); albumDetails=albums; foreach (const CdAlbum &a, albums) { if (a.disc>0) { combo->addItem(tr("%1 - %2 Disc %3 (%4)", "artist - album Disc disc (year)").arg(a.artist).arg(a.name).arg(a.disc).arg(a.year)); } else { combo->addItem(tr("%1 - %2 (%3)", "artist - album (year)").arg(a.artist).arg(a.name).arg(a.year)); } } updateTracks(); exec(); return combo->currentIndex(); } void CddbSelectionDialog::updateTracks() { tracks->clear(); bool sameArtist=true; const CdAlbum &a=albumDetails.at(combo->currentIndex()); foreach (const Song &s, a.tracks) { new QTreeWidgetItem(tracks, QStringList() << s.artist << s.title); if (s.artist!=a.artist) { sameArtist=false; } } tracks->setColumnHidden(0, sameArtist); } cantata-2.2.0/devices/cddbselectiondialog.h000066400000000000000000000024431316350454000207070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CDDBSELECTIONDIALOG_H #define CDDBSELECTIONDIALOG_H #include "support/dialog.h" #include "cdalbum.h" #include class QComboBox; class QTreeWidget; class CddbSelectionDialog : public Dialog { Q_OBJECT public: CddbSelectionDialog(QWidget *parent); int select(const QList &albums); private Q_SLOTS: void updateTracks(); private: QComboBox *combo; QTreeWidget *tracks; QList albumDetails; }; #endif cantata-2.2.0/devices/cdparanoia.cpp000066400000000000000000000104131316350454000173550ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "cdparanoia.h" #include "config.h" #include #include #include static QSet lockedDevices; static QMutex mutex; CdParanoia::CdParanoia(const QString &device, bool full, bool noSkip, bool playback) : drive(0) , paranoia(0) , paranoiaMode(0) , neverSkip(noSkip) , maxRetries(20) { QMutexLocker locker(&mutex); if (!lockedDevices.contains(device)) { dev = device; if (init()) { lockedDevices.insert(device); } else { dev=QString(); } } if (!dev.isEmpty()) { setFullParanoiaMode(full); if (playback) { maxRetries=1; #if !defined CDIOPARANOIA_FOUND && defined CDPARANOIA_HAS_CACHEMODEL_SIZE paranoia_cachemodel_size(paranoia, 24); #endif } } } CdParanoia::~CdParanoia() { QMutexLocker locker(&mutex); free(); if (!dev.isEmpty()) { lockedDevices.remove(dev); } } void CdParanoia::setParanoiaMode(int mode) { // from cdrdao 1.1.7 paranoiaMode = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP; switch (mode) { case 0: paranoiaMode = PARANOIA_MODE_DISABLE; break; case 1: paranoiaMode |= PARANOIA_MODE_OVERLAP; paranoiaMode &= ~PARANOIA_MODE_VERIFY; break; case 2: paranoiaMode &= ~(PARANOIA_MODE_SCRATCH|PARANOIA_MODE_REPAIR); break; } if (neverSkip) { paranoiaMode |= PARANOIA_MODE_NEVERSKIP; } if (paranoia) { #ifdef CDIOPARANOIA_FOUND cdio_paranoia_modeset(paranoia, paranoiaMode); #else paranoia_modeset(paranoia, paranoiaMode); #endif } } qint16 * CdParanoia::read() { #ifdef CDIOPARANOIA_FOUND return paranoia ? cdio_paranoia_read_limited(paranoia, 0, maxRetries) : 0; #else return paranoia ? paranoia_read_limited(paranoia, 0, maxRetries) : 0; #endif } int CdParanoia::seek(long sector, int mode) { #ifdef CDIOPARANOIA_FOUND return paranoia ? cdio_paranoia_seek(paranoia, sector, mode) : -1; #else return paranoia ? paranoia_seek(paranoia, sector, mode) : -1; #endif } int CdParanoia::firstSectorOfTrack(int track) { #ifdef CDIOPARANOIA_FOUND return paranoia ? cdio_cddap_track_firstsector(drive, track) : -1; #else return paranoia ? cdda_track_firstsector(drive, track) : -1; #endif } int CdParanoia::lastSectorOfTrack(int track) { #ifdef CDIOPARANOIA_FOUND return paranoia ? cdio_cddap_track_lastsector(drive, track) : -1; #else return paranoia ? cdda_track_lastsector(drive, track) : -1; #endif } bool CdParanoia::init() { free(); #ifdef CDIOPARANOIA_FOUND drive = cdda_identify(dev.toLatin1().data(), 0, 0); #else drive = cdda_identify(dev.toLatin1().data(), 0, 0); #endif if (!drive) { return false; } #ifdef CDIOPARANOIA_FOUND cdio_cddap_open(drive); paranoia = cdio_paranoia_init(drive); #else cdda_open(drive); paranoia = paranoia_init(drive); #endif if (paranoia == 0) { free(); return false; } return true; } void CdParanoia::free() { if (paranoia) { #ifdef CDIOPARANOIA_FOUND cdio_paranoia_free(paranoia); #else paranoia_free(paranoia); #endif paranoia = 0; } if (drive) { #ifdef CDIOPARANOIA_FOUND cdio_cddap_close(drive); #else cdda_close(drive); #endif drive = 0; } } cantata-2.2.0/devices/cdparanoia.h000066400000000000000000000044221316350454000170250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CDPARANOIA_H #define CDPARANOIA_H #include #include "config.h" extern "C" { #ifdef CDIOPARANOIA_FOUND #ifdef HAVE_CDIO_PARANOIA_H #include #elif defined HAVE_CDIO_PARANOIA_PARANOIA_H #include #endif #ifdef HAVE_CDIO_CDDA_H #include #elif defined HAVE_CDIO_PARANOIA_CDDA_H #include #endif #else #include #include #endif } class CdParanoia { public: CdParanoia(const QString &device, bool full, bool noSkip, bool playback=false); ~CdParanoia(); inline operator bool() const { return !dev.isEmpty(); } void setParanoiaMode(int mode); void setFullParanoiaMode(bool f) { setParanoiaMode(f ? 3 : 0); } void setMaxRetries(int m) { maxRetries=m; } qint16 * read(); int seek(long sector, int mode); int firstSectorOfTrack(int track); int lastSectorOfTrack(int track); int length(); int lengthOfTrack(int n); int numOfFramesOfTrack(int n); double sizeOfTrack(int n); //in MiB int frameOffsetOfTrack(int n); bool isAudioTrack(int n); void reset() { init(); } private: bool init(); void free(); private: QString dev; #ifdef CDIOPARANOIA_FOUND cdrom_drive_t *drive; cdrom_paranoia_t *paranoia; #else cdrom_drive *drive; cdrom_paranoia *paranoia; #endif int paranoiaMode; bool neverSkip; int maxRetries; }; #endif cantata-2.2.0/devices/device.cpp000066400000000000000000000305251316350454000165210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "device.h" #include "gui/covers.h" #include "support/utils.h" #include "mpd-interface/mpdconnection.h" #include #include #include #include #ifdef ENABLE_DEVICES_SUPPORT #include "models/devicesmodel.h" #include "umsdevice.h" #ifdef MTP_FOUND #include "mtpdevice.h" #endif // MTP_FOUND #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "audiocddevice.h" #endif // defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "encoders.h" #include "tags/tags.h" #include "mpd-interface/song.h" #include "mpd-interface/mpdparseutils.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemalbum.h" #include "models/musiclibraryitemsong.h" #include "models/musiclibrarymodel.h" #include "solid-lite/portablemediaplayer.h" #include "solid-lite/storageaccess.h" #include "solid-lite/storagedrive.h" #include "solid-lite/opticaldisc.h" #include "solid-lite/genericinterface.h" #endif // ENABLE_DEVICES_SUPPORT void Device::moveDir(const QString &from, const QString &to, const QString &base, const QString &coverFile) { QDir d(from); if (d.exists()) { QFileInfoList entries=d.entryInfoList(QDir::Files|QDir::NoSymLinks|QDir::Dirs|QDir::NoDotAndDotDot); QList extraFiles; QSet others=Covers::standardNames().toSet(); others << coverFile << "albumart.pamp"; if (!coverFile.isEmpty()) { if (coverFile.endsWith(QLatin1String(".jpg"))) { others << coverFile.left(coverFile.length()-4)+QLatin1String(".png"); } else if (coverFile.endsWith(QLatin1String(".png"))) { others << coverFile.left(coverFile.length()-4)+QLatin1String(".jpg"); } } foreach (const QFileInfo &info, entries) { if (info.isDir()) { return; } if (!others.contains(info.fileName()) && !MPDConnection::isPlaylist(info.fileName())) { return; } extraFiles.append(info.fileName()); } foreach (const QString &f, extraFiles) { if (!QFile::rename(from+'/'+f, to+'/'+f)) { return; } } cleanDir(from, base, coverFile); } } void Device::cleanDir(const QString &dir, const QString &base, const QString &coverFile, int level) { QDir d(dir); if (d.exists()) { QFileInfoList entries=d.entryInfoList(QDir::Files|QDir::NoSymLinks|QDir::Dirs|QDir::NoDotAndDotDot); QList extraFiles; QSet others=Covers::standardNames().toSet(); others << coverFile << "albumart.pamp"; foreach (const QFileInfo &info, entries) { if (info.isDir()) { return; } if (!others.contains(info.fileName())) { return; } extraFiles.append(info.absoluteFilePath()); } foreach (const QString &cf, extraFiles) { if (!QFile::remove(cf)) { return; } } if (Utils::fixPath(dir)==Utils::fixPath(base)) { return; } QString dirName=d.dirName(); if (dirName.isEmpty()) { return; } d.cdUp(); if (!d.rmdir(dirName)) { return; } if (level>=3) { return; } QString upDir=d.absolutePath(); if (Utils::fixPath(upDir)!=Utils::fixPath(base)) { cleanDir(upDir, base, coverFile, level+1); } } } Song Device::fixPath(const Song &orig, bool fullPath) const { Song s=orig; if (fullPath) { QString p=path(); if (!p.isEmpty()) { s.file=p+s.file; } } return s; } #ifdef ENABLE_DEVICES_SUPPORT #include const QLatin1String Device::constNoCover("-"); const QLatin1String Device::constEmbedCover("+"); Device * Device::create(MusicLibraryModel *m, const QString &udi) { Solid::Device device=Solid::Device(udi); if (device.is()) { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND return Encoders::getAvailable().isEmpty() ? 0 : new AudioCdDevice(m, device); #endif } else if (device.is()) { #ifdef MTP_FOUND Solid::PortableMediaPlayer *pmp = device.as(); if (pmp->supportedProtocols().contains(QLatin1String("mtp"))) { Solid::GenericInterface *iface = device.as(); if (iface) { QMap properties = iface->allProperties(); QMap::ConstIterator bus = properties.find(QLatin1String("BUSNUM")); QMap::ConstIterator dev = properties.find(QLatin1String("DEVNUM")); return properties.constEnd()==bus || properties.end()==dev ? 0 : new MtpDevice(m, device, bus.value().toUInt(), dev.value().toUInt()); } } #endif } else if (device.is()) { const Solid::StorageAccess* ssa = device.as(); if( ssa && (!device.parent().as() || Solid::StorageDrive::Usb!=device.parent().as()->bus()) && (!device.as() || Solid::StorageDrive::Usb!=device.as()->bus()) ) { return 0; } //HACK: ignore apple stuff until we have common MediaDeviceFactory. if (!device.vendor().contains("apple", Qt::CaseInsensitive)) { // Solid::StorageAccess *sa = device.as(); // if (QLatin1String("usb")==sa->bus) { return new UmsDevice(m, device); // } } } return 0; } bool Device::fixVariousArtists(const QString &file, Song &song, bool applyFix) { Song orig=song; if (!file.isEmpty() && song.albumartist.isEmpty()) { song=Tags::read(file); } if (song.artist.isEmpty() || song.albumartist.isEmpty() || !Song::isVariousArtists(song.albumartist)) { song=orig; return false; } bool needsUpdating=false; if (!applyFix) { // Then real artist is embedded in track title... needsUpdating=song.revertVariousArtists(); } else if (applyFix) { // We must be copying to device, and need to place song artist into title... needsUpdating=song.fixVariousArtists(); } if (needsUpdating && (file.isEmpty() || Tags::Update_Modified==Tags::updateArtistAndTitle(file, song))) { return true; } song=orig; return false; } static QByteArray save(const QImage &img) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); img.save(&buffer, "JPG"); buffer.close(); return ba; } void Device::embedCover(const QString &file, Song &song, unsigned int coverMaxSize) { if (Tags::readImage(file).isNull()) { Covers::Image coverImage=Covers::self()->getImage(song); if (!coverImage.img.isNull()) { QByteArray imgData; if (coverMaxSize && (coverImage.img.width()>(int)coverMaxSize || coverImage.img.height()>(int)coverMaxSize)) { imgData=save(coverImage.img.scaled(QSize(coverMaxSize, coverMaxSize), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else if (!coverImage.fileName.endsWith(".jpg", Qt::CaseInsensitive) || !QFile::exists(coverImage.fileName)) { imgData=save(coverImage.img); } else { QFile f(coverImage.fileName); if (f.open(QIODevice::ReadOnly)) { imgData=f.readAll(); } else { imgData=save(coverImage.img); } } Tags::embedImage(file, imgData); } } } QTemporaryFile * Device::copySongToTemp(Song &song) { QTemporaryFile *temp=new QTemporaryFile(); int index=song.file.lastIndexOf('.'); if (index>0) { temp=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX"+song.file.mid(index)); } else { temp=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX"); } temp->setAutoRemove(false); if (temp->open()) { temp->close(); if (QFile::exists(temp->fileName())) { QFile::remove(temp->fileName()); // Copy will *not* overwrite file! } if (!QFile::copy(song.file, temp->fileName())) { temp->remove(); delete temp; temp=0; } } return temp; } bool Device::isLossless(const QString &file) { static QStringList lossless=QStringList() << ".flac" << ".wav"; // TODO: ALAC?? Its in .m4a!!! QString lower = file.toLower(); for (const auto &e: lossless) { if (lower.endsWith(e)) { return true; } } return false; } Device::Device(MusicLibraryModel *m, Solid::Device &dev, bool albumArtistSupport, bool flat) : MusicLibraryItemRoot(dev.product().startsWith(dev.vendor()) ? dev.product() : (dev.vendor()+QChar(' ')+dev.product()), albumArtistSupport, flat) , configured(false) , solidDev(dev) , deviceId(dev.udi()) , update(0) , needToFixVa(false) , jobAbortRequested(false) , transcoding(false) { m_model=m; icn=Icon(solidDev.isValid() ? solidDev.icon() : QLatin1String("inode-directory")); m_itemData[0]=m_itemData[0].toUpper(); } Device::Device(MusicLibraryModel *m, const QString &name, const QString &id) : MusicLibraryItemRoot(name) , configured(false) , deviceId(id) , update(0) , needToFixVa(false) , jobAbortRequested(false) , transcoding(false) { m_model=m; icn=Icon(solidDev.isValid() ? solidDev.icon() : QLatin1String("inode-directory")); } void Device::saveCache() { QTimer::singleShot(0, this, SIGNAL(cacheSaved())); } void Device::applyUpdate() { if (!update) { return; } /*if (m_childItems.count() && update && update->childCount()) { QSet currentSongs=allSongs(); QSet updateSongs=update->allSongs(); QSet removed=currentSongs-updateSongs; QSet added=updateSongs-currentSongs; foreach (const Song &s, removed) { removeSongFromList(s); } foreach (const Song &s, added) { addSongToList(s); } delete update; } else*/ { int oldCount=childCount(); if (oldCount>0) { m_model->beginRemoveRows(index(), 0, oldCount-1); clearItems(); m_model->endRemoveRows(); } int newCount=newRows(); if (newCount>0) { m_model->beginInsertRows(index(), 0, newCount-1); foreach (MusicLibraryItem *item, update->childItems()) { item->setParent(this); } if (AudioCd!=devType()) { refreshIndexes(); } m_model->endInsertRows(); } } delete update; update=0; } QModelIndex Device::index() const { return m_model->createIndex(m_model->row((void *)this), 0, (void *)this); } void Device::setStatusMessage(const QString &msg) { statusMsg=msg; updateStatus(); } void Device::updateStatus() { QModelIndex modelIndex=index(); emit m_model->dataChanged(modelIndex, modelIndex); } void Device::songCount(int c) { setStatusMessage(tr("Updating (%1)...").arg(c)); } void Device::updatePercentage(int pc) { setStatusMessage(tr("Updating (%1%)...").arg(pc)); } #endif // ENABLE_DEVICES_SUPPORT cantata-2.2.0/devices/device.h000066400000000000000000000143311316350454000161630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICE_H #define DEVICE_H #include "models/musiclibraryitemroot.h" #include "mpd-interface/song.h" #include "gui/covers.h" #include "config.h" #ifdef ENABLE_DEVICES_SUPPORT #include "deviceoptions.h" #include "solid-lite/device.h" #endif class QWidget; class QImage; class QTemporaryFile; class MusicLibraryModel; // MOC requires the QObject class to be first. But due to models storing void pointers, and // needing to cast these - the model prefers MusicLibraryItemRoot to be first! #ifdef Q_MOC_RUN class Device : public QObject, public MusicLibraryItemRoot #else class Device : public MusicLibraryItemRoot, public QObject #endif { Q_OBJECT public: #ifdef ENABLE_DEVICES_SUPPORT static const QLatin1String constNoCover; static const QLatin1String constEmbedCover; static Device * create(MusicLibraryModel *m, const QString &id); static bool fixVariousArtists(const QString &file, Song &song, bool applyFix); static void embedCover(const QString &file, Song &song, unsigned int coverMaxSize); static QTemporaryFile * copySongToTemp(Song &song); static bool isLossless(const QString &file); #endif static void moveDir(const QString &from, const QString &to, const QString &base, const QString &coverFile); static void cleanDir(const QString &dir, const QString &base, const QString &coverFile, int level=0); enum Status { Ok, FileExists, SongExists, DirCreationFaild, SourceFileDoesNotExist, SongDoesNotExist, Failed, NotConnected, CodecNotAvailable, TranscodeFailed, FailedToCreateTempFile, ReadFailed, WriteFailed, NoSpace, FailedToUpdateTags, Cancelled, FailedToLockDevice, // These are for online services... DownloadFailed }; enum DevType { Ums, Mtp, RemoteFs, AudioCd }; #ifndef ENABLE_DEVICES_SUPPORT Device(MusicLibraryModel *m, const QString &name, const QString &id) : MusicLibraryItemRoot(name) , update(0) , needToFixVa(false) , jobAbortRequested(false) , transcoding(false) { Q_UNUSED(m) Q_UNUSED(id) } #else Device(MusicLibraryModel *m, Solid::Device &dev, bool albumArtistSupport=true, bool flat=false); Device(MusicLibraryModel *m, const QString &name, const QString &id); #endif virtual ~Device() { } Icon icon() const { return icn; } Song fixPath(const Song &orig, bool fullPath) const; virtual QString coverFile() const { return QString(); } virtual bool isConnected() const=0; virtual void rescan(bool full=true)=0; virtual void stop()=0; virtual void connectionStateChanged() { } virtual void toggle() { } virtual bool isRefreshing() const=0; bool isIdle() const { return isConnected() && !isRefreshing(); } virtual void configure(QWidget *) { } virtual QString path() const =0; virtual void addSong(const Song &s, bool overwrite, bool copyCover)=0; virtual void copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover)=0; virtual void removeSong(const Song &s)=0; virtual void cleanDirs(const QSet &dirs)=0; virtual Covers::Image requestCover(const Song &) { return Covers::Image(); } virtual double usedCapacity()=0; virtual QString capacityString()=0; virtual qint64 freeSpace()=0; virtual DevType devType() const=0; virtual void removeCache() { } virtual bool isDevice() const { return true; } #ifdef ENABLE_DEVICES_SUPPORT virtual void saveCache(); const QString & id() const { return deviceId; } void applyUpdate(); bool haveUpdate() const { return 0!=update; } int newRows() const { return update ? update->childCount() : 0; } const DeviceOptions & options() const { return opts; } void setOptions(const DeviceOptions &o) { opts=o; saveOptions(); } virtual void saveOptions()=0; const QString & statusMessage() const { return statusMsg; } bool isConfigured() { return configured; } virtual void abortJob() { jobAbortRequested=true; } bool abortRequested() const { return jobAbortRequested; } virtual bool canPlaySongs() const { return false; } virtual bool supportsDisconnect() const { return false; } virtual bool isStdFs() const { return false; } virtual QString subText() { return QString(); } QModelIndex index() const; void updateStatus(); public Q_SLOTS: void setStatusMessage(const QString &message); void songCount(int c); void updatePercentage(int pc); #endif Q_SIGNALS: void connected(const QString &id); void disconnected(const QString &id); void updating(const QString &id, bool s); void actionStatus(int status, bool copiedCover=false); void progress(int pc); void error(const QString &); void cover(const Song &song, const QImage &img); void cacheSaved(); void configurationChanged(); void play(const QList &songs); void updatedDetails(const QList &songs); void renamed(); protected: #ifdef ENABLE_DEVICES_SUPPORT DeviceOptions opts; bool configured; Solid::Device solidDev; QString deviceId; Song currentSong; #endif MusicLibraryItemRoot *update; QString currentDestFile; QString statusMsg; bool needToFixVa; bool jobAbortRequested; bool transcoding; Icon icn; }; #endif cantata-2.2.0/devices/deviceoptions.cpp000066400000000000000000000334551316350454000201420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "deviceoptions.h" #include "mpd-interface/song.h" #include "support/configuration.h" #include #include #ifndef _MSC_VER #include #endif static QString cleanPath(const QString &path) { /* Unicode uses combining characters to form accented versions of other characters. * (Exception: Latin-1 table for compatibility with ASCII.) * Those can be found in the Unicode tables listed at: * http://en.wikipedia.org/w/index.php?title=Combining_character&oldid=255990982 * Removing those characters removes accents. :) */ QString result = path; // German umlauts result.replace(QChar(0x00e4), "ae").replace(QChar(0x00c4), "Ae"); result.replace(QChar(0x00f6), "oe").replace(QChar(0x00d6), "Oe"); result.replace(QChar(0x00fc), "ue").replace(QChar(0x00dc), "Ue"); result.replace(QChar(0x00df), "ss"); // other special cases result.replace(QChar(0x00C6), "AE"); result.replace(QChar(0x00E6), "ae"); result.replace(QChar(0x00D8), "OE"); result.replace(QChar(0x00F8), "oe"); // normalize in a form where accents are separate characters result = result.normalized(QString::NormalizationForm_D); // remove accents from table "Combining Diacritical Marks" for (int i = 0x0300; i <= 0x036F; i++) { result.remove(QChar(i)); } return result; } static QString asciiPath(const QString &path) { QString result = path; for (int i = 0; i < result.length(); i++) { QChar c = result[ i ]; if (c > QChar(0x7f) || QChar(0)==c) { c = '_'; } result[ i ] = c; } return result; } QString vfatPath(const QString &path) { QString s = path; if ('/'==QDir::separator()) {// we are on *nix, \ is a valid character in file or directory names, NOT the dir separator s.replace('\\', '_'); } else { s.replace('/', '_'); // on windows we have to replace / instead } for (int i = 0; i < s.length(); i++) { QChar c = s[ i ]; if (c < QChar(0x20) || c == QChar(0x7F) // 0x7F = 127 = DEL control character || '*'==c || '?'==c || '<'==c || '>'==c || '|'==c || '"'==c || ':'==c) { c = '_'; } else if ('['==c) { c = '('; } else if (']'==c) { c = ')'; } s[ i ] = c; } /* beware of reserved device names */ uint len = s.length(); if (3==len || (len > 3 && '.'==s[3])) { QString l = s.left(3).toLower(); if ("aux"==l || "con"==l || "nul"==l || "prn"==l) { s = '_' + s; } } else if (4==len || (len > 4 && '.'==s[4])) { QString l = s.left(3).toLower(); QString d = s.mid(3,1); if (("com"==l || "lpt"==l) && ("0"==d || "1"==d || "2"==d || "3"==d || "4"==d || "5"==d || "6"==d || "7"==d || "8"==d || "9"==d)) { s = '_' + s; } } // "clock$" is only allowed WITH extension, according to: // http://en.wikipedia.org/w/index.php?title=Filename&oldid=303934888#Comparison_of_file_name_limitations if (0==QString::compare(s, "clock$", Qt::CaseInsensitive)) { s = '_' + s; } /* max path length of Windows API */ s = s.left(255); /* whitespace at the end of folder/file names or extensions are bad */ len = s.length(); if (' '==s[len-1]) { s[len-1] = '_'; } int extensionIndex = s.lastIndexOf('.'); // correct trailing spaces in file name itself if (s.length()>1 && extensionIndex>0 && ' '==s.at(extensionIndex-1)) { s[extensionIndex - 1] = '_'; } for (int i = 1; i < s.length(); i++) {// correct trailing whitespace in folder names if ((s.at(i) == QDir::separator()) && ' '==s.at(i-1)) { s[i - 1] = '_'; } } return s; } static void manipulateThe(QString &str, bool reverse) { if (reverse) { if (!str.startsWith("the ", Qt::CaseInsensitive)) { return; } QString begin = str.left(3); str = str.append(", %1").arg(begin); str = str.mid(4); return; } if(!str.endsWith(", the", Qt::CaseInsensitive)) { return; } QString end = str.right(3); str = str.prepend("%1 ").arg(end); str.truncate(str.length() - end.length() - 2); } const QLatin1String DeviceOptions::constAlbumArtist("%albumartist%"); const QLatin1String DeviceOptions::constComposer("%composer%"); const QLatin1String DeviceOptions::constAlbumTitle("%album%"); const QLatin1String DeviceOptions::constTrackArtist("%artist%"); const QLatin1String DeviceOptions::constTrackTitle("%title%"); const QLatin1String DeviceOptions::constTrackArtistAndTitle("%artistandtitle%"); const QLatin1String DeviceOptions::constTrackNumber("%track%"); const QLatin1String DeviceOptions::constCdNumber("%discnumber%"); const QLatin1String DeviceOptions::constGenre("%genre%"); const QLatin1String DeviceOptions::constYear("%year%"); #ifdef ENABLE_DEVICES_SUPPORT DeviceOptions::DeviceOptions(const QString &cvrName) #else DeviceOptions::DeviceOptions() #endif : scheme(constAlbumArtist+QChar('/')+ constAlbumTitle+QChar('/')+ constTrackNumber+QChar(' ')+ constTrackTitle) , vfatSafe(true) , asciiOnly(false) , ignoreThe(false) , replaceSpaces(false) #ifdef ENABLE_DEVICES_SUPPORT , fixVariousArtists(false) , transcoderValue(0) , transcoderWhenDifferent(false) , transcoderWhenSourceIsLosssless(false) , useCache(true) , autoScan(false) , coverName(cvrName) , coverMaxSize(0) #endif { } static const QLatin1String constMpdGroup("mpd"); static const QLatin1String constSchemeKey("scheme"); static const QLatin1String constVFatKey("vfatSafe"); static const QLatin1String constAsciiKey("asciiOnly"); static const QLatin1String constIgnoreTheKey("ignoreThe"); static const QLatin1String constSpacesKey("replaceSpaces"); #ifdef ENABLE_DEVICES_SUPPORT static const QLatin1String constTransCodecKey("transcoderCodec"); static const QLatin1String constTransValKey("transcoderValue"); static const QLatin1String constTransIfDiffKey("transcoderWhenDifferent"); static const QLatin1String constTransIfLosslessKey("transcoderWhenSourceIsLosssless"); #endif static const QLatin1String constUseCacheKey("useCache"); static const QLatin1String constFixVaKey("fixVariousArtists"); static const QLatin1String constNameKey("name"); static const QLatin1String constCvrFileKey("coverFileName"); static const QLatin1String constCvrMaxKey("coverMaxSize"); static const QLatin1String constVolKey("volumeId"); bool DeviceOptions::isConfigured(const QString &group, bool isMpd) { if (Configuration(group).hasEntry(constSchemeKey)) { return true; } if (isMpd) { return Configuration(constMpdGroup).hasEntry(constSchemeKey); } return false; } void DeviceOptions::load(const QString &group, bool isMpd) { Configuration cfg(group); if (isMpd && !cfg.hasEntry(constSchemeKey)) { // Try old [mpd] group... Configuration mpdGrp(constMpdGroup); if (mpdGrp.hasEntry(constSchemeKey)) { scheme=mpdGrp.get(constSchemeKey, scheme); vfatSafe=mpdGrp.get(constVFatKey, vfatSafe); asciiOnly=mpdGrp.get(constAsciiKey, asciiOnly); ignoreThe=mpdGrp.get(constIgnoreTheKey, ignoreThe); replaceSpaces=mpdGrp.get(constSpacesKey, replaceSpaces); #ifdef ENABLE_DEVICES_SUPPORT transcoderCodec=mpdGrp.get(constTransCodecKey, (isMpd ? "lame" : transcoderCodec)); transcoderValue=mpdGrp.get(constTransValKey, (isMpd ? 2 : transcoderValue)); transcoderWhenDifferent=mpdGrp.get(constTransIfDiffKey, transcoderWhenDifferent); transcoderWhenSourceIsLosssless=mpdGrp.get(constTransIfLosslessKey, transcoderWhenSourceIsLosssless); #endif // Save these settings into new group, [mpd] is left for other connections... save(group, true, true, true); } } else { scheme=cfg.get(constSchemeKey, scheme); vfatSafe=cfg.get(constVFatKey, vfatSafe); asciiOnly=cfg.get(constAsciiKey, asciiOnly); ignoreThe=cfg.get(constIgnoreTheKey, ignoreThe); replaceSpaces=cfg.get(constSpacesKey, replaceSpaces); #ifdef ENABLE_DEVICES_SUPPORT if (!isMpd) { useCache=cfg.get(constUseCacheKey, useCache); fixVariousArtists=cfg.get(constFixVaKey, fixVariousArtists); name=cfg.get(constNameKey, name); coverName=cfg.get(constCvrFileKey, coverName); coverMaxSize=cfg.get(constCvrMaxKey, coverMaxSize); volumeId=cfg.get(constVolKey, volumeId); checkCoverSize(); } transcoderCodec=cfg.get(constTransCodecKey, (isMpd ? "lame" : transcoderCodec)); transcoderValue=cfg.get(constTransValKey, (isMpd ? 2 : transcoderValue)); transcoderWhenDifferent=cfg.get(constTransIfDiffKey, transcoderWhenDifferent); transcoderWhenSourceIsLosssless=cfg.get(constTransIfLosslessKey, transcoderWhenSourceIsLosssless); #endif } } void DeviceOptions::save(const QString &group, bool isMpd, bool saveTrans, bool saveFileName) const { Configuration cfg(group); if (saveFileName) { cfg.set(constSchemeKey, scheme); cfg.set(constVFatKey, vfatSafe); cfg.set(constAsciiKey, asciiOnly); cfg.set(constIgnoreTheKey, ignoreThe); cfg.set(constSpacesKey, replaceSpaces); } #ifdef ENABLE_DEVICES_SUPPORT if (!isMpd) { cfg.set(constUseCacheKey, useCache); cfg.set(constFixVaKey, fixVariousArtists); cfg.set(constNameKey, name); cfg.set(constCvrFileKey, coverName); cfg.set(constCvrMaxKey, coverMaxSize); cfg.set(constVolKey, volumeId); } if (saveTrans) { cfg.set(constTransCodecKey, transcoderCodec); cfg.set(constTransValKey, transcoderValue); cfg.set(constTransIfDiffKey, transcoderWhenDifferent); cfg.set(constTransIfLosslessKey, transcoderWhenSourceIsLosssless); } #else Q_UNUSED(isMpd) Q_UNUSED(saveTrans) #endif } QString DeviceOptions::clean(const QString &str) const { QString result(str); if (asciiOnly) { result = cleanPath(result); result = asciiPath(result); } result=result.simplified(); if (replaceSpaces) { result.replace(QRegExp("\\s"), "_"); } if (vfatSafe) { result = vfatPath(result); } result.replace('/', '-'); return result; } Song DeviceOptions::clean(const Song &s) const { Song copy=s; if (ignoreThe) { manipulateThe(copy.albumartist, true); manipulateThe(copy.artist, true); } copy.albumartist=clean(copy.albumartist); copy.artist=clean(copy.artist); copy.album=clean(copy.album); copy.title=clean(copy.title); for (int i=0; i * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICE_OPTIONS_H #define DEVICE_OPTIONS_H #include #include "config.h" #ifdef ENABLE_DEVICES_SUPPORT #include "encoders.h" #endif struct Song; struct DeviceStorage { DeviceStorage() : size(0), used(0) { } qulonglong freeSpace() const { return size-used; } qulonglong size; qulonglong used; QString description; QString volumeIdentifier; }; struct DeviceOptions { static const QLatin1String constAlbumArtist; static const QLatin1String constComposer; static const QLatin1String constAlbumTitle; static const QLatin1String constTrackArtist; static const QLatin1String constTrackTitle; static const QLatin1String constTrackArtistAndTitle; static const QLatin1String constTrackNumber; static const QLatin1String constCdNumber; static const QLatin1String constGenre; static const QLatin1String constYear; static bool isConfigured(const QString &group, bool isMpd=false); #ifdef ENABLE_DEVICES_SUPPORT DeviceOptions(const QString &cvrName=QString()); #else DeviceOptions(); #endif void load(const QString &group, bool isMpd=false); void save(const QString &group, bool isMpd=false, bool saveTrans=true, bool saveFileNameScheme=true) const; bool operator==(const DeviceOptions &o) const { return vfatSafe==o.vfatSafe && asciiOnly==o.asciiOnly && ignoreThe==o.ignoreThe && replaceSpaces==o.replaceSpaces && scheme==o.scheme #ifdef ENABLE_DEVICES_SUPPORT && coverMaxSize==o.coverMaxSize && coverName==o.coverName && name==o.name && fixVariousArtists==o.fixVariousArtists && useCache==o.useCache && transcoderCodec==o.transcoderCodec && autoScan==o.autoScan && volumeId==o.volumeId && (transcoderCodec.isEmpty() || (transcoderValue==o.transcoderValue && transcoderWhenDifferent==o.transcoderWhenDifferent && transcoderWhenSourceIsLosssless==o.transcoderWhenSourceIsLosssless)) #endif ; } bool operator!=(const DeviceOptions &o) const { return !(*this==o); } QString clean(const QString &str) const; Song clean(const Song &s) const; QString createFilename(const Song &s) const; #ifdef ENABLE_DEVICES_SUPPORT void checkCoverSize() { if (0==coverMaxSize || coverMaxSize>400) { coverMaxSize=0; } else { coverMaxSize=((unsigned int)(coverMaxSize/100))*100; } } #endif QString scheme; bool vfatSafe; bool asciiOnly; bool ignoreThe; bool replaceSpaces; #ifdef ENABLE_DEVICES_SUPPORT bool fixVariousArtists; QString transcoderCodec; int transcoderValue; bool transcoderWhenDifferent; bool transcoderWhenSourceIsLosssless; bool useCache; bool autoScan; QString name; QString coverName; unsigned int coverMaxSize; QString volumeId; #endif }; #endif cantata-2.2.0/devices/devicepropertiesdialog.cpp000066400000000000000000000040761316350454000220200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" DevicePropertiesDialog::DevicePropertiesDialog(QWidget *parent) : Dialog(parent) { setButtons(Ok|Cancel); setCaption(tr("Device Properties")); setAttribute(Qt::WA_DeleteOnClose); setWindowModality(Qt::WindowModal); devProp=new DevicePropertiesWidget(this); devProp->showRemoteConnectionNote(false); setMainWidget(devProp); } void DevicePropertiesDialog::show(const QString &path, const DeviceOptions &opts, const QList &storage, int props, int disabledProps) { devProp->update(path, opts, storage, props, disabledProps); connect(devProp, SIGNAL(updated()), SLOT(enableOkButton())); Dialog::show(); enableButtonOk(false); } void DevicePropertiesDialog::enableOkButton() { enableButtonOk(devProp->isSaveable() && devProp->isModified()); } void DevicePropertiesDialog::slotButtonClicked(int button) { switch (button) { case Ok: emit updatedSettings(devProp->music(), devProp->settings()); break; case Cancel: emit cancelled(); reject(); break; default: break; } if (Ok==button) { accept(); } Dialog::slotButtonClicked(button); } cantata-2.2.0/devices/devicepropertiesdialog.h000066400000000000000000000032371316350454000214630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICEPROPERTIESDIALOG_H #define DEVICEPROPERTIESDIALOG_H #include "support/dialog.h" #include "device.h" class FilenameSchemeDialog; class DevicePropertiesWidget; class DevicePropertiesDialog : public Dialog { Q_OBJECT public: DevicePropertiesDialog(QWidget *parent); void show(const QString &path, const DeviceOptions &opts, int props, int disabledProps=0) { show(path, opts, QList(), props, disabledProps); } void show(const QString &path, const DeviceOptions &opts, const QList &storage, int props, int disabledProps=0); Q_SIGNALS: void updatedSettings(const QString &path, const DeviceOptions &opts); void cancelled(); private Q_SLOTS: void enableOkButton(); private: void slotButtonClicked(int button); private: DevicePropertiesWidget *devProp; }; #endif cantata-2.2.0/devices/devicepropertieswidget.cpp000066400000000000000000000405031316350454000220370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "devicepropertieswidget.h" #include "filenameschemedialog.h" #include "gui/covers.h" #include "widgets/icons.h" #include "support/utils.h" #include #include #include class CoverNameValidator : public QValidator { public: CoverNameValidator(QObject *parent) : QValidator(parent) { } State validate(QString &input, int &) const { int dotCount(0); for (int i=0; i1) { return Invalid; } } else if (!input[i].isLetterOrNumber() || input[i].isSpace()) { return Invalid; } } if (input.endsWith('.')) { return Intermediate; } return Acceptable; } // void fixup(QString &input) const // { // QString out; // int dotCount(0); // for (int i=0; isetIcon(Icons::self()->configureIcon); coverMaxSize->insertItems(0, QStringList() << tr("No maximum size") << tr("400 pixels") << tr("300 pixels") << tr("200 pixels") << tr("100 pixels")); fixVariousArtists->setToolTip(tr("

    When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', " "then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the " "track 'Title' tag to 'TrackArtist - TrackTitle'.


    When copying from a device, Cantata " "will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it " "will attempt to extract the real artist from the 'Title' tag, and remove the artist name " "from the 'Title' tag.

    ")); useCache->setToolTip(tr("

    If you enable this, then Cantata will create a cache of the device's music library. " "This will help to speed up subsequent library scans (as the cache file will be used instead of " "having to read the tags of each file.)


    NOTE: If you use another application to update " "the device's library, then this cache will become out-of-date. To rectify this, simply " "click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and " "the contents of the device re-scanned.

    ")); if (qobject_cast(parent)) { verticalLayout->setMargin(4); } } #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; void DevicePropertiesWidget::update(const QString &path, const DeviceOptions &opts, const QList &storage, int props, int disabledProps) { bool allowCovers=(props&Prop_CoversAll)||(props&Prop_CoversBasic); albumCovers->clear(); if (allowCovers) { if (props&Prop_CoversAll) { albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText << Covers::standardNames()); } else { albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText); } } if (props&Prop_Name) { name->setText(opts.name); connect(name, SIGNAL(textChanged(const QString &)), SLOT(checkSaveable())); } else { REMOVE(name) REMOVE(nameLabel) } if (props&Prop_FileName) { filenameScheme->setText(opts.scheme); vfatSafe->setChecked(opts.vfatSafe); asciiOnly->setChecked(opts.asciiOnly); ignoreThe->setChecked(opts.ignoreThe); replaceSpaces->setChecked(opts.replaceSpaces); } else { REMOVE(filenamesGroupBox) filenameScheme=0; vfatSafe=0; asciiOnly=0; ignoreThe=0; replaceSpaces=0; configFilename=0; } origOpts=opts; if (props&Prop_Folder) { musicFolder->setText(Utils::convertPathForDisplay(path)); connect(musicFolder, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); if (disabledProps&Prop_Folder) { musicFolder->setDisabled(true); } } else { REMOVE(musicFolder); REMOVE(musicFolderLabel); } if (allowCovers) { albumCovers->setEditable(props&Prop_CoversAll); if (origOpts.coverName==Device::constNoCover) { origOpts.coverName=noCoverText; albumCovers->setCurrentIndex(0); } if (origOpts.coverName==Device::constEmbedCover) { origOpts.coverName=embedCoverText; albumCovers->setCurrentIndex(1); } else { albumCovers->setCurrentIndex(0); for (int i=1; icount(); ++i) { if (albumCovers->itemText(i)==origOpts.coverName) { albumCovers->setCurrentIndex(i); break; } } } if (0!=origOpts.coverMaxSize) { int coverMax=origOpts.coverMaxSize/100; if (coverMax<0 || coverMax>=coverMaxSize->count()) { coverMax=0; } coverMaxSize->setCurrentIndex(0==coverMax ? 0 : (coverMaxSize->count()-coverMax)); } else { coverMaxSize->setCurrentIndex(0); } albumCovers->setValidator(new CoverNameValidator(this)); connect(albumCovers, SIGNAL(editTextChanged(const QString &)), this, SLOT(albumCoversChanged())); connect(coverMaxSize, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable())); } else { REMOVE(albumCovers); REMOVE(albumCoversLabel); REMOVE(coverMaxSize); REMOVE(coverMaxSizeLabel); } if (props&Prop_Va) { fixVariousArtists->setChecked(opts.fixVariousArtists); connect(fixVariousArtists, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); } else { REMOVE(fixVariousArtists); } if (props&Prop_Cache) { useCache->setChecked(opts.useCache); connect(useCache, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); } else { REMOVE(useCache); } if (props&Prop_AutoScan) { autoScan->setChecked(opts.autoScan); connect(autoScan, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); } else { REMOVE(autoScan); } if (props&Prop_Transcoder || props&Prop_Encoder) { bool transcode=props&Prop_Transcoder; transcoderName->clear(); if (transcode) { transcoderName->addItem(tr("Do not transcode"), QString()); transcoderName->setCurrentIndex(0); transcoderValue->setVisible(false); transcoderWhenDifferent->setVisible(false); transcoderWhenDifferent->setChecked(opts.transcoderWhenDifferent); transcoderWhenSourceIsLosssless->setVisible(false); transcoderWhenSourceIsLosssless->setChecked(opts.transcoderWhenSourceIsLosssless); connect(transcoderWhenDifferent, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); connect(transcoderWhenSourceIsLosssless, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); } else { transcoderFrame->setTitle(tr("Encoder")); REMOVE(transcoderWhenDifferent); REMOVE(transcoderWhenSourceIsLosssless); } QList encs=Encoders::getAvailable(); if (encs.isEmpty()) { transcoderFrame->setVisible(false); } else { foreach (const Encoders::Encoder &e, encs) { if (!transcode || e.transcoder) { QString name=e.name; if (transcode && name.endsWith(QLatin1String(" (ffmpeg)"))) { name=name.left(name.length()-9); } transcoderName->addItem(transcode ? tr("Transcode to %1").arg(name) : name, e.codec); } } if (opts.transcoderCodec.isEmpty()) { transcoderChanged(); } else { Encoders::Encoder enc=Encoders::getEncoder(opts.transcoderCodec); if (!enc.isNull()) { for (int i=1; icount(); ++i) { if (transcoderName->itemData(i).toString()==opts.transcoderCodec) { transcoderName->setCurrentIndex(i); transcoderChanged(); transcoderValue->setValue(opts.transcoderValue); break; } } } } } connect(transcoderName, SIGNAL(currentIndexChanged(int)), this, SLOT(transcoderChanged())); connect(transcoderValue, SIGNAL(valueChanged(int)), this, SLOT(checkSaveable())); } else { REMOVE(transcoderFrame); } if (storage.count()<2) { REMOVE(defaultVolume); REMOVE(defaultVolumeLabel); } else { foreach (const DeviceStorage &ds, storage) { defaultVolume->addItem(tr("%1 (%2 free)", "name (size free)") .arg(ds.description).arg(Utils::formatByteSize(ds.size-ds.used)), ds.volumeIdentifier); } for (int i=0; icount(); ++i) { if (defaultVolume->itemData(i).toString()==opts.volumeId) { defaultVolume->setCurrentIndex(i); break; } } connect(defaultVolume,SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable())); } origMusicFolder=Utils::fixPath(path); if (props&Prop_FileName) { connect(configFilename, SIGNAL(clicked()), SLOT(configureFilenameScheme())); connect(filenameScheme, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(vfatSafe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); connect(asciiOnly, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); connect(ignoreThe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); connect(replaceSpaces, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable())); } if (albumCovers) { albumCoversChanged(); } QTimer::singleShot(0, this, SLOT(setSize())); } void DevicePropertiesWidget::transcoderChanged() { QString codec=transcoderName->itemData(transcoderName->currentIndex()).toString(); if (codec.isEmpty()) { transcoderName->setToolTip(QString()); transcoderValue->setVisible(false); if (transcoderWhenDifferent) { transcoderWhenDifferent->setVisible(false); } if (transcoderWhenSourceIsLosssless) { transcoderWhenSourceIsLosssless->setVisible(false); } } else { Encoders::Encoder enc=Encoders::getEncoder(codec); transcoderName->setToolTip(enc.description); if (transcoderWhenDifferent) { transcoderWhenDifferent->setVisible(true); } if (transcoderWhenSourceIsLosssless) { transcoderWhenSourceIsLosssless->setVisible(true); } if (enc.values.count()) { transcoderValue->setValues(enc); transcoderValue->setVisible(true); } else { transcoderValue->setVisible(false); } } Utils::resizeWindow(this, true, false); checkSaveable(); } void DevicePropertiesWidget::albumCoversChanged() { if (coverMaxSize) { bool enableSize=albumCovers->currentText()!=noCoverText; coverMaxSize->setEnabled(enableSize); coverMaxSizeLabel->setEnabled(enableSize); } checkSaveable(); } void DevicePropertiesWidget::checkSaveable() { DeviceOptions opts=settings(); bool checkFolder=musicFolder ? musicFolder->isEnabled() : false; modified=opts!=origOpts; if (!modified && checkFolder) { modified=music()!=origMusicFolder; } saveable=!opts.scheme.isEmpty() && (!checkFolder || !music().isEmpty()) && !opts.coverName.isEmpty(); if (saveable && ( (-1!=opts.coverName.indexOf(noCoverText) && opts.coverName!=noCoverText) || (-1!=opts.coverName.indexOf(embedCoverText) && opts.coverName!=embedCoverText) ) ) { saveable=false; } emit updated(); } void DevicePropertiesWidget::configureFilenameScheme() { if (!schemeDlg) { schemeDlg=new FilenameSchemeDialog(this); connect(schemeDlg, SIGNAL(scheme(const QString &)), filenameScheme, SLOT(setText(const QString &))); } schemeDlg->show(settings()); } DeviceOptions DevicePropertiesWidget::settings() { DeviceOptions opts; if (name && name->isEnabled()) { opts.name=name->text().trimmed(); } if (filenameScheme) { opts.scheme=filenameScheme->text().trimmed(); } if (vfatSafe) { opts.vfatSafe=vfatSafe->isChecked(); } if (asciiOnly) { opts.asciiOnly=asciiOnly->isChecked(); } if (ignoreThe) { opts.ignoreThe=ignoreThe->isChecked(); } if (replaceSpaces) { opts.replaceSpaces=replaceSpaces->isChecked(); } opts.fixVariousArtists=fixVariousArtists ? fixVariousArtists->isChecked() : false; opts.useCache=useCache ? useCache->isChecked() : false; opts.autoScan=autoScan ? autoScan->isChecked() : false; opts.transcoderCodec=QString(); opts.transcoderValue=0; opts.transcoderWhenDifferent=false; opts.transcoderWhenSourceIsLosssless=false; opts.coverName=cover(); opts.coverMaxSize=(!coverMaxSize || 0==coverMaxSize->currentIndex()) ? 0 : ((coverMaxSize->count()-coverMaxSize->currentIndex())*100); opts.volumeId=defaultVolume && defaultVolume->isVisible() ? defaultVolume->itemData(defaultVolume->currentIndex()).toString() : QString(); if (transcoderFrame && transcoderFrame->isVisible()) { opts.transcoderCodec=transcoderName->itemData(transcoderName->currentIndex()).toString(); if (!opts.transcoderCodec.isEmpty()) { Encoders::Encoder enc=Encoders::getEncoder(opts.transcoderCodec); opts.transcoderWhenDifferent=transcoderWhenDifferent ? transcoderWhenDifferent->isChecked() : false; opts.transcoderWhenSourceIsLosssless=transcoderWhenSourceIsLosssless ? transcoderWhenSourceIsLosssless->isChecked() : false; if (!enc.isNull() && transcoderValue->value()value()).value; } } } return opts; } QString DevicePropertiesWidget::cover() const { QString coverName=albumCovers ? albumCovers->currentText().trimmed() : noCoverText; return coverName==noCoverText ? Device::constNoCover : coverName==embedCoverText ? Device::constEmbedCover : coverName; } void DevicePropertiesWidget::setSize() { Utils::resizeWindow(this, true, false); } cantata-2.2.0/devices/devicepropertieswidget.h000066400000000000000000000050271316350454000215060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICEPROPERTIESWIDGET_H #define DEVICEPROPERTIESWIDGET_H #include "ui_devicepropertieswidget.h" #include "device.h" #include "support/utils.h" class FilenameSchemeDialog; class DevicePropertiesWidget : public QWidget, Ui::DevicePropertiesWidget { Q_OBJECT public: enum Properties { Prop_Basic = 0x0000, Prop_Name = 0x0001, Prop_Folder = 0x0002, Prop_FileName = 0x0004, Prop_CoversAll = 0x0008, Prop_CoversBasic = 0x0010, Prop_Va = 0x0020, Prop_Transcoder = 0x0040, Prop_Cache = 0x0080, Prop_AutoScan = 0x0100, Prop_Encoder = 0x0200, Prop_All = 0x03FF }; DevicePropertiesWidget(QWidget *parent); virtual ~DevicePropertiesWidget() { } void update(const QString &path, const DeviceOptions &opts, const QList &storage, int props, int disabledProps); DeviceOptions settings(); bool isModified() const { return modified; } bool isSaveable() const { return saveable; } QString music() const { return musicFolder && musicFolder->isEnabled() ? Utils::convertPathFromDisplay(musicFolder->text()) : origMusicFolder; } QString cover() const; void showRemoteConnectionNote(bool v) { remoteDeviceNote->setVisible(v); } Q_SIGNALS: void updated(); private Q_SLOTS: void configureFilenameScheme(); void checkSaveable(); void transcoderChanged(); void albumCoversChanged(); void setSize(); private: FilenameSchemeDialog *schemeDlg; DeviceOptions origOpts; QString origMusicFolder; QString noCoverText; QString embedCoverText; bool modified; bool saveable; }; #endif cantata-2.2.0/devices/devicepropertieswidget.ui000066400000000000000000000210011316350454000216620ustar00rootroot00000000000000 DevicePropertiesWidget 0 0 549 528 0 These settings are only valid, and editable, when the device is connected. QFormLayout::ExpandingFieldsGrow Name: name Music folder: musicFolder Copy album covers as: albumCovers true Maximum cover size: coverMaxSize false Default volume: 'Various Artists' workaround Automatically scan music when attached Use cache Filenames QFormLayout::ExpandingFieldsGrow Filename scheme: filenameScheme 0 288 0 true true VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Only transcode if source file is of a different format Only transcode if source is FLAC/WAV Qt::Vertical 20 0 LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    PathRequester QLineEdit
    support/pathrequester.h
    1
    ValueSlider QSlider
    devices/valueslider.h
    PlainNoteLabel QLabel
    widgets/notelabel.h
    musicFolder albumCovers coverMaxSize defaultVolume fixVariousArtists autoScan useCache vfatSafe asciiOnly replaceSpaces ignoreThe filenameScheme configFilename transcoderName transcoderWhenDifferent transcoderWhenSourceIsLosssless transcoderValue
    cantata-2.2.0/devices/devicespage.cpp000066400000000000000000000533061316350454000175430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "devicespage.h" #include "models/musiclibraryitemroot.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemalbum.h" #include "models/musiclibraryitemsong.h" #include "models/devicesmodel.h" #include "gui/settings.h" #include "support/messagebox.h" #include "support/configuration.h" #include "widgets/icons.h" #include "widgets/menubutton.h" #include "support/action.h" #include "support/monoicon.h" #include "gui/stdactions.h" #include "syncdialog.h" #ifdef ENABLE_REMOTE_DEVICES #include "remotedevicepropertiesdialog.h" #include "devicepropertieswidget.h" #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT #include "replaygain/rgdialog.h" #endif #include "tags/tageditor.h" #include "actiondialog.h" #include "tags/trackorganiser.h" #include "gui/preferencesdialog.h" #include "gui/coverdialog.h" #include "mpd-interface/mpdconnection.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "audiocddevice.h" #include "albumdetailsdialog.h" #include "cddbselectiondialog.h" #endif #include DevicesPage::DevicesPage(QWidget *p) : SinglePageWidget(p) { copyAction = new Action(Icons::self()->downloadIcon, tr("Copy To Library"), this); ToolButton *copyToLibraryButton=new ToolButton(this); copyToLibraryButton->setDefaultAction(copyAction); syncAction = new Action(MonoIcon::icon(FontAwesome::exchange, Utils::monoIconColor()), tr("Synchronise"), this); syncAction->setEnabled(false); connect(syncAction, SIGNAL(triggered()), this, SLOT(sync())); #ifdef ENABLE_REMOTE_DEVICES forgetDeviceAction=new Action(tr("Forget Device"), this); connect(forgetDeviceAction, SIGNAL(triggered()), this, SLOT(forgetRemoteDevice())); #endif connect(DevicesModel::self()->connectAct(), SIGNAL(triggered()), this, SLOT(toggleDevice())); connect(DevicesModel::self()->disconnectAct(), SIGNAL(triggered()), this, SLOT(toggleDevice())); connect(DevicesModel::self(), SIGNAL(updated(QModelIndex)), this, SLOT(updated(QModelIndex))); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(view, SIGNAL(searchItems()), this, SLOT(searchItems())); connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions())); connect(copyAction, SIGNAL(triggered()), this, SLOT(copyToLibrary())); connect(DevicesModel::self()->configureAct(), SIGNAL(triggered()), this, SLOT(configureDevice())); connect(DevicesModel::self()->refreshAct(), SIGNAL(triggered()), this, SLOT(refreshDevice())); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND connect(DevicesModel::self()->editAct(), SIGNAL(triggered()), this, SLOT(editDetails())); connect(DevicesModel::self(), SIGNAL(matches(const QString &, const QList &)), SLOT(cdMatches(const QString &, const QList &))); #endif proxy.setSourceModel(DevicesModel::self()); view->setModel(&proxy); view->setRootIsDecorated(false); view->setSearchResetLevel(1); Configuration config(metaObject()->className()); view->load(config); MenuButton *menu=new MenuButton(this); menu->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); menu->addSeparator(); menu->addAction(DevicesModel::self()->configureAct()); menu->addAction(DevicesModel::self()->refreshAct()); #ifdef ENABLE_REMOTE_DEVICES menu->addSeparator(); Action *addRemote=new Action(tr("Add Device"), this); connect(addRemote, SIGNAL(triggered()), this, SLOT(addRemoteDevice())); menu->addAction(addRemote); menu->addAction(forgetDeviceAction); #endif init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu, QList() << copyToLibraryButton); view->addAction(copyAction); view->addAction(syncAction); view->addAction(StdActions::self()->organiseFilesAction); view->addAction(StdActions::self()->editTagsAction); #ifdef ENABLE_REPLAYGAIN_SUPPORT view->addAction(StdActions::self()->replaygainAction); #endif #ifdef ENABLE_REMOTE_DEVICES view->addSeparator(); view->addAction(forgetDeviceAction); #endif view->addSeparator(); view->addAction(StdActions::self()->deleteSongsAction); } DevicesPage::~DevicesPage() { Configuration config(metaObject()->className()); view->save(config); } void DevicesPage::clear() { DevicesModel::self()->clear(); view->goToTop(); } QString DevicesPage::activeFsDeviceUdi() const { Device *dev=activeFsDevice(); return dev ? dev->id() : QString(); } Device * DevicesPage::activeFsDevice() const { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (selected.isEmpty()) { return 0; } QString udi; Device *activeDev=0; foreach (const QModelIndex &idx, selected) { QModelIndex index = proxy.mapToSource(idx); MusicLibraryItem *item=static_cast(index.internalPointer()); if (item && MusicLibraryItem::Type_Root!=item->itemType()) { while(item->parentItem()) { item=item->parentItem(); } } if (item && MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (Device::Ums!=dev->devType() && Device::RemoteFs!=dev->devType()) { return 0; } if (activeDev) { return 0; } activeDev=dev; } } return activeDev; } QStringList DevicesPage::playableUrls() const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } QModelIndexList mapped; foreach (const QModelIndex &idx, selected) { mapped.append(proxy.mapToSource(idx)); } return DevicesModel::self()->playableUrls(mapped); } QList DevicesPage::selectedSongs(bool allowPlaylists) const { Q_UNUSED(allowPlaylists) QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } // Ensure all songs are from UMS/Remote devices... foreach (const QModelIndex &idx, selected) { MusicLibraryItem *item=static_cast(proxy.mapToSource(idx).internalPointer()); if (item && MusicLibraryItem::Type_Root!=item->itemType()) { while(item->parentItem()) { item=item->parentItem(); } } if (item && MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (Device::Ums!=dev->devType() && Device::RemoteFs!=dev->devType()) { return QList(); } } } return DevicesModel::self()->songs(proxy.mapToSource(selected)); } void DevicesPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { QStringList files=playableUrls(); if (!files.isEmpty()) { if (name.isEmpty()) { emit add(files, action, priorty, decreasePriority); } else { emit addSongsToPlaylist(name, files); } view->clearSelection(); } } void DevicesPage::refresh() { view->goToTop(); DevicesModel::self()->resetModel(); if (ItemView::Mode_SimpleTree==view->viewMode() || ItemView::Mode_DetailedTree==view->viewMode()) { for (int i=0; irowCount(QModelIndex()); ++i) { view->setExpanded(proxy.mapFromSource(DevicesModel::self()->index(i, 0, QModelIndex()))); } } } void DevicesPage::itemDoubleClicked(const QModelIndex &) { // const QModelIndexList selected = view->selectedIndexes(); // if (1!=selected.size()) { // return; //doubleclick should only have one selected item // } // MusicDevicesItem *item = DevicesModel::self()->toItem(proxy.mapToSource(selected.at(0))); // if (MusicDevicesItem::Type_Song==item->itemType()) { // addSelectionToPlaylist(); // } } void DevicesPage::searchItems() { QString text=view->searchText().trimmed(); proxy.update(text); if (proxy.enabled() && !proxy.filterText().isEmpty()) { view->expandAll(); } } void DevicesPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool haveTracks=false; bool onlyFs=true; bool singleUdi=true; bool connected=false; #ifdef ENABLE_REMOTE_DEVICES bool remoteDev=false; #endif bool deviceSelected=false; bool busyDevice=false; bool audioCd=false; bool canPlay=false; QString udi; foreach (const QModelIndex &idx, selected) { MusicLibraryItem *item=static_cast(proxy.mapToSource(idx).internalPointer()); if (item && MusicLibraryItem::Type_Root==item->itemType()) { deviceSelected=true; } if (item && MusicLibraryItem::Type_Root!=item->itemType()) { while(item->parentItem()) { item=item->parentItem(); } } if (item && MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (!dev->isStdFs()) { onlyFs=false; } if (!dev->isIdle()) { busyDevice=true; } if (Device::AudioCd==dev->devType()) { audioCd=true; } #ifdef ENABLE_REMOTE_DEVICES else if (Device::RemoteFs==dev->devType()) { remoteDev=true; } #endif canPlay=dev->canPlaySongs(); if (udi.isEmpty()) { udi=dev->id(); } else if (udi!=dev->id()) { singleUdi=false; } if (!haveTracks) { haveTracks=dev->childCount()>0; } connected=dev->isConnected(); } } DevicesModel::self()->configureAct()->setEnabled(!busyDevice && 1==selected.count() && !audioCd); DevicesModel::self()->refreshAct()->setEnabled(!busyDevice && 1==selected.count()); copyAction->setEnabled(!busyDevice && haveTracks && (!deviceSelected || audioCd)); syncAction->setEnabled(!audioCd && !busyDevice && deviceSelected && connected && 1==selected.count() && singleUdi && MPDConnection::self()->getDetails().dirReadable); StdActions::self()->deleteSongsAction->setEnabled(!audioCd && !busyDevice && haveTracks && !deviceSelected); StdActions::self()->editTagsAction->setEnabled(!busyDevice && haveTracks && onlyFs && singleUdi && !deviceSelected); #ifdef ENABLE_REPLAYGAIN_SUPPORT StdActions::self()->replaygainAction->setEnabled(!busyDevice && haveTracks && onlyFs && singleUdi && !deviceSelected); #endif StdActions::self()->organiseFilesAction->setEnabled(!busyDevice && haveTracks && onlyFs && singleUdi && !deviceSelected); StdActions::self()->enableAddToPlayQueue(canPlay && !selected.isEmpty() && singleUdi && !busyDevice && haveTracks && (audioCd || !deviceSelected)); #ifdef ENABLE_REMOTE_DEVICES forgetDeviceAction->setEnabled(singleUdi && remoteDev); #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND DevicesModel::self()->editAct()->setEnabled(!AlbumDetailsDialog::instanceCount() && !busyDevice && 1==selected.count() && audioCd && haveTracks && deviceSelected); #endif } void DevicesPage::copyToLibrary() { const QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return; } QModelIndexList mapped; foreach (const QModelIndex &idx, selected) { mapped.append(proxy.mapToSource(idx)); } MusicLibraryItem *item=static_cast(mapped.first().internalPointer()); while (item->parentItem()) { item=item->parentItem(); } QString udi; if (MusicLibraryItem::Type_Root==item->itemType()) { udi=static_cast(item)->id(); } if (udi.isEmpty()) { return; } QList songs=DevicesModel::self()->songs(mapped); if (!songs.isEmpty()) { emit addToDevice(udi, QString(), songs); view->clearSelection(); } } void DevicesPage::configureDevice() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } MusicLibraryItem *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { static_cast(item)->configure(this); } } void DevicesPage::refreshDevice() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } MusicLibraryItem *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); QString udi=dev->id(); bool full=true; if (Device::AudioCd==dev->devType()) { // Bit hacky - we use 'full' to determine CDDB/MusicBrainz! #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND switch (MessageBox::questionYesNoCancel(this, tr("Lookup album and track details?"), tr("Refresh"), GuiItem(tr("Via CDDB")), GuiItem(tr("Via MusicBrainz")))) { case MessageBox::Yes: full=true; // full==true => CDDB break; case MessageBox::No: full=false; // full==false => MusicBrainz break; default: return; } #else if (MessageBox::No==MessageBox::questionYesNo(this, tr("Lookup album and track details?"), tr("Refresh"), GuiItem(tr("Refresh")), StdGuiItem::cancel())) { return; } #endif dev=DevicesModel::self()->device(udi); } else { if (dev->childCount() && Device::Mtp!=dev->devType()) { static const QChar constBullet(0x2022); switch (MessageBox::questionYesNoCancel(this, tr("Which type of refresh do you wish to perform?")+QLatin1String("\n\n")+ constBullet+QLatin1Char(' ')+tr("Partial - Only new songs are scanned (quick)")+QLatin1Char('\n')+ constBullet+QLatin1Char(' ')+tr("Full - All songs are rescanned (slow)"), tr("Refresh"), GuiItem(tr("Partial")), GuiItem(tr("Full")))) { case MessageBox::Yes: full=false; case MessageBox::No: break; default: return; } // We have to query for device again, as it could have been unplugged whilst the above question dialog was visible... dev=DevicesModel::self()->device(udi); } } if (dev) { dev->rescan(full); } } } void DevicesPage::deleteSongs() { const QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return; } QModelIndexList mapped; foreach (const QModelIndex &idx, selected) { mapped.append(proxy.mapToSource(idx)); } MusicLibraryItem *item=static_cast(mapped.first().internalPointer()); while (item->parentItem()) { item=item->parentItem(); } QString udi; if (MusicLibraryItem::Type_Root==item->itemType()) { udi=static_cast(item)->id(); } if (udi.isEmpty()) { return; } QList songs=DevicesModel::self()->songs(mapped); if (!songs.isEmpty()) { if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Are you sure you wish to delete the selected songs?\n\nThis cannot be undone."), tr("Delete Songs"), StdGuiItem::del(), StdGuiItem::cancel())) { emit deleteSongs(udi, songs); } view->clearSelection(); } } void DevicesPage::addRemoteDevice() { #ifdef ENABLE_REMOTE_DEVICES RemoteDevicePropertiesDialog *dlg=new RemoteDevicePropertiesDialog(this); dlg->show(DeviceOptions(QLatin1String("cover.jpg")), RemoteFsDevice::Details(), DevicePropertiesWidget::Prop_All-DevicePropertiesWidget::Prop_Folder, true); connect(dlg, SIGNAL(updatedSettings(const DeviceOptions &, RemoteFsDevice::Details)), DevicesModel::self(), SLOT(addRemoteDevice(const DeviceOptions &, RemoteFsDevice::Details))); #endif } void DevicesPage::forgetRemoteDevice() { #ifdef ENABLE_REMOTE_DEVICES Device *dev=activeFsDevice(); if (!dev) { return; } QString udi=dev->id(); QString devName=dev->data(); if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Are you sure you wish to forget '%1'?").arg(devName))) { DevicesModel::self()->removeRemoteDevice(udi); } #endif } void DevicesPage::toggleDevice() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } MusicLibraryItem *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (dev->isConnected() && (Device::AudioCd==dev->devType() ? MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to eject Audio CD '%1 - %2'?").arg(dev->data()).arg(dev->subText()), tr("Eject"), GuiItem(tr("Eject")), StdGuiItem::cancel()) : MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to disconnect '%1'?").arg(dev->data()), tr("Disconnect"), GuiItem(tr("Disconnect")), StdGuiItem::cancel()))) { return; } static_cast(item)->toggle(); } } void DevicesPage::sync() { if (0!=SyncDialog::instanceCount()) { return; } if (0!=PreferencesDialog::instanceCount() || 0!=TagEditor::instanceCount() || 0!=TrackOrganiser::instanceCount() || 0!=ActionDialog::instanceCount() || 0!=CoverDialog::instanceCount() #ifdef ENABLE_REPLAYGAIN_SUPPORT || 0!=RgDialog::instanceCount() #endif ) { MessageBox::error(this, tr("Please close other dialogs first.")); return; } const QModelIndexList selected = view->selectedIndexes(); if (1!=selected.size()) { return; } MusicLibraryItem *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { SyncDialog *dlg=new SyncDialog(this); dlg->sync(static_cast(item)->id()); } } void DevicesPage::updated(const QModelIndex &idx) { view->setExpanded(proxy.mapFromSource(idx)); } void DevicesPage::cdMatches(const QString &udi, const QList &albums) { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND int chosen=0; Device *dev=DevicesModel::self()->device(udi); if (dev && Device::AudioCd==dev->devType()) { if (Device::AudioCd==dev->devType()) { CddbSelectionDialog *dlg=new CddbSelectionDialog(this); chosen=dlg->select(albums); if (chosen<0 || chosen>=albums.count()) { chosen=0; } } } // Need to get device again, as it may have been removed! dev=DevicesModel::self()->device(udi); if (dev && Device::AudioCd==dev->devType()) { if (Device::AudioCd==dev->devType()) { static_cast(dev)->setDetails(albums.at(chosen)); } } #else Q_UNUSED(udi) Q_UNUSED(albums) #endif } void DevicesPage::editDetails() { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (AlbumDetailsDialog::instanceCount()) { return; } const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } MusicLibraryItem *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (Device::AudioCd==dev->devType()) { AlbumDetailsDialog *dlg=new AlbumDetailsDialog(this); dlg->show(static_cast(dev)); } } #endif } cantata-2.2.0/devices/devicespage.h000066400000000000000000000046651316350454000172140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICESPAGE_H #define DEVICESPAGE_H #include "device.h" #include "widgets/singlepagewidget.h" #ifdef ENABLE_REMOTE_DEVICES #include "remotefsdevice.h" #endif #include "cdalbum.h" #include "models/musiclibraryproxymodel.h" class Action; class DevicesPage : public SinglePageWidget { Q_OBJECT public: DevicesPage(QWidget *p); virtual ~DevicesPage(); void clear(); QString activeFsDeviceUdi() const; QStringList playableUrls() const; QList selectedSongs(bool allowPlaylists=false) const; void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); void focusSearch() { view->focusSearch(); } void refresh(); void resort() { proxy.sort(); } public Q_SLOTS: void itemDoubleClicked(const QModelIndex &); void searchItems(); void controlActions(); void copyToLibrary(); void configureDevice(); void refreshDevice(); void deleteSongs(); void addRemoteDevice(); void forgetRemoteDevice(); void sync(); void toggleDevice(); void updated(const QModelIndex &idx); void cdMatches(const QString &udi, const QList &albums); void editDetails(); private: Device * activeFsDevice() const; Q_SIGNALS: void addToDevice(const QString &from, const QString &to, const QList &songs); void deleteSongs(const QString &from, const QList &songs); private: MusicLibraryProxyModel proxy; Action *copyAction; Action *syncAction; #ifdef ENABLE_REMOTE_DEVICES Action *forgetDeviceAction; #endif }; #endif cantata-2.2.0/devices/encoders.cpp000066400000000000000000000607151316350454000170700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * Large portions of this file, mainly the textual descriptions, etc, are taken from * Amarok - hence the (c) notice below... */ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * 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 Encoder. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "encoders.h" #include "support/utils.h" #include #include namespace Encoders { static QList installedEncoders; static bool usingAvconv=false; static void insertCodec(const QString &cmd, const QString ¶m,const QString &outputParam, Encoder &enc) { QString command=Utils::findExe(cmd); if (!command.isEmpty()) { int index=installedEncoders.indexOf(enc); enc.codec=cmd; enc.param=param; enc.transcoder=false; enc.app=command; enc.outputParam=outputParam; if (-1!=index) { Encoder orig=installedEncoders.takeAt(index); orig.name+=usingAvconv ? QLatin1String(" (avconv)") : QLatin1String(" (ffmpeg)"); installedEncoders.insert(index, orig); enc.name+=QLatin1String(" (")+cmd+QChar(')'); installedEncoders.insert(index+1, enc); } else { installedEncoders.append(enc); } } } static void init() { static bool initialised=false; if (!initialised) { initialised=true; QString command=Utils::findExe(QLatin1String("ffmpeg")); if (command.isEmpty()) { command=Utils::findExe(QLatin1String("avconv")); usingAvconv=true; } QByteArray vbr = "~%1kb/s VBR"; QByteArray cbr = "%1kb/s CBR"; QByteArray quality = "%1 (~%2kb/s VBR)"; Encoder aac(QLatin1String("AAC"), QObject::tr("Advanced Audio " "Coding (AAC) is a patented lossy codec for digital audio.
    AAC " "generally achieves better sound quality than MP3 at similar bit rates. " "It is a reasonable choice for the iPod and some other portable music " "players."), QObject::tr("The bitrate is a measure of the quantity of data used to represent a second " "of the audio track.
    The AAC encoder used by Cantata supports a variable bitrate (VBR) setting, which means that the bitrate value " "fluctuates along the track based on the complexity of the audio content. " "More complex intervals of data are encoded with a higher bitrate than less " "complex ones; this approach yields overall better quality and a smaller file " "than having a constant bitrate throughout the track.
    " "For this reason, the bitrate measure in this slider is just an estimate " "of the average bitrate of " "the encoded track.
    " "150kb/s is a good choice for music listening on a portable player.
    " "Anything below 120kb/s might be unsatisfactory for music and anything above " "200kb/s is probably overkill."), QLatin1String("m4a"), command, QLatin1String("libfaac"), QLatin1String("-aq"), QObject::tr("Expected average bitrate for variable bitrate encoding"), QList() << Setting(QObject::tr(vbr).arg(25), 30) << Setting(QObject::tr(vbr).arg(50), 55) << Setting(QObject::tr(vbr).arg(70), 80) << Setting(QObject::tr(vbr).arg(90), 105) << Setting(QObject::tr(vbr).arg(120), 125) << Setting(QObject::tr(vbr).arg(150), 155) << Setting(QObject::tr(vbr).arg(170), 180) << Setting(QObject::tr(vbr).arg(180), 205) << Setting(QObject::tr(vbr).arg(190), 230) << Setting(QObject::tr(vbr).arg(200), 255) << Setting(QObject::tr(vbr).arg(210), 280), QObject::tr("Smaller file"), QObject::tr("Better sound quality"), 5); Encoder lame(QLatin1String("MP3"), QObject::tr("MPEG Audio Layer 3 (MP3) is " "a patented digital audio codec using a form of lossy data compression." "
    In spite of its shortcomings, it is a common format for consumer " "audio storage, and is widely supported on portable music players."), QObject::tr("The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
    The MP3 encoder used by Cantata supports " "a variable bitrate (VBR) " "setting, which means that the bitrate value fluctuates along the track " "based on the complexity of the audio content. More complex intervals of " "data are encoded with a higher bitrate than less complex ones; this " "approach yields overall better quality and a smaller file than having a " "constant bitrate throughout the track.
    " "For this reason, the bitrate measure in this slider is just an estimate " "of the average bitrate of the encoded track.
    " "160kb/s is a good choice for music listening on a portable player.
    " "Anything below 120kb/s might be unsatisfactory for music and anything above " "205kb/s is probably overkill."), QLatin1String("mp3"), command, QLatin1String("libmp3lame"), QLatin1String("-aq"), QObject::tr("Expected average bitrate for variable bitrate encoding"), QList() << Setting(QObject::tr(vbr).arg(80), 9) << Setting(QObject::tr(vbr).arg(100), 8) << Setting(QObject::tr(vbr).arg(120), 7) << Setting(QObject::tr(vbr).arg(140), 6) << Setting(QObject::tr(vbr).arg(160), 5) << Setting(QObject::tr(vbr).arg(175), 4) << Setting(QObject::tr(vbr).arg(190), 3) << Setting(QObject::tr(vbr).arg(205), 2) << Setting(QObject::tr(vbr).arg(220), 1) << Setting(QObject::tr(vbr).arg(240), 0), QObject::tr("Smaller file"), QObject::tr("Better sound quality"), 4); Encoder ogg(QObject::tr("Ogg Vorbis"), QObject::tr("Ogg Vorbis is an open " "and royalty-free audio codec for lossy audio compression.
    It produces " "smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an " "all-around excellent choice, especially for portable music players that " "support it."), QObject::tr("The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
    The Vorbis encoder used by Cantata supports " "a variable bitrate " "(VBR) setting, which means that the bitrate value fluctuates along the track " "based on the complexity of the audio content. More complex intervals of " "data are encoded with a higher bitrate than less complex ones; this " "approach yields overall better quality and a smaller file than having a " "constant bitrate throughout the track.
    " "The Vorbis encoder uses a quality rating between -1 and 10 to define " "a certain expected audio quality level. The bitrate measure in this slider is " "just a rough estimate (provided by Vorbis) of the average bitrate of the encoded " "track given a quality value. In fact, with newer and more efficient Vorbis versions the " "actual bitrate is even lower.
    " "5 is a good choice for music listening on a portable player.
    " "Anything below 3 might be unsatisfactory for music and anything above " "8 is probably overkill."), QLatin1String("ogg"), command, QLatin1String("libvorbis"), QLatin1String("-aq"), QObject::tr("Quality rating"), QList() << Setting(QObject::tr(quality).arg(-1).arg(45), -1) << Setting(QObject::tr(quality).arg(0).arg(64), 0) << Setting(QObject::tr(quality).arg(1).arg(80), 1) << Setting(QObject::tr(quality).arg(2).arg(96), 2) << Setting(QObject::tr(quality).arg(3).arg(112), 3) << Setting(QObject::tr(quality).arg(4).arg(128), 4) << Setting(QObject::tr(quality).arg(5).arg(160), 5) << Setting(QObject::tr(quality).arg(6).arg(192), 6) << Setting(QObject::tr(quality).arg(7).arg(224), 7) << Setting(QObject::tr(quality).arg(8).arg(256), 8) << Setting(QObject::tr(quality).arg(9).arg(320), 9) << Setting(QObject::tr(quality).arg(10).arg(500), 10), QObject::tr("Smaller file"), QObject::tr("Better sound quality"), 6); Encoder opus(QObject::tr("Opus"), QObject::tr("Opus is " "a patent-free digital audio codec using a form of lossy data compression."), QObject::tr("The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
    The Opus encoder used by Cantata supports " "a variable bitrate (VBR) " "setting, which means that the bitrate value fluctuates along the track " "based on the complexity of the audio content. More complex intervals of " "data are encoded with a higher bitrate than less complex ones; this " "approach yields overall better quality and a smaller file than having a " "constant bitrate throughout the track.
    " "For this reason, the bitrate measure in this slider is just an estimate " "of the average bitrate of the encoded track.
    " "128kb/s is a good choice for music listening on a portable player.
    " "Anything below 100kb/s might be unsatisfactory for music and anything above " "256kb/s is probably overkill."), QLatin1String("opus"), command, QLatin1String("libopus"), QLatin1String("-ab"), QObject::tr("Bitrate"), QList() << Setting(QObject::tr(vbr).arg(32), 32) << Setting(QObject::tr(vbr).arg(64), 64) << Setting(QObject::tr(vbr).arg(96), 96) << Setting(QObject::tr(vbr).arg(128), 128) << Setting(QObject::tr(vbr).arg(160), 160) << Setting(QObject::tr(vbr).arg(192), 192) << Setting(QObject::tr(vbr).arg(256), 256) << Setting(QObject::tr(vbr).arg(320), 320) << Setting(QObject::tr(vbr).arg(360), 360), QObject::tr("Smaller file"), QObject::tr("Better sound quality"), 4, 1000); if (!command.isEmpty()) { QList initial; initial.append(aac); initial.append( Encoder(QObject::tr("Apple Lossless"), QObject::tr("Apple Lossless " "(ALAC) is an audio codec for lossless compression of digital music.
    " "Recommended only for Apple music players and players that do not support " "FLAC."), QString(), QLatin1String("m4a"), command, QLatin1String("alac"), QString(), QString(), QList(), QString(), QString(), 0)); initial.append( Encoder(QObject::tr("FLAC"), QObject::tr("Free " "Lossless Audio Codec (FLAC) is an open and royalty-free codec for " "lossless compression of digital music.
    If you wish to store your music " "without compromising on audio quality, FLAC is an excellent choice."), QObject::tr("The " "compression level is an integer value between 0 and 8 that represents " "the tradeoff between file size and compression speed while encoding with FLAC.
    " "Setting the compression level to 0 yields the shortest compression time but " "generates a comparably big file.
    " "On the other hand, a compression level of 8 makes compression quite slow but " "produces the smallest file.
    " "Note that since FLAC is by definition a lossless codec, the audio quality " "of the output is exactly the same regardless of the compression level.
    " "Also, levels above 5 dramatically increase compression time but create an only " "slightly smaller file, and are not recommended."), QLatin1String("flac"), command, QLatin1String("flac"), QLatin1String("-compression_level"), QObject::tr("Compression level"), QList() << Setting(QString::number(0), 0) << Setting(QString::number(1), 1) << Setting(QString::number(2), 2) << Setting(QString::number(3), 3) << Setting(QString::number(4), 4) << Setting(QString::number(5), 5) << Setting(QString::number(6), 6) << Setting(QString::number(7), 7) << Setting(QString::number(8), 8), QObject::tr("Faster compression"), QObject::tr("Smaller file"), 5)); initial.append(lame); initial.append(ogg); initial.append(opus); initial.append( Encoder(QObject::tr("Windows Media Audio"), QObject::tr("Windows Media " "Audio (WMA) is a proprietary codec developed by Microsoft for lossy " "audio compression.
    Recommended only for portable music players that " "do not support Ogg Vorbis."), QObject::tr("The bitrate is a measure of the quantity of data used to represent a " "second of the audio track.
    Due to the limitations of the proprietary WMA " "format and the difficulty of reverse-engineering a proprietary encoder, the " "WMA encoder used by Cantata sets a constant bitrate (CBR) setting.
    " "For this reason, the bitrate measure in this slider is a pretty accurate estimate " "of the bitrate of the encoded track.
    " "136kb/s is a good choice for music listening on a portable player.
    " "Anything below 112kb/s might be unsatisfactory for music and anything above " "182kb/s is probably overkill."), QLatin1String("wma"), command, QLatin1String("wmav2"), QLatin1String("-ab"), QObject::tr("Bitrate"), QList() << Setting(QObject::tr(cbr).arg(64), 65) << Setting(QObject::tr(cbr).arg(80), 75) << Setting(QObject::tr(cbr).arg(96), 88) << Setting(QObject::tr(cbr).arg(112), 106) << Setting(QObject::tr(cbr).arg(136), 133) << Setting(QObject::tr(cbr).arg(182), 180) << Setting(QObject::tr(cbr).arg(275), 271) << Setting(QObject::tr(cbr).arg(550), 545), QObject::tr("Smaller file"), QObject::tr("Better sound quality"), 4, 1000)); QProcess proc; proc.start(command, QStringList() << "-codecs"); if (proc.waitForStarted()) { proc.waitForFinished(); } if (0!=proc.exitCode()) { return; } QString output=proc.readAllStandardOutput(); if (output.simplified().isEmpty()) { return; } QStringList lines=output.split('\n', QString::SkipEmptyParts); foreach (const QString &line, lines) { int pos=line.indexOf(QRegExp(QLatin1String("[\\. D]EA"))); if (0==pos || 1==pos) { QList::Iterator it(initial.begin()); QList::Iterator end(initial.end()); for (; it!=end; ++it) { if (line.contains((*it).codec)) { installedEncoders.append((*it)); initial.erase(it); break; } } if (initial.isEmpty()) { break; } } } } insertCodec(QLatin1String("faac"), QLatin1String("-q"), QLatin1String("-o"), aac); insertCodec(QLatin1String("lame"), QLatin1String("-V"), QString(), lame); insertCodec(QLatin1String("oggenc"), QLatin1String("-q"), QLatin1String("-o"), ogg); insertCodec(QLatin1String("opusenc"), QLatin1String("--bitrate"), QString(), opus); qSort(installedEncoders); } } bool Encoder::operator<(const Encoder &o) const { return name.compare(o.name)<0; } QString Encoder::changeExtension(const QString &file) { return Utils::changeExtension(file, extension); } bool Encoder::isDifferent(const QString &file) { return file.toLower()!=changeExtension(file).toLower(); } QStringList Encoder::params(int value, const QString &in, const QString &out) const { QStringList p; if (transcoder) { p << app << QLatin1String("-i") << in << QLatin1String("-threads") << QLatin1String("0") << QLatin1String(usingAvconv ? "-c:a" : "-acodec") << codec; } else { p << app; } if (!param.isEmpty() && values.size()>1) { bool increase=values.at(0).valuevalue) || (!increase && s.value getAvailable() { init(); return installedEncoders; } Encoder getEncoder(const QString &codec) { init(); foreach (const Encoder &encoder, installedEncoders) { if (encoder.codec==codec) { return encoder; } } return Encoder(); } } cantata-2.2.0/devices/encoders.h000066400000000000000000000053561316350454000165350ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ENCODERS_H #define ENCODERS_H #include #include #include namespace Encoders { struct Setting { Setting(const QString &d, int v) : descr(d) , value(v) { } QString descr; int value; }; struct Encoder { Encoder() : defaultValueIndex(0), ffmpegValueMultiplier(0), transcoder(false) { } Encoder(const QString &n, const QString &d, const QString &t, const QString &e, const QString &a, const QString &c, const QString &f, const QString &v, const QList &vs, const QString &l, const QString &h, int def, int mult=1) : name(n) , description(d) , tooltip(t) , extension(e) , app(a) , codec(c) , param(f) , valueLabel(v) , values(vs) , low(l) , high(h) , defaultValueIndex(def) , ffmpegValueMultiplier(mult) , transcoder(true) { } bool isNull() { return name.isEmpty(); } bool operator==(const Encoder &o) const { return name==o.name && codec==o.codec; } bool operator<(const Encoder &o) const; QString changeExtension(const QString &file); bool isDifferent(const QString &file); QStringList params(int value, const QString &in, const QString &out) const; QString name; QString description; QString tooltip; QString extension; QString app; QString codec; QString param; QString valueLabel; QList values; QString low; QString high; QString outputParam; int defaultValueIndex; int ffmpegValueMultiplier; bool transcoder; }; extern QList getAvailable(); extern Encoder getEncoder(const QString &codec); }; #endif cantata-2.2.0/devices/extractjob.cpp000066400000000000000000000124751316350454000174330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, */ #include "extractjob.h" #include "device.h" #include "support/utils.h" #include "tags/tags.h" #include "cdparanoia.h" #include "gui/covers.h" #include "mpd-interface/mpdconnection.h" #include "gui/settings.h" #include #include #include const int ExtractJob::constWavHeaderSize=44; // ffmpeg uses 46 byte header? static void insertSize(unsigned char *data, qint32 size) { data[0]=(size&0x000000ff); data[1]=(size&0x0000ff00)>>8; data[2]=(size&0x00ff0000)>>16; data[3]=(size&0xff000000)>>24; } void ExtractJob::writeWavHeader(QIODevice &dev, qint32 size) { unsigned char riffHeader[] = { 0x52, 0x49, 0x46, 0x46, // 0 "RIFF" 0x00, 0x00, 0x00, 0x00, // 4 wavSize 0x57, 0x41, 0x56, 0x45, // 8 "WAVE" 0x66, 0x6d, 0x74, 0x20, // 12 "fmt " 0x10, 0x00, 0x00, 0x00, // 16 Size of WAVE section chunk (ffmpeg has 12 here???) 0x01, 0x00, 0x02, 0x00, // 20 WAVE type format / Number of channels 0x44, 0xac, 0x00, 0x00, // 24 Samples per second 0x10, 0xb1, 0x02, 0x00, // 28 Bytes per second 0x04, 0x00, 0x10, 0x00, // 32 Block alignment / Bits per sample 0x64, 0x61, 0x74, 0x61, // 36 "data" (ffmpeg preceeds this with two 0 bytes) 0x00, 0x00, 0x00, 0x00 // 40 byteCount }; if (0!=size) { insertSize(&riffHeader[4], (size+constWavHeaderSize)-8); insertSize(&riffHeader[40], size); } dev.write((char*)riffHeader, constWavHeaderSize); } ExtractJob::ExtractJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const Song &s, const QString &cover) : encoder(enc) , value(val) , srcFile(src) , destFile(dest) , song(s) , coverFile(cover) , copiedCover(false) { } ExtractJob::~ExtractJob() { } void ExtractJob::run() { if (stopRequested) { emit result(Device::Cancelled); } else { QStringList encParams=encoder.params(value, encoder.transcoder ? "pipe:" : "-", destFile); CdParanoia cdparanoia(srcFile, Settings::self()->paranoiaFull(), Settings::self()->paranoiaNeverSkip()); if (!cdparanoia) { emit result(Device::FailedToLockDevice); return; } QProcess process; QString cmd=encParams.takeFirst(); process.start(cmd, encParams, QIODevice::WriteOnly); process.waitForStarted(); if (stopRequested) { emit result(Device::Cancelled); process.close(); return; } int firstSector = cdparanoia.firstSectorOfTrack(song.id); int lastSector = cdparanoia.lastSectorOfTrack(song.id); int total=lastSector-firstSector; int count=0; cdparanoia.seek(firstSector, SEEK_SET); writeWavHeader(process); while ((firstSector+count) <= lastSector) { qint16 *buf = cdparanoia.read(); if (!buf) { emit result(Device::Failed); process.close(); return; } if (stopRequested) { emit result(Device::Cancelled); process.close(); return; } char *buffer=(char *)buf; qint64 writePos=0; do { qint64 bytesWritten = process.write(&buffer[writePos], CD_FRAMESIZE_RAW - writePos); if (stopRequested) { emit result(Device::Cancelled); process.close(); QFile::remove(destFile); return; } if (-1==bytesWritten) { emit result(Device::WriteFailed); process.close(); QFile::remove(destFile); return; } writePos+=bytesWritten; } while (writePosgetDetails().coverName; if (mpdCover.isEmpty()) { mpdCover=Covers::constFileName; } copiedCover=Covers::copyImage(Utils::getDir(coverFile), Utils::getDir(destFile), Utils::getFile(coverFile), mpdCover+coverFile.mid(coverFile.length()-4), 0); } emit result(Device::Ok); } } cantata-2.2.0/devices/extractjob.h000066400000000000000000000030071316350454000170670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef EXTRACT_JOB_H #define EXTRACT_JOB_H #include "filejob.h" #include "encoders.h" class ExtractJob : public FileJob { Q_OBJECT public: static const int constWavHeaderSize; static void writeWavHeader(QIODevice &dev, qint32 size=0); explicit ExtractJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const Song &s, const QString &cover); virtual ~ExtractJob(); bool coverCopied() const { return copiedCover; } private: void run(); private: Encoders::Encoder encoder; int value; QString srcFile; QString destFile; Song song; QString coverFile; bool copiedCover; }; #endif //TRANSCODING_JOB_H cantata-2.2.0/devices/filejob.cpp000066400000000000000000000150731316350454000166750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "filejob.h" #include "support/utils.h" #include "device.h" #include "context/songview.h" #include "gui/covers.h" #include "support/thread.h" #include "support/globalstatic.h" #include #include #include #include GLOBAL_STATIC(FileThread, instance) FileThread::FileThread() : thread(0) { } FileThread::~FileThread() { // stop(); } void FileThread::addJob(FileJob *job) { if (!thread) { thread=new Thread(metaObject()->className()); thread->start(); } job->moveToThread(thread); } void FileThread::stop() { if (thread) { thread->stop(); thread=0; } } FileJob::FileJob() : stopRequested(false) , progressPercent(0) { FileThread::self()->addJob(this); // Cant call deleteLater here, as in the device's xxResult() slots "sender()" returns // null. Therefore, xxResult() slots need to call finished() //connect(this, SIGNAL(result(int)), SLOT(deleteLater())); } void FileJob::start() { QTimer::singleShot(0, this, SLOT(run())); } void FileJob::setPercent(int pc) { if (pc!=progressPercent) { progressPercent=pc; emit percent(progressPercent); } } CopyJob::~CopyJob() { if (temp) { temp->remove(); delete temp; } } static const int constChunkSize=32*1024; QString CopyJob::updateTagsLocal() { // First, if we are going to update tags (e.g. to fix various artists), then check if we want to do that locally, before // copying to device. For UMS devices, we just modify on device, but for remote (e.g. sshfs) then it'd be better to do locally :-) if (copyOpts&OptsFixLocal && (copyOpts&OptsApplyVaFix || copyOpts&OptsUnApplyVaFix || Device::constEmbedCover==deviceOpts.coverName)) { song.file=srcFile; temp=Device::copySongToTemp(song); if (!temp) { emit result(Device::FailedToUpdateTags); return QString(); } if ((copyOpts&OptsApplyVaFix || copyOpts&OptsUnApplyVaFix) && !Device::fixVariousArtists(temp->fileName(), song, copyOpts&OptsApplyVaFix)) { emit result(Device::FailedToUpdateTags); return QString(); } if (Device::constEmbedCover==deviceOpts.coverName) { Device::embedCover(temp->fileName(), song, deviceOpts.coverMaxSize); } return temp->fileName(); } return srcFile; } void CopyJob::updateTagsDest() { if (!stopRequested && !(copyOpts&OptsFixLocal) && (copyOpts&OptsApplyVaFix || copyOpts&OptsUnApplyVaFix || Device::constEmbedCover==deviceOpts.coverName)) { if (copyOpts&OptsApplyVaFix || copyOpts&OptsUnApplyVaFix) { Device::fixVariousArtists(destFile, song, copyOpts&OptsApplyVaFix); } if (!stopRequested && Device::constEmbedCover==deviceOpts.coverName) { Device::embedCover(destFile, song, deviceOpts.coverMaxSize); } } } void CopyJob::copyCover(const QString &origSrcFile) { if (!stopRequested && !deviceOpts.coverName.isEmpty() && Device::constNoCover!=deviceOpts.coverName && Device::constEmbedCover!=deviceOpts.coverName) { song.file=destFile; copiedCover=Covers::copyCover(song, Utils::getDir(origSrcFile), Utils::getDir(destFile), deviceOpts.coverName, deviceOpts.coverMaxSize); } } void CopyJob::run() { QString origSrcFile(srcFile); srcFile=updateTagsLocal(); if (srcFile.isEmpty()) { return; } if (stopRequested) { emit result(Device::Cancelled); return; } QFile src(srcFile); if (!src.open(QIODevice::ReadOnly)) { emit result(Device::ReadFailed); return; } QFile dest(destFile); if (!dest.open(QIODevice::WriteOnly)) { emit result(Device::WriteFailed); return; } char buffer[constChunkSize]; qint64 totalBytes = src.size(); qint64 readPos = 0; qint64 bytesRead = 0; qint64 adjustTotal = Device::constNoCover!=deviceOpts.coverName ? 16384 : 0; do { if (stopRequested) { emit result(Device::Cancelled); return; } bytesRead = src.read(buffer, constChunkSize); readPos+=bytesRead; if (bytesRead<0) { emit result(Device::ReadFailed); return; } if (stopRequested) { emit result(Device::Cancelled); return; } qint64 writePos=0; do { qint64 bytesWritten = dest.write(&buffer[writePos], bytesRead - writePos); if (stopRequested) { emit result(Device::Cancelled); return; } if (-1==bytesWritten) { emit result(Device::WriteFailed); return; } writePos+=bytesWritten; } while (writePos * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FILE_JOB_H #define FILE_JOB_H #include #include #include "mpd-interface/song.h" #include "deviceoptions.h" class QTemporaryFile; class Thread; class FileJob; class FileThread : public QObject { Q_OBJECT public: static FileThread * self(); FileThread(); ~FileThread(); void addJob(FileJob *job); void stop(); private: Thread *thread; }; class FileJob : public QObject { Q_OBJECT public: static void finished(QObject *obj) { if (obj) { obj->deleteLater(); } } FileJob(); virtual ~FileJob() { } void setPercent(int pc); bool wasStarted() const { return 0!=progressPercent && 100!=progressPercent; } Q_SIGNALS: void percent(int pc); void result(int status); public: virtual void stop() { stopRequested=true; } virtual void start(); protected Q_SLOTS: virtual void run()=0; protected: bool stopRequested; int progressPercent; }; class CopyJob : public FileJob { Q_OBJECT public: enum Options { OptsNone = 0x00, OptsApplyVaFix = 0x01, OptsUnApplyVaFix = 0x02, OptsFixLocal = 0x04 // Apply any fixes to a local temp file before sending... }; CopyJob(const QString &src, const QString &dest, const DeviceOptions &d, int co, const Song &s) : srcFile(src) , destFile(dest) , deviceOpts(d) , copyOpts(co) , song(s) , temp(0) , copiedCover(false) { } virtual ~CopyJob(); bool coverCopied() const { return copiedCover; } protected: QString updateTagsLocal(); void updateTagsDest(); void copyCover(const QString &origSrcFile); private: virtual void run(); protected: QString srcFile; QString destFile; DeviceOptions deviceOpts; int copyOpts; Song song; QTemporaryFile *temp; bool copiedCover; }; class DeleteJob : public FileJob { public: DeleteJob(const QString &file, bool rl=false) : fileName(file) , remLyrics(rl) { } private: void run(); private: QString fileName; bool remLyrics; }; class CleanJob : public FileJob { public: CleanJob(const QSet &d, const QString &b, const QString &cf) : dirs(d) , base(b) , coverFile(cf) { } private: void run(); private: QSet dirs; QString base; QString coverFile; }; #endif cantata-2.2.0/devices/filenameschemedialog.cpp000066400000000000000000000147121316350454000214070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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 detailexampleSong. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "filenameschemedialog.h" #include "mpd-interface/song.h" #include "support/messagebox.h" static const char * constVariableProperty="cantata-var"; FilenameSchemeDialog::FilenameSchemeDialog(QWidget *parent) : Dialog(parent) { setButtons(Ok|Cancel); setCaption(tr("Filename Scheme")); setWindowModality(Qt::WindowModal); QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); connect(pattern, SIGNAL(textChanged(const QString &)), this, SLOT(enableOkButton())); connect(pattern, SIGNAL(textChanged(const QString &)), this, SLOT(updateExample())); connect(help, SIGNAL(leftClickedUrl()), this, SLOT(showHelp())); connect(albumArtist, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(composer, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(albumTitle, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(trackArtist, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(trackTitle, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(trackArtistAndTitle, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(trackNo, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(cdNo, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(genre, SIGNAL(clicked()), this, SLOT(insertVariable())); connect(year, SIGNAL(clicked()), this, SLOT(insertVariable())); albumArtist->setProperty(constVariableProperty, DeviceOptions::constAlbumArtist); albumTitle->setProperty(constVariableProperty, DeviceOptions::constAlbumTitle); composer->setProperty(constVariableProperty, DeviceOptions::constComposer); trackArtist->setProperty(constVariableProperty, DeviceOptions::constTrackArtist); trackTitle->setProperty(constVariableProperty, DeviceOptions::constTrackTitle); trackArtistAndTitle->setProperty(constVariableProperty, DeviceOptions::constTrackArtistAndTitle); trackNo->setProperty(constVariableProperty, DeviceOptions::constTrackNumber); cdNo->setProperty(constVariableProperty, DeviceOptions::constCdNumber); year->setProperty(constVariableProperty, DeviceOptions::constYear); genre->setProperty(constVariableProperty, DeviceOptions::constGenre); exampleSong.albumartist=tr("Various Artists", "Example album artist"); exampleSong.artist=tr("Wibble", "Example artist"); exampleSong.setComposer(tr("Vivaldi", "Example composer")); exampleSong.album=tr("Now 5001", "Example album"); exampleSong.title=tr("Wobble", "Example song name"); exampleSong.genres[0]=tr("Dance", "Example genre"); exampleSong.track=1; exampleSong.disc=2; exampleSong.year=2001; exampleSong.file="wibble.mp3"; adjustSize(); setMaximumHeight(height()); } void FilenameSchemeDialog::show(const DeviceOptions &opts) { origOpts=opts; pattern->setText(opts.scheme); example->setText(origOpts.createFilename(exampleSong)); Dialog::show(); enableButtonOk(false); } void FilenameSchemeDialog::slotButtonClicked(int button) { switch (button) { case Ok: emit scheme(pattern->text().trimmed()); break; case Cancel: reject(); break; default: break; } if (Ok==button) { accept(); } Dialog::slotButtonClicked(button); } static QString stripAccelerator(QString str) { return str.replace("&&", "____").replace("&", "").replace("____", "&"); } static QString tableEntry(const QAbstractButton *widget) { return QLatin1String("")+widget->property(constVariableProperty).toString()+ QLatin1String("")+stripAccelerator(widget->text())+ QLatin1String("")+widget->toolTip()+ QLatin1String(""); } void FilenameSchemeDialog::showHelp() { MessageBox::information(this, tr("The following variables will be replaced with their corresponding meaning for each track name.")+ QLatin1String("

    ")+ #ifdef Q_OS_MAC QLatin1String("")+ #endif QLatin1String("")+ tr("")+ tableEntry(albumArtist)+tableEntry(albumTitle)+tableEntry(composer)+tableEntry(trackArtist)+ tableEntry(trackTitle)+tableEntry(trackArtistAndTitle)+tableEntry(trackNo)+tableEntry(cdNo)+ tableEntry(year)+tableEntry(genre)+ QLatin1String("
    VariableButtonDescription
    ") #ifdef Q_OS_MAC +QLatin1String("
    ") #endif ); } void FilenameSchemeDialog::enableOkButton() { QString scheme=pattern->text().trimmed(); enableButtonOk(!scheme.isEmpty() && scheme!=origOpts.scheme); } void FilenameSchemeDialog::insertVariable() { QString text(pattern->text()); QString insert=sender()->property(constVariableProperty).toString(); int pos=pattern->cursorPosition(); text.insert(pos, insert); pattern->setText(text); pattern->setCursorPosition(pos+insert.length()); pattern->setFocus(); updateExample(); } void FilenameSchemeDialog::updateExample() { QString saveScheme=origOpts.scheme; origOpts.scheme=pattern->text(); QString exampleStr=origOpts.createFilename(exampleSong); if (!exampleStr.endsWith(QLatin1String(".mp3"))) { exampleStr+=QLatin1String(".mp3"); } example->setText(exampleStr); origOpts.scheme=saveScheme; } cantata-2.2.0/devices/filenameschemedialog.h000066400000000000000000000027431316350454000210550ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FILENAMESCHEMEDIALOG_H #define FILENAMESCHEMEDIALOG_H #include "ui_filenameschemedialog.h" #include "support/dialog.h" #include "mpd-interface/song.h" #include "deviceoptions.h" class FilenameSchemeDialog : public Dialog, Ui::FilenameSchemeDialog { Q_OBJECT public: FilenameSchemeDialog(QWidget *parent); void show(const DeviceOptions &opts); Q_SIGNALS: void scheme(const QString &text); private Q_SLOTS: void showHelp(); void enableOkButton(); void insertVariable(); void updateExample(); private: void slotButtonClicked(int button); private: DeviceOptions origOpts; Song exampleSong; }; #endif cantata-2.2.0/devices/filenameschemedialog.ui000066400000000000000000000136121316350454000212400ustar00rootroot00000000000000 FilenameSchemeDialog 0 0 504 180 0 0 0 0 16777215 21 Example: true Qt::Horizontal 40 20 About filename schemes Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Album Artist The name of the album. Album Title The composer. Composer The artist of each track. Track Artist The track title (without <i>Track Artist</i>). Track Title 0 0 16777215 16777215 The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Track Title (+Artist) The track number. Track # The album number of a multi-album album. Often compilations consist of several albums. CD # The year of the album's release. Year The genre of the album. Genre Qt::Vertical 20 0 UrlLabel QLabel
    support/urllabel.h
    LineEdit QLineEdit
    support/lineedit.h
    cantata-2.2.0/devices/freespaceinfo.cpp000066400000000000000000000041421316350454000200670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "freespaceinfo.h" #include "support/utils.h" #if defined(Q_OS_UNIX) #include #elif defined Q_OS_WIN #include "windows.h" #endif FreeSpaceInfo::FreeSpaceInfo(const QString &path) : location(path) , isDirty(true) , totalSize(0) , usedSpace(0) { } void FreeSpaceInfo::setPath(const QString &path) { if (location!=path) { location=Utils::fixPath(path); isDirty=true; } } qulonglong FreeSpaceInfo::size() { if (isDirty) { update(); } return totalSize; } qulonglong FreeSpaceInfo::used() { if (isDirty) { update(); } return usedSpace; } void FreeSpaceInfo::update() { #if defined(Q_OS_UNIX) struct statvfs fs_info; if (0==statvfs(location.toLocal8Bit().constData(), &fs_info)) { totalSize=quint64(fs_info.f_blocks) * quint64(fs_info.f_bsize); usedSpace=totalSize-(quint64(fs_info.f_bavail) * quint64(fs_info.f_bsize)); } #elif defined(Q_OS_WIN) _ULARGE_INTEGER totalRet; _ULARGE_INTEGER freeRet; if (0!=GetDiskFreeSpaceEx(QDir::toNativeSeparators(location).toLocal8Bit().constData(), &freeRet, &totalRet, NULL)) { totalSize=totalRet.QuadPart; usedSpace=totalRet.QuadPart-freeRet.QuadPart; } #endif isDirty=false; } cantata-2.2.0/devices/freespaceinfo.h000066400000000000000000000024341316350454000175360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FREESPACEINFO_H #define FREESPACEINFO_H #include class FreeSpaceInfo { public: FreeSpaceInfo(const QString &path=QString()); void setPath(const QString &path); void setDirty() { isDirty=true; } quint64 size(); quint64 used(); const QString & path() const { return location; } private: void update(); private: QString location; bool isDirty; quint64 totalSize; quint64 usedSpace; }; #endif cantata-2.2.0/devices/fsdevice.cpp000066400000000000000000000604031316350454000170500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "umsdevice.h" #include "tags/tags.h" #include "models/musiclibraryitemsong.h" #include "models/musiclibraryitemalbum.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemroot.h" #include "models/mpdlibrarymodel.h" #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "support/utils.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdconnection.h" #include "encoders.h" #include "transcodingjob.h" #include "actiondialog.h" #include "gui/covers.h" #include "support/thread.h" #include #include #include #include #include const QLatin1String FsDevice::constCantataCacheFile("/.cache"); const QLatin1String FsDevice::constCantataSettingsFile("/.cantata"); const QLatin1String FsDevice::constMusicFilenameSchemeKey("music_filenamescheme"); const QLatin1String FsDevice::constVfatSafeKey("vfat_safe"); const QLatin1String FsDevice::constAsciiOnlyKey("ascii_only"); const QLatin1String FsDevice::constIgnoreTheKey("ignore_the"); const QLatin1String FsDevice::constReplaceSpacesKey("replace_spaces"); const QLatin1String FsDevice::constCoverFileNameKey("cover_filename"); // Cantata extension! const QLatin1String FsDevice::constCoverMaxSizeKey("cover_maxsize"); // Cantata extension! const QLatin1String FsDevice::constVariousArtistsFixKey("fix_various_artists"); // Cantata extension! const QLatin1String FsDevice::constTranscoderKey("transcoder"); // Cantata extension! const QLatin1String FsDevice::constUseCacheKey("use_cache"); // Cantata extension! const QLatin1String FsDevice::constDefCoverFileName("cover.jpg"); const QLatin1String FsDevice::constAutoScanKey("auto_scan"); // Cantata extension! MusicScanner::MusicScanner() : QObject(0) , stopRequested(false) , count(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } MusicScanner::~MusicScanner() { stop(); } void MusicScanner::scan(const QString &folder, const QString &cacheFile, bool readCache, const QSet &existingSongs) { if (!cacheFile.isEmpty() && readCache) { MusicLibraryItemRoot *lib=new MusicLibraryItemRoot; readProgress(0.0); if (lib->fromXML(cacheFile, folder)) { if (!stopRequested) { emit libraryUpdated(lib); } else { delete lib; } return; } else { delete lib; } } if (stopRequested) { return; } count=0; MusicLibraryItemRoot *library = new MusicLibraryItemRoot; QString topLevel=Utils::fixPath(QDir(folder).absolutePath()); QSet existing=existingSongs; timer.start(); scanFolder(library, topLevel, topLevel, existing, 0); if (!stopRequested) { if (!cacheFile.isEmpty()) { writeProgress(0.0); library->toXML(cacheFile, this); } emit libraryUpdated(library); } else { delete library; } } void MusicScanner::saveCache(const QString &cache, MusicLibraryItemRoot *lib) { writeProgress(0.0); lib->toXML(cache, this); emit cacheSaved(); } void MusicScanner::stop() { stopRequested=true; thread->stop(); thread=0; } void MusicScanner::scanFolder(MusicLibraryItemRoot *library, const QString &topLevel, const QString &f, QSet &existing, int level) { if (stopRequested) { return; } if (level<4) { QDir d(f); QFileInfoList entries=d.entryInfoList(QDir::Files|QDir::NoSymLinks|QDir::Dirs|QDir::NoDotAndDotDot); MusicLibraryItemArtist *artistItem = 0; MusicLibraryItemAlbum *albumItem = 0; foreach (const QFileInfo &info, entries) { if (stopRequested) { return; } if (info.isDir()) { scanFolder(library, topLevel, info.absoluteFilePath(), existing, level+1); } else if(info.isReadable()) { Song song; QString fname=info.absoluteFilePath().mid(topLevel.length()); if (fname.endsWith(".jpg", Qt::CaseInsensitive) || fname.endsWith(".png", Qt::CaseInsensitive) || fname.endsWith(".lyrics", Qt::CaseInsensitive) || fname.endsWith(".pamp", Qt::CaseInsensitive)) { continue; } song.file=fname; QSet::iterator it=existing.find(song); if (existing.end()==it) { song=Tags::read(info.absoluteFilePath()); song.file=fname; } else { song=*it; existing.erase(it); } if (song.isEmpty()) { continue; } count++; if (timer.elapsed()>=1500 || 0==(count%5)) { timer.restart(); emit songCount(count); } song.fillEmptyFields(); song.populateSorts(); song.size=info.size(); if (!artistItem || song.artistOrComposer()!=artistItem->data()) { artistItem = library->artist(song); } if (!albumItem || albumItem->parentItem()!=artistItem || song.albumName()!=albumItem->data()) { albumItem = artistItem->album(song); } albumItem->append(new MusicLibraryItemSong(song, albumItem)); } } } } void MusicScanner::readProgress(double pc) { emit readingCache(pc); } void MusicScanner::writeProgress(double pc) { emit savingCache(pc); } bool FsDevice::readOpts(const QString &fileName, DeviceOptions &opts, bool readAll) { QFile file(fileName); opts=DeviceOptions(constDefCoverFileName); if (file.open(QIODevice::ReadOnly|QIODevice::Text)) { QTextStream in(&file); while (!in.atEnd()) { QString line = in.readLine(); if (line.startsWith(constCoverFileNameKey+"=")) { opts.coverName=line.section('=', 1, 1); } if (line.startsWith(constCoverMaxSizeKey+"=")) { opts.coverMaxSize=line.section('=', 1, 1).toUInt(); opts.checkCoverSize(); } else if(line.startsWith(constVariousArtistsFixKey+"=")) { opts.fixVariousArtists=QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constTranscoderKey+"=")) { QStringList parts=line.section('=', 1, 1).split(','); if (parts.size()>=3) { opts.transcoderCodec=parts.at(0); opts.transcoderValue=parts.at(1).toInt(); opts.transcoderWhenDifferent=QLatin1String("true")==parts.at(2); if (parts.size()>=4) { opts.transcoderWhenSourceIsLosssless=QLatin1String("true")==parts.at(3); } } } else if (line.startsWith(constUseCacheKey+"=")) { opts.useCache=QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constAutoScanKey+"=")) { opts.autoScan=QLatin1String("true")==line.section('=', 1, 1); } else if (readAll) { // For UMS these are stored in .is_audio_player - for Amarok compatability! if (line.startsWith(constMusicFilenameSchemeKey+"=")) { QString scheme = line.section('=', 1, 1); //protect against empty setting. if (!scheme.isEmpty() ) { opts.scheme = scheme; } } else if (line.startsWith(constVfatSafeKey+"=")) { opts.vfatSafe = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constAsciiOnlyKey+"=")) { opts.asciiOnly = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constIgnoreTheKey+"=")) { opts.ignoreThe = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constReplaceSpacesKey+"=")) { opts.replaceSpaces = QLatin1String("true")==line.section('=', 1, 1); } } } return true; } return false; } static inline QString toString(bool b) { return b ? QLatin1String("true") : QLatin1String("false"); } void FsDevice::writeOpts(const QString &fileName, const DeviceOptions &opts, bool writeAll) { DeviceOptions def(constDefCoverFileName); // If we are just using the defaults, then mayas wel lremove the file! if ( (writeAll && opts==def) || (!writeAll && opts.coverName==constDefCoverFileName && 0==opts.coverMaxSize && opts.fixVariousArtists!=def.fixVariousArtists && opts.transcoderCodec.isEmpty() && opts.useCache==def.useCache && opts.autoScan!=def.autoScan)) { if (QFile::exists(fileName)) { QFile::remove(fileName); } return; } QFile file(fileName); if (file.open(QIODevice::WriteOnly|QIODevice::Text)) { QTextStream out(&file); if (writeAll) { if (opts.scheme!=def.scheme) { out << constMusicFilenameSchemeKey << '=' << opts.scheme << '\n'; } if (opts.scheme!=def.scheme) { out << constVfatSafeKey << '=' << toString(opts.vfatSafe) << '\n'; } if (opts.asciiOnly!=def.asciiOnly) { out << constAsciiOnlyKey << '=' << toString(opts.asciiOnly) << '\n'; } if (opts.ignoreThe!=def.ignoreThe) { out << constIgnoreTheKey << '=' << toString(opts.ignoreThe) << '\n'; } if (opts.replaceSpaces!=def.replaceSpaces) { out << constReplaceSpacesKey << '=' << toString(opts.replaceSpaces) << '\n'; } } // NOTE: If any options are added/changed - take care of the "if ( (writeAll..." block above!!! if (opts.coverName!=constDefCoverFileName) { out << constCoverFileNameKey << '=' << opts.coverName << '\n'; } if (0!=opts.coverMaxSize) { out << constCoverMaxSizeKey << '=' << opts.coverMaxSize << '\n'; } if (opts.fixVariousArtists!=def.fixVariousArtists) { out << constVariousArtistsFixKey << '=' << toString(opts.fixVariousArtists) << '\n'; } if (!opts.transcoderCodec.isEmpty()) { out << constTranscoderKey << '=' << opts.transcoderCodec << ',' << opts.transcoderValue << ',' << toString(opts.transcoderWhenDifferent) << ',' << toString(opts.transcoderWhenSourceIsLosssless) << '\n'; } if (opts.useCache!=def.useCache) { out << constUseCacheKey << '=' << toString(opts.useCache) << '\n'; } if (opts.autoScan!=def.autoScan) { out << constAutoScanKey << '=' << toString(opts.autoScan) << '\n'; } } } FsDevice::FsDevice(MusicLibraryModel *m, Solid::Device &dev) : Device(m, dev) , state(Idle) , scanned(false) , cacheProgress(-1) , scanner(0) { } FsDevice::FsDevice(MusicLibraryModel *m, const QString &name, const QString &id) : Device(m, name, id) , state(Idle) , scanned(false) , cacheProgress(-1) , scanner(0) { } FsDevice::~FsDevice() { stopScanner(); } void FsDevice::rescan(bool full) { spaceInfo.setDirty(); // If this is the first scan (scanned=false) and we are set to use cache, attempt to load that before scanning if (isIdle()) { if (full) { removeCache(); clear(); } startScanner(full); scanned=true; } } void FsDevice::stop() { if (0!=scanner) { stopScanner(); } } void FsDevice::addSong(const Song &s, bool overwrite, bool copyCover) { jobAbortRequested=false; if (!isConnected()) { emit actionStatus(NotConnected); return; } needToFixVa=opts.fixVariousArtists && s.isVariousArtists(); if (!overwrite) { Song check=s; if (needToFixVa) { Device::fixVariousArtists(QString(), check, true); } if (songExists(check)) { emit actionStatus(SongExists); return; } } if (!QFile::exists(s.file)) { emit actionStatus(SourceFileDoesNotExist); return; } currentDestFile=audioFolder+opts.createFilename(s); Encoders::Encoder encoder; if (!opts.transcoderCodec.isEmpty()) { encoder=Encoders::getEncoder(opts.transcoderCodec); if (encoder.codec.isEmpty()) { emit actionStatus(CodecNotAvailable); return; } currentDestFile=encoder.changeExtension(currentDestFile); } if (!overwrite && QFile::exists(currentDestFile)) { emit actionStatus(FileExists); return; } QDir dir(Utils::getDir(currentDestFile)); if(!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), QString())) { emit actionStatus(DirCreationFaild); return; } currentSong=s; transcoding = !opts.transcoderCodec.isEmpty() && (!opts.transcoderWhenDifferent || encoder.isDifferent(s.file)) && (!opts.transcoderWhenSourceIsLosssless || Device::isLossless(s.file)); if (transcoding) { TranscodingJob *job=new TranscodingJob(encoder, opts.transcoderValue, s.file, currentDestFile, copyCover ? opts : DeviceOptions(Device::constNoCover), (needToFixVa ? CopyJob::OptsApplyVaFix : CopyJob::OptsNone)| (Device::RemoteFs==devType() ? CopyJob::OptsFixLocal : CopyJob::OptsNone), currentSong); connect(job, SIGNAL(result(int)), SLOT(addSongResult(int))); connect(job, SIGNAL(percent(int)), SLOT(percent(int))); job->start(); } else { CopyJob *job=new CopyJob(s.file, currentDestFile, copyCover ? opts : DeviceOptions(Device::constNoCover), (needToFixVa ? CopyJob::OptsApplyVaFix : CopyJob::OptsNone)|(Device::RemoteFs==devType() ? CopyJob::OptsFixLocal : CopyJob::OptsNone), currentSong); connect(job, SIGNAL(result(int)), SLOT(addSongResult(int))); connect(job, SIGNAL(percent(int)), SLOT(percent(int))); job->start(); } } void FsDevice::copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover) { jobAbortRequested=false; if (!isConnected()) { emit actionStatus(NotConnected); return; } needToFixVa=opts.fixVariousArtists && s.isVariousArtists(); if (!overwrite) { Song check=s; if (needToFixVa) { Device::fixVariousArtists(QString(), check, false); } if (MpdLibraryModel::self()->songExists(check)) { emit actionStatus(SongExists); return; } } QString source=audioFolder+s.file; if (!QFile::exists(source)) { emit actionStatus(SourceFileDoesNotExist); return; } QString baseDir=MPDConnection::self()->getDetails().dir; if (!overwrite && QFile::exists(baseDir+musicPath)) { emit actionStatus(FileExists); return; } currentDestFile=baseDir+musicPath; QDir dir(Utils::getDir(currentDestFile)); if (!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), baseDir)) { emit actionStatus(DirCreationFaild); return; } currentSong=s; // Pass an empty filename as covername, so that Covers::copyCover knows this is TO MPD... CopyJob *job=new CopyJob(source, currentDestFile, copyCover ? DeviceOptions(QString()) : DeviceOptions(Device::constNoCover), needToFixVa ? CopyJob::OptsUnApplyVaFix : CopyJob::OptsNone, currentSong); connect(job, SIGNAL(result(int)), SLOT(copySongToResult(int))); connect(job, SIGNAL(percent(int)), SLOT(percent(int))); job->start(); } void FsDevice::removeSong(const Song &s) { jobAbortRequested=false; if (!isConnected()) { emit actionStatus(NotConnected); return; } if (!QFile::exists(audioFolder+s.file)) { emit actionStatus(SourceFileDoesNotExist); return; } currentSong=s; DeleteJob *job=new DeleteJob(audioFolder+s.file); connect(job, SIGNAL(result(int)), SLOT(removeSongResult(int))); job->start(); } void FsDevice::cleanDirs(const QSet &dirs) { CleanJob *job=new CleanJob(dirs, audioFolder, opts.coverName); connect(job, SIGNAL(result(int)), SLOT(cleanDirsResult(int))); connect(job, SIGNAL(percent(int)), SLOT(percent(int))); job->start(); } Covers::Image FsDevice::requestCover(const Song &s) { Covers::Image i; QString songFile=audioFolder+s.file; QString dirName=Utils::getDir(songFile); if (QFile::exists(dirName+opts.coverName)) { QImage img(dirName+opts.coverName); if (!img.isNull()) { emit cover(s, img); return Covers::Image(img, dirName+opts.coverName); } } QStringList files=QDir(dirName).entryList(QStringList() << QLatin1String("*.jpg") << QLatin1String("*.png"), QDir::Files|QDir::Readable); foreach (const QString &fileName, files) { QImage img(dirName+fileName); if (!img.isNull()) { emit cover(s, img); return Covers::Image(img, dirName+fileName); } } return Covers::Image(); } void FsDevice::percent(int pc) { if (jobAbortRequested && 100!=pc) { FileJob *job=qobject_cast(sender()); if (job) { job->stop(); } return; } emit progress(pc); } void FsDevice::addSongResult(int status) { CopyJob *job=qobject_cast(sender()); FileJob::finished(job); spaceInfo.setDirty(); if (jobAbortRequested) { if (job && job->wasStarted() && QFile::exists(currentDestFile)) { QFile::remove(currentDestFile); } return; } if (Ok!=status) { emit actionStatus(status); } else { currentSong.file=currentDestFile.mid(audioFolder.length()); if (needToFixVa) { currentSong.fixVariousArtists(); } addSongToList(currentSong); emit actionStatus(Ok, job && job->coverCopied()); } } void FsDevice::copySongToResult(int status) { CopyJob *job=qobject_cast(sender()); FileJob::finished(job); spaceInfo.setDirty(); if (jobAbortRequested) { if (job && job->wasStarted() && QFile::exists(currentDestFile)) { QFile::remove(currentDestFile); } return; } if (Ok!=status) { emit actionStatus(status); } else { currentSong.file=currentDestFile.mid(MPDConnection::self()->getDetails().dir.length()); QString origPath; if (MPDConnection::self()->isMopidy()) { origPath=currentSong.file; currentSong.file=Song::encodePath(currentSong.file); } if (needToFixVa) { currentSong.revertVariousArtists(); } Utils::setFilePerms(currentDestFile); // MusicLibraryModel::self()->addSongToList(currentSong); // DirViewModel::self()->addFileToList(origPath.isEmpty() ? currentSong.file : origPath, // origPath.isEmpty() ? QString() : currentSong.file); emit actionStatus(Ok, job && job->coverCopied()); } } void FsDevice::removeSongResult(int status) { FileJob::finished(sender()); spaceInfo.setDirty(); if (jobAbortRequested) { return; } if (Ok!=status) { emit actionStatus(status); } else { removeSongFromList(currentSong); emit actionStatus(Ok); } } void FsDevice::cleanDirsResult(int status) { FileJob::finished(sender()); spaceInfo.setDirty(); if (jobAbortRequested) { return; } emit actionStatus(status); } void FsDevice::initScaner() { if (!scanner) { static bool registeredTypes=false; if (!registeredTypes) { qRegisterMetaType >("QSet"); registeredTypes=true; } scanner=new MusicScanner(); connect(scanner, SIGNAL(libraryUpdated(MusicLibraryItemRoot *)), this, SLOT(libraryUpdated(MusicLibraryItemRoot *))); connect(scanner, SIGNAL(songCount(int)), this, SLOT(songCount(int))); connect(scanner, SIGNAL(cacheSaved()), this, SLOT(savedCache())); connect(scanner, SIGNAL(savingCache(int)), this, SLOT(savingCache(int))); connect(scanner, SIGNAL(readingCache(int)), this, SLOT(readingCache(int))); connect(this, SIGNAL(scan(const QString &, const QString &, bool, const QSet &)), scanner, SLOT(scan(const QString &, const QString &, bool, const QSet &))); connect(this, SIGNAL(saveCache(const QString &, MusicLibraryItemRoot *)), scanner, SLOT(saveCache(const QString &, MusicLibraryItemRoot *))); } } void FsDevice::startScanner(bool fullScan) { stopScanner(); initScaner(); QSet existingSongs; if (!fullScan) { QSet songs=allSongs(); foreach (const Song &s, songs) { existingSongs.insert(FileOnlySong(s)); } } state=Updating; emit scan(audioFolder, opts.useCache ? cacheFileName() : QString(), !scanned, existingSongs); setStatusMessage(tr("Updating...")); emit updating(id(), true); } void FsDevice::stopScanner() { state=Idle; if (!scanner) { return; } disconnect(scanner, SIGNAL(libraryUpdated(MusicLibraryItemRoot *)), this, SLOT(libraryUpdated(MusicLibraryItemRoot *))); disconnect(scanner, SIGNAL(songCount(int)), this, SLOT(songCount(int))); disconnect(scanner, SIGNAL(cacheSaved()), this, SLOT(savedCache())); disconnect(scanner, SIGNAL(savingCache(int)), this, SLOT(savingCache(int))); disconnect(scanner, SIGNAL(readingCache(int)), this, SLOT(readingCache(int))); scanner->deleteLater(); scanner=0; } void FsDevice::clear() const { if (childCount()) { FsDevice *that=const_cast(this); that->update=new MusicLibraryItemRoot(); that->applyUpdate(); that->scanned=false; } } void FsDevice::libraryUpdated(MusicLibraryItemRoot *lib) { cacheProgress=-1; if (update) { delete update; } update=lib; setStatusMessage(QString()); state=Idle; emit updating(id(), false); } QString FsDevice::cacheFileName() const { if (audioFolder.isEmpty()) { setAudioFolder(); } return audioFolder+constCantataCacheFile+".xml.gz"; } void FsDevice::saveCache() { if (opts.useCache) { state=SavingCache; initScaner(); emit saveCache(cacheFileName(), this); } } void FsDevice::savedCache() { state=Idle; cacheProgress=-1; setStatusMessage(QString()); emit cacheSaved(); } void FsDevice::removeCache() { QString cacheFile(cacheFileName()); if (QFile::exists(cacheFile)) { QFile::remove(cacheFile); } } void FsDevice::readingCache(int pc) { cacheStatus(tr("Reading cache"), pc); } void FsDevice::savingCache(int pc) { cacheStatus(tr("Saving cache"), pc); } void FsDevice::cacheStatus(const QString &msg, int prog) { if (prog!=cacheProgress) { cacheProgress=prog; setStatusMessage(tr("%1 %2%","Message percent").arg(msg).arg(cacheProgress)); } } cantata-2.2.0/devices/fsdevice.h000066400000000000000000000123751316350454000165220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FSDEVICE_H #define FSDEVICE_H #include "device.h" #include "mpd-interface/song.h" #include "support/utils.h" #include "freespaceinfo.h" #include "models/musiclibraryitemroot.h" #include "http/httpserver.h" #include #include class Thread; struct FileOnlySong : public Song { FileOnlySong(const Song &o) : Song(o) { } bool operator==(const FileOnlySong &o) const { return file==o.file; } bool operator<(const FileOnlySong &o) const { return file.compare(o.file)<0; } }; inline uint qHash(const FileOnlySong &key) { return qHash(key.file); } class MusicScanner : public QObject, public MusicLibraryProgressMonitor { Q_OBJECT public: MusicScanner(); virtual ~MusicScanner(); void stop(); bool wasStopped() const { return stopRequested; } void readProgress(double pc); void writeProgress(double pc); public Q_SLOTS: void scan(const QString &folder, const QString &cacheFile, bool readCache, const QSet &existingSongs); void saveCache(const QString &cache, MusicLibraryItemRoot *lib); Q_SIGNALS: void songCount(int c); void libraryUpdated(MusicLibraryItemRoot *); void cacheSaved(); void readingCache(int pc); void savingCache(int pc); private: void scanFolder(MusicLibraryItemRoot *library, const QString &topLevel, const QString &f, QSet &existing, int level); private: Thread *thread; bool stopRequested; int count; QElapsedTimer timer; }; class FsDevice : public Device { Q_OBJECT public: enum State { Idle, Updating, SavingCache }; static const QLatin1String constCantataCacheFile; static const QLatin1String constCantataSettingsFile; static const QLatin1String constMusicFilenameSchemeKey; static const QLatin1String constVfatSafeKey; static const QLatin1String constAsciiOnlyKey; static const QLatin1String constIgnoreTheKey; static const QLatin1String constReplaceSpacesKey; static const QLatin1String constCoverFileNameKey; // Cantata extension! static const QLatin1String constCoverMaxSizeKey; // Cantata extension! static const QLatin1String constVariousArtistsFixKey; // Cantata extension! static const QLatin1String constTranscoderKey; // Cantata extension! static const QLatin1String constUseCacheKey; // Cantata extension! static const QLatin1String constDefCoverFileName; static const QLatin1String constAutoScanKey; // Cantata extension! static bool readOpts(const QString &fileName, DeviceOptions &opts, bool readAll); static void writeOpts(const QString &fileName, const DeviceOptions &opts, bool writeAll); FsDevice(MusicLibraryModel *m, Solid::Device &dev); FsDevice(MusicLibraryModel *m, const QString &name, const QString &id); virtual ~FsDevice(); void rescan(bool full=true); void stop(); bool isRefreshing() const { return Idle!=state; } QString path() const { return audioFolder; } QString coverFile() const { return opts.coverName; } void addSong(const Song &s, bool overwrite, bool copyCover); void copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover); void removeSong(const Song &s); void cleanDirs(const QSet &dirs); Covers::Image requestCover(const Song &s); QString cacheFileName() const; virtual void setAudioFolder() const { } void saveCache(); void removeCache(); bool isStdFs() const { return true; } bool canPlaySongs() const { return HttpServer::self()->isAlive(); } Q_SIGNALS: // For talking to scanner... void scan(const QString &folder, const QString &cacheFile, bool readCache, const QSet &existingSongs); void saveCache(const QString &cacheFile, MusicLibraryItemRoot *lib); protected: void initScaner(); void startScanner(bool fullScan=true); void stopScanner(); void clear() const; protected Q_SLOTS: void savedCache(); void libraryUpdated(MusicLibraryItemRoot *lib); void percent(int pc); void addSongResult(int status); void copySongToResult(int status); void removeSongResult(int status); void cleanDirsResult(int status); void readingCache(int pc); void savingCache(int pc); private: void cacheStatus(const QString &msg, int prog); protected: State state; bool scanned; int cacheProgress; MusicScanner *scanner; mutable QString audioFolder; FreeSpaceInfo spaceInfo; }; #endif cantata-2.2.0/devices/mounter/000077500000000000000000000000001316350454000162425ustar00rootroot00000000000000cantata-2.2.0/devices/mounter/CMakeLists.txt000066400000000000000000000021041316350454000207770ustar00rootroot00000000000000set(CANTATA_MOUNTER_SRCS main.cpp mounter.cpp) set(CANTATA_MOUNTER_MOC_HDRS mounter.h) qt5_wrap_cpp( CANTATA_MOUNTER_MOC_SRCS ${CANTATA_MOUNTER_MOC_HDRS} ) qt5_add_dbus_adaptor(CANTATA_MOUNTER_SRCS mpd.cantata.mounter.xml mounter.h Mounter ) add_executable( cantata-mounter ${CANTATA_MOUNTER_SRCS} ${CANTATA_MOUNTER_MOC_SRCS} ) install(TARGETS cantata-mounter RUNTIME DESTINATION lib/cantata) configure_file(mpd.cantata.mounter.service.cmake ${CMAKE_CURRENT_BINARY_DIR}/mpd.cantata.mounter.service) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mpd.cantata.mounter.service DESTINATION ${SHARE_INSTALL_PREFIX}/dbus-1/system-services/ ) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mpd.cantata.mounter.conf DESTINATION /etc/dbus-1/system.d/ ) target_link_libraries(cantata-mounter ${Qt5Core_LIBRARIES} ${Qt5DBus_LIBRARIES}) include_directories( ${QTINCLUDES} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR}) install(FILES ${_qmFile} DESTINATION ${SHARE_INSTALL_PREFIX}/cantata/) install(PROGRAMS mount.cifs.wrapper DESTINATION ${SHARE_INSTALL_PREFIX}/cantata/scripts/ ) cantata-2.2.0/devices/mounter/main.cpp000066400000000000000000000017711316350454000177000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mounter.h" #include int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Mounter srv; return app.exec(); } cantata-2.2.0/devices/mounter/mount.cifs.wrapper000077500000000000000000000002341316350454000217330ustar00rootroot00000000000000#!/bin/bash # # Calling mount.cifs directly from DBUS service seems to mess things up? # ..so call mount.cifs via this wrapper :-) # mount.cifs $@ exit $? cantata-2.2.0/devices/mounter/mounter.cpp000066400000000000000000000167331316350454000204510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mounter.h" #include "mounteradaptor.h" #include "config.h" #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include #include #include #include #include #include #include Mounter::Mounter(QObject *p) : QObject(p) , timer(0) , procCount(0) { new MounterAdaptor(this); QDBusConnection bus=QDBusConnection::systemBus(); if (!bus.registerService("mpd.cantata.mounter") || !bus.registerObject("/Mounter", this)) { QTimer::singleShot(0, qApp, SLOT(quit())); } } static inline bool mpOk(const QString &mp) { return !mp.isEmpty() && mp.startsWith("/home/"); // ) && mp.contains("cantata"); } static QString fixPath(const QString &dir) { QString d(dir); if (!d.isEmpty() && !d.startsWith(QLatin1String("http://"))) { d.replace(QLatin1String("//"), QChar('/')); } d.replace(QLatin1String("/./"), QChar('/')); if (!d.isEmpty() && !d.endsWith('/')) { d+='/'; } return d; } // Control via: // qdbus --system mpd.cantata.mounter /Mounter mpd.cantata.mounter.mount smb://workgroup\user:password@host:port/path?domain=domain mountPoint uid gid void Mounter::mount(const QString &url, const QString &mountPoint, int uid, int gid, int pid) { if (calledFromDBus()) { registerPid(pid); } qWarning() << url << mountPoint << uid << gid; QUrl u(url); int st=-1; if (u.scheme()=="smb" && mpOk(mountPoint)) { QString user=u.userName(); QString domain; QString password=u.password(); int port=u.port(); #if QT_VERSION < 0x050000 if (u.hasQueryItem("domain")) { domain=u.queryItemValue("domain"); } #else QUrlQuery q(u); if (q.hasQueryItem("domain")) { domain=q.queryItemValue("domain"); } #endif QTemporaryFile *temp=0; if (!password.isEmpty()) { temp=new QTemporaryFile(); if (temp && temp->open()) { QTextStream str(temp); if (!user.isEmpty()) { str << "username=" << user << endl; } str << "password=" << password << endl; if (!domain.isEmpty()) { str << "domain=" << domain << endl; } } } QString path=fixPath(u.host()+"/"+u.path()); while (!path.startsWith("//")) { path="/"+path; } // qWarning() << "EXEC" << "mount.cifs" << path << mountPoint // << "-o" << // (temp ? ("credentials="+temp->fileName()+",") : QString())+ // "uid="+QString::number(uid)+",gid="+QString::number(gid)+ // (445==port || port<1 ? QString() : ",port="+QString::number(port))+ // (temp || user.isEmpty() ? QString() : (",username="+user))+ // (temp || domain.isEmpty() ? QString() : (",domain="+domain))+ // (temp ? QString() : ",password="); QProcess *proc=new QProcess(this); connect(proc, SIGNAL(finished(int)), SLOT(mountResult(int))); proc->setProperty("mp", mountPoint); proc->setProperty("pid", pid); proc->start(QLatin1String(INSTALL_PREFIX"/share/cantata/scripts/mount.cifs.wrapper"), QStringList() << path << mountPoint << "-o" << (temp ? ("credentials="+temp->fileName()+",") : QString())+ "uid="+QString::number(uid)+",gid="+QString::number(gid)+ (445==port || port<1 ? QString() : ",port="+QString::number(port))+ (temp || user.isEmpty() ? QString() : (",username="+user))+ (temp || domain.isEmpty() ? QString() : (",domain="+domain))+ (temp ? QString() : ",password="), QIODevice::WriteOnly); if (temp) { tempFiles.insert(proc, temp); } procCount++; return; } emit mountStatus(mountPoint, pid, st); } // Control via: // qdbus --system mpd.cantata.mounter /Mounter mpd.cantata.mounter.umount mountPoint void Mounter::umount(const QString &mountPoint, int pid) { if (calledFromDBus()) { registerPid(pid); } if (mpOk(mountPoint)) { QProcess *proc=new QProcess(this); connect(proc, SIGNAL(finished(int)), SLOT(umountResult(int))); proc->start("umount", QStringList() << mountPoint); proc->setProperty("mp", mountPoint); proc->setProperty("pid", pid); procCount++; } else { emit umountStatus(mountPoint, pid, -1); } } void Mounter::mountResult(int st) { QProcess *proc=dynamic_cast(sender()); qWarning() << "MOUNT RESULT" << st << (void *)proc; if (proc) { procCount--; proc->close(); proc->deleteLater(); if (tempFiles.contains(proc)) { tempFiles[proc]->close(); tempFiles[proc]->deleteLater(); tempFiles.remove(proc); } emit mountStatus(proc->property("mp").toString(), proc->property("pid").toInt(), st); } startTimer(); } void Mounter::umountResult(int st) { QProcess *proc=dynamic_cast(sender()); if (proc) { procCount--; proc->close(); proc->deleteLater(); emit umountStatus(proc->property("mp").toString(), proc->property("pid").toInt(), st); } startTimer(); } void Mounter::startTimer() { if (!timer) { timer=new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timeout())); } timer->start(30000); } void Mounter::registerPid(int pid) { pids.insert(pid); startTimer(); } void Mounter::timeout() { if (procCount!=0) { startTimer(); return; } QSet running; foreach (int p, pids) { if (0==kill(p, 0)) { running.insert(p); } } pids=running; if (pids.isEmpty()) { qApp->exit(); QMap::ConstIterator it(tempFiles.constBegin()); QMap::ConstIterator end(tempFiles.constEnd()); for (; it!=end; ++it) { it.value()->close(); delete it.value(); } tempFiles.clear(); } else { startTimer(); } } cantata-2.2.0/devices/mounter/mounter.h000066400000000000000000000033671316350454000201150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MOUNTER_H #define MOUNTER_H #include #include #include #include class QTimer; class QTemporaryFile; class Mounter : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "mpd.cantata.mounter") public: Mounter(QObject *p=0); ~Mounter() { } public Q_SLOTS: Q_NOREPLY void mount(const QString &url, const QString &mountPoint, int uid, int gid, int pid); Q_NOREPLY void umount(const QString &mountPoint, int pid); private: void startTimer(); void registerPid(int pid); private Q_SLOTS: void mountResult(int st); void umountResult(int st); void timeout(); Q_SIGNALS: void mountStatus(const QString &src, int pid, int st); void umountStatus(const QString &src, int pid, int st); private: QTimer *timer; int procCount; QMap tempFiles; QSet pids; }; #endif cantata-2.2.0/devices/mounter/mpd.cantata.mounter.conf000066400000000000000000000012301316350454000227670ustar00rootroot00000000000000 cantata-2.2.0/devices/mounter/mpd.cantata.mounter.service.cmake000066400000000000000000000001531316350454000245640ustar00rootroot00000000000000[D-BUS Service] Name=mpd.cantata.mounter Exec=@CMAKE_INSTALL_PREFIX@/lib/cantata/cantata-mounter User=root cantata-2.2.0/devices/mounter/mpd.cantata.mounter.xml000066400000000000000000000022361316350454000226510ustar00rootroot00000000000000 cantata-2.2.0/devices/mountpoints.cpp000066400000000000000000000041451316350454000176600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mountpoints.h" #include "support/globalstatic.h" #include #include #include GLOBAL_STATIC(MountPoints, instance) MountPoints::MountPoints() : token(0) { mounts=new QFile("/proc/mounts", this); if (mounts && mounts->open(QIODevice::ReadOnly)) { QSocketNotifier *notifier = new QSocketNotifier(mounts->handle(), QSocketNotifier::Exception, mounts); connect(notifier, SIGNAL(activated(int)), this, SLOT(updateMountPoints())); updateMountPoints(); } else if (mounts) { mounts->deleteLater(); mounts=0; } } void MountPoints::updateMountPoints() { QSet entries; QFile f("/proc/mounts"); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { QStringList lines=QString(f.readAll()).split("\n"); foreach (const QString &l, lines) { QStringList parts = l.split(' '); if (parts.size()>=2) { entries.insert(QString(parts.at(1)).replace("\\040", " ")); } } } if (entries!=current) { token++; current=entries; emit updated(); } } bool MountPoints::isMounted(const QString &mp) const { return current.contains(mp.endsWith('/') ? mp.left(mp.length()-1) : mp); } cantata-2.2.0/devices/mountpoints.h000066400000000000000000000025041316350454000173220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MOUNTPOINTS_H #define MOUNTPOINTS_H #include #include #include class QFile; class MountPoints : public QObject { Q_OBJECT public: static MountPoints * self(); MountPoints(); int currentToken() const { return token; } bool isMounted(const QString &mp) const; Q_SIGNALS: void updated(); private Q_SLOTS: void updateMountPoints(); private: int token; QSet current; QFile *mounts; }; #endif // MOUNTPOINTS_H cantata-2.2.0/devices/mtpdevice.cpp000066400000000000000000001523631316350454000172470ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mtpdevice.h" #include "models/musiclibraryitemsong.h" #include "models/musiclibraryitemalbum.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemroot.h" #include "models/mpdlibrarymodel.h" #include "models/devicesmodel.h" #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "gui/covers.h" #include "mpd-interface/song.h" #include "encoders.h" #include "transcodingjob.h" #include "support/utils.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdconnection.h" #include "filejob.h" #include "support/configuration.h" #include "support/thread.h" #include #include #include #include #include #include //#define TIME_MTP_OPERATIONS #ifdef TIME_MTP_OPERATIONS #include #endif #include #define COVER_DBUG if (Covers::debugEnabled()) qWarning() << metaObject()->className() << __FUNCTION__ #define DBUG_CLASS(CLASS) if (DevicesModel::debugEnabled()) qWarning() << CLASS << QThread::currentThread()->objectName() << __FUNCTION__ #define DBUG DBUG_CLASS(metaObject()->className()) // Enable the following #define to have Cantata attempt to ascertain the AlbumArtist tag by // looking at the file path #define MTP_FAKE_ALBUMARTIST_SUPPORT // Enable the following #define to have Cantata attempt to ascertain the tracks's track number // from its filename. This will only be done if the device returns '0' as the track number. #define MTP_TRACKNUMBER_FROM_FILENAME static const QLatin1String constMtpDefaultCover("AlbumArt.jpg"); static const uint32_t constRootFolder=0xffffffffU; static const QString constMusicFolder=QLatin1String("Music"); static const quint16 constOrigFileName = Song::Performer; static int progressMonitor(uint64_t const processed, uint64_t const total, void const * const data) { const MtpConnection *con=static_cast(data); const_cast(con)->emitProgress((int)(((processed*1.0)/(total*1.0)*100.0)+0.5)); return con->abortWasRequested() ? -1 : 0; } static int trackListMonitor(uint64_t const processed, uint64_t const total, void const * const data) { const MtpConnection *con=static_cast(data); const_cast(con)->trackListProgress(((processed*100.0)/(total*1.0))+0.5); return con->abortWasRequested() ? -1 : 0; } static uint16_t fileReceiver(void *params, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen) { Q_UNUSED(params) QByteArray *byteData=static_cast(priv); (*byteData)+=QByteArray((char *)data, (int)sendlen); *putlen = sendlen; return LIBMTP_HANDLER_RETURN_OK; } MtpConnection::MtpConnection(unsigned int bus, unsigned int dev, bool aaSupport) : device(0) #ifdef MTP_CLEAN_ALBUMS , albums(0) #endif , library(0) , lastListPercent(-1) , abortRequested(false) , busNum(bus) , devNum(dev) , supprtAlbumArtistTag(aaSupport) { size=0; used=0; LIBMTP_Init(); thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } MtpConnection::~MtpConnection() { stop(); } MusicLibraryItemRoot * MtpConnection::takeLibrary() { MusicLibraryItemRoot *lib=library; library=0; return lib; } void MtpConnection::emitProgress(int percent) { emit progress(percent); } void MtpConnection::trackListProgress(int percent) { if (percent!=lastListPercent) { lastListPercent=percent; emit updatePercentage(percent); } } void MtpConnection::connectToDevice() { #ifdef TIME_MTP_OPERATIONS QElapsedTimer timer; QElapsedTimer totalTimer; timer.start(); totalTimer.start(); #endif device=0; storage.clear(); defaultMusicFolder=0; LIBMTP_raw_device_t *rawDevices=0; int numDev=-1; emit statusMessage(tr("Connecting to device...")); if (LIBMTP_ERROR_NONE!=LIBMTP_Detect_Raw_Devices(&rawDevices, &numDev) || numDev<=0) { emit statusMessage(tr("No devices found")); return; } #ifdef TIME_MTP_OPERATIONS qWarning() << "Connect to device:" << timer.elapsed(); timer.restart(); #endif LIBMTP_mtpdevice_t *mptDev=0; for (int i = 0; i < numDev; i++) { if (0!=busNum && 0!=devNum) { if (rawDevices[i].bus_location==busNum && rawDevices[i].devnum==devNum) { mptDev = LIBMTP_Open_Raw_Device_Uncached(&rawDevices[i]); break; } } else { if ((mptDev = LIBMTP_Open_Raw_Device_Uncached(&rawDevices[i]))) { break; } } } #ifdef TIME_MTP_OPERATIONS qWarning() << "Open raw device:" << timer.elapsed(); timer.restart(); #endif size=0; used=0; device=mptDev; updateStorage(); #ifdef TIME_MTP_OPERATIONS qWarning() << "Update storage:" << timer.elapsed(); #endif free(rawDevices); if (!device) { emit statusMessage(tr("No devices found")); #ifdef TIME_MTP_OPERATIONS qWarning() << "TOTAL connect:" <default_music_folder; emit statusMessage(tr("Connected to device")); #ifdef TIME_MTP_OPERATIONS qWarning() << "TOTAL connect:" < artists; QList songs; }; struct MtpFolder { MtpFolder(const QString &ar=QString(), const QString &al=QString()) : artist(ar), album(al) { } QString artist; QString album; }; #endif struct Path { Path() : storage(0), parent(0), id(0) { } bool ok() const { return 0!=storage && 0!=parent && 0!=id; } uint32_t storage; uint32_t parent; uint32_t id; QString path; }; static QString encodePath(LIBMTP_track_t *track, const QString &path, const QString &store) { return QChar('{')+QString::number(track->storage_id)+QChar('/')+QString::number(track->parent_id)+QChar('/')+QString::number(track->item_id)+ (store.isEmpty() ? QString() : (QChar('/')+store))+QChar('}')+path; } static Path decodePath(const QString &path) { Path p; if (path.startsWith(QChar('{')) && path.contains(QChar('}'))) { int end=path.indexOf(QChar('}')); QStringList details=path.mid(1, end-1).split(QChar('/')); if (details.count()>=3) { p.storage=details.at(0).toUInt(); p.parent=details.at(1).toUInt(); p.id=details.at(2).toUInt(); } p.path=path.mid(end+1); } else { p.path=path; } return p; } void MtpConnection::updateLibrary(const DeviceOptions &opts) { if (!isConnected()) { connectToDevice(); } destroyData(); if (!isConnected()) { emit libraryUpdated(); return; } #ifdef TIME_MTP_OPERATIONS QElapsedTimer timer; QElapsedTimer totalTimer; timer.start(); totalTimer.start(); #endif library = new MusicLibraryItemRoot; emit statusMessage(tr("Updating folders...")); updateFilesAndFolders(); if (abortRequested) { return; } #ifdef TIME_MTP_OPERATIONS qWarning() << "Folder update:" << timer.elapsed(); timer.restart(); #endif if (folderMap.isEmpty()) { destroyData(); emit libraryUpdated(); return; } #ifdef MTP_CLEAN_ALBUMS updateAlbums(); #ifdef TIME_MTP_OPERATIONS qWarning() << "Clean albums:" << timer.elapsed(); timer.restart(); #endif #endif emit statusMessage(tr("Updating tracks...")); lastListPercent=-1; LIBMTP_track_t *tracks=LIBMTP_Get_Tracklisting_With_Callback(device, &trackListMonitor, this); QMap::ConstIterator folderEnd=folderMap.constEnd(); QList::Iterator store=storage.begin(); QList::Iterator storeEnd=storage.end(); QMap storageNames; for (; store!=storeEnd; ++store) { setMusicFolder(*store); storageNames[(*store).id]=(*store).description; } if (abortRequested) { return; } MusicLibraryItemArtist *artistItem = 0; MusicLibraryItemAlbum *albumItem = 0; #ifdef MTP_FAKE_ALBUMARTIST_SUPPORT QMap albumMap; QMap folders; bool getAlbumArtistFromPath=opts.scheme.startsWith(DeviceOptions::constAlbumArtist+QChar('/')+DeviceOptions::constAlbumTitle+QChar('/')); #endif #ifdef TIME_MTP_OPERATIONS qWarning() << "Tracks update:" << timer.elapsed(); timer.restart(); #endif while (tracks) { if (abortRequested) { return; } LIBMTP_track_t *track=tracks; tracks=tracks->next; QMap::ConstIterator folder=folderMap.find(track->parent_id); if (folder==folderEnd) { // We only care about tracks in the music folder... LIBMTP_destroy_track_t(track); continue; } Song s; QString trackFilename=QString::fromUtf8(track->filename); s.id=track->item_id; s.file=encodePath(track, folder.value().path+trackFilename, storageNames.count()>1 ? storageNames[track->storage_id] : QString()); s.album=QString::fromUtf8(track->album); s.artist=QString::fromUtf8(track->artist); s.albumartist=s.artist; // TODO: ALBUMARTIST: Read from 'track' when libMTP supports album artist! QString composer=QString::fromUtf8(track->composer); if (!composer.isEmpty()) { s.setComposer(composer); } s.year=QString::fromUtf8(track->date).mid(0, 4).toUInt(); s.title=QString::fromUtf8(track->title); s.genres[0]=QString::fromUtf8(track->genre); s.track=track->tracknumber; s.time=(track->duration/1000.0)+0.5; s.size=track->filesize; s.fillEmptyFields(); s.populateSorts(); #ifdef MTP_FAKE_ALBUMARTIST_SUPPORT if (getAlbumArtistFromPath) { QStringList folderParts=(*folder).path.split('/', QString::SkipEmptyParts); if (folderParts.length()>=3) { // Path should be "Music/${AlbumArtist}/${Album}" int artistPath=1; int albumPath=2; // BubbleUPNP will download to "Music/Artist/${AlbumArtist}/${Album}" if it is configured to // download to "Music" and "Preserve folder structure" (At least this is the folder structure // from MiniDLNA...) if (folderParts.length()>=4 && QLatin1String("Artist")==folderParts.at(1)) { artistPath++; albumPath++; } MtpFolder folder(folderParts.at(artistPath), folderParts.at(albumPath)); folders.insert(track->parent_id, folder); if (folder.album==s.album && Song::isVariousArtists(folder.artist)) { s.albumartist=folder.artist; } } } #endif #ifdef MTP_TRACKNUMBER_FROM_FILENAME if (0==s.track) { int space=trackFilename.indexOf(' '); if (space>0 && space<=3) { s.track=trackFilename.mid(0, space).toInt(); } } #endif if (!artistItem || (supprtAlbumArtistTag ? s.artistOrComposer()!=artistItem->data() : s.artist!=artistItem->data())) { artistItem = library->artist(s); } if (!albumItem || albumItem->parentItem()!=artistItem || s.albumName()!=albumItem->data()) { albumItem = artistItem->album(s); } MusicLibraryItemSong *songItem = new MusicLibraryItemSong(s, albumItem); albumItem->append(songItem); #ifdef MTP_FAKE_ALBUMARTIST_SUPPORT // Store AlbumName->Artists/Songs mapping MtpAlbum &al=albumMap[s.album]; al.artists.insert(s.artist); al.songs.append(songItem); al.folder=track->parent_id; #endif LIBMTP_destroy_track_t(track); } while (tracks) { LIBMTP_track_t *track=tracks; tracks=tracks->next; LIBMTP_destroy_track_t(track); } #ifdef TIME_MTP_OPERATIONS qWarning() << "Tracks parse:" << timer.elapsed(); timer.restart(); #endif #ifdef MTP_FAKE_ALBUMARTIST_SUPPORT // Use Album map to determine 'AlbumArtist' tag for various artist albums, and // albums that have tracks where artist is set to '${artist} and somebodyelse' QMap::ConstIterator it=albumMap.constBegin(); QMap::ConstIterator end=albumMap.constEnd(); for (; it!=end; ++it) { if (abortRequested) { return; } if ((*it).artists.count()>1) { QSet tracks; QString shortestArtist; bool duplicateTrackNumbers=false; foreach (MusicLibraryItemSong *s, (*it).songs) { if (tracks.contains(s->track())) { duplicateTrackNumbers=true; break; } else { if (shortestArtist.isEmpty() || s->song().artist.length()song().artist; } tracks.insert(s->track()); } } // If an album has mutiple tracks with the same track number, then we probably have X albums // by X artists - in which case we proceeed no further. if (!duplicateTrackNumbers) { MtpFolder &f=folders[(*it).folder]; // Now, check to see if all artists contain 'shortestArtist'. If so then use 'shortestArtist' as the album // artist. This is probably due to songs which have artist set to '${artist} and somebodyelse' QString albumArtist=shortestArtist; foreach (const QString &artist, (*it).artists) { if (!artist.contains(shortestArtist)) { // Found an artist that did not contain 'shortestArtist', so use 'Various Artists' for album artist albumArtist=!f.artist.isEmpty() && f.album==it.key() ? f.artist : Song::variousArtists(); break; } } // Now move songs to correct artist/album... foreach (MusicLibraryItemSong *s, (*it).songs) { if (s->song().albumartist==albumArtist) { continue; } Song song=s->song(); song.albumartist=albumArtist; artistItem=library->artist(song); albumItem=artistItem->album(song); MusicLibraryItemSong *songItem = new MusicLibraryItemSong(song, albumItem); albumItem->append(songItem); MusicLibraryItemAlbum *prevAlbum=(MusicLibraryItemAlbum *)s->parentItem(); prevAlbum->remove(s); if (0==prevAlbum->childCount()) { // Album no longer has any songs, so remove! MusicLibraryItemArtist *prevArtist=(MusicLibraryItemArtist *)prevAlbum->parentItem(); prevArtist->remove(prevAlbum); if (0==prevArtist->childCount()) { // Artist no longer has any albums, so remove! library->remove(prevArtist); } } } } } } #ifdef TIME_MTP_OPERATIONS qWarning() << "AlbumArtist detection:" << timer.elapsed(); timer.restart(); #endif #endif if (abortRequested) { return; } #ifdef TIME_MTP_OPERATIONS qWarning() << "Grouping:" << timer.elapsed(); qWarning() << "TOTAL update:" <next; QString name=QString::fromUtf8(file->filename); if (LIBMTP_FILETYPE_FOLDER==file->filetype) { bool isMusic=constRootFolder!=parentDir || 0==name.compare(constMusicFolder, Qt::CaseInsensitive); if (isMusic) { QMap::ConstIterator it=folderMap.find(file->parent_id); QString path; if (it!=folderMap.constEnd()) { path=it.value().path+name+QLatin1Char('/'); } else { path=name+QLatin1Char('/'); } QMap::iterator entry=folderMap.insert(file->item_id, Folder(path, file->item_id, file->parent_id, file->storage_id)); if (folderMap.contains(file->parent_id)) { folderMap[file->parent_id].folders.insert(file->item_id); } listFolder(storage, file->item_id, &(entry.value())); } } else if (f && constRootFolder!=parentDir) { if (name.endsWith(".jpg", Qt::CaseInsensitive) || name.endsWith(".png", Qt::CaseInsensitive) || QLatin1String("albumart.pamp")==name) { f->covers.insert(file->item_id, File(name, file->filesize, file->item_id)); } else { f->files.insert(file->item_id, File(name, file->filesize, file->item_id)); } } LIBMTP_destroy_file_t(file); } } void MtpConnection::updateStorage() { uint64_t sizeCalc=0; uint64_t usedCalc=0; if (device && LIBMTP_ERROR_NONE==LIBMTP_Get_Storage(device, LIBMTP_STORAGE_SORTBY_MAXSPACE)) { LIBMTP_devicestorage_struct *s=device->storage; while (s) { QString volumeIdentifier=QString::fromUtf8(s->VolumeIdentifier); QList::Iterator it=storage.begin(); QList::Iterator end=storage.end(); for ( ;it!=end; ++it) { if ((*it).volumeIdentifier==volumeIdentifier) { // We know about this storage ID, so update its size... (*it).size=s->MaxCapacity; (*it).used=s->MaxCapacity-s->FreeSpaceInBytes; break; } } if (it==end) { // Unknown storage ID, so add to list! Storage store; store.id=s->id; store.description=QString::fromUtf8(s->StorageDescription); store.volumeIdentifier=QString::fromUtf8(s->VolumeIdentifier); store.size=s->MaxCapacity; store.used=s->MaxCapacity-s->FreeSpaceInBytes; storage.append(store); } sizeCalc+=s->MaxCapacity; usedCalc+=s->MaxCapacity-s->FreeSpaceInBytes; s=s->next; } } size=sizeCalc; used=usedCalc; } QList MtpConnection::getStorageList() const { QList s; QList::ConstIterator it=storage.constBegin(); QList::ConstIterator end=storage.constEnd(); for ( ;it!=end; ++it) { DeviceStorage store; store.size=(*it).size; store.used=(*it).used; store.description=(*it).description; store.volumeIdentifier=(*it).volumeIdentifier; s.append(store); } return s; } MtpConnection::Storage & MtpConnection::getStorage(const QString &volumeIdentifier) { QList::Iterator first=storage.begin(); if (!volumeIdentifier.isEmpty()) { QList::Iterator it=first; QList::Iterator end=storage.end(); for ( ;it!=end; ++it) { if ((*it).volumeIdentifier==volumeIdentifier) { return *it; } } } return *first; } MtpConnection::Storage & MtpConnection::getStorage(uint32_t id) { QList::Iterator first=storage.begin(); QList::Iterator it=first; QList::Iterator end=storage.end(); for ( ;it!=end; ++it) { if ((*it).id==id) { return *it; } } return *first; } uint32_t MtpConnection::createFolder(const QString &name, const QString &fullPath, uint32_t parentId, uint32_t storageId) { char *nameCopy = qstrdup(name.toUtf8().constData()); uint32_t newFolderId=LIBMTP_Create_Folder(device, nameCopy, parentId, storageId); delete nameCopy; if (0==newFolderId) { return 0; } folderMap.insert(newFolderId, Folder(fullPath, newFolderId, parentId, storageId)); return newFolderId; } uint32_t MtpConnection::getFolder(const QString &path, uint32_t storageId) { QMap::ConstIterator it=folderMap.constBegin(); QMap::ConstIterator end=folderMap.constEnd(); for (; it!=end; ++it) { if (storageId==(*it).storageId && 0==(*it).path.compare(path, Qt::CaseInsensitive)) { return (*it).id; } } return 0; } QString MtpConnection::getPath(uint32_t folderId) { return folderMap.contains(folderId) ? folderMap[folderId].path : QString(); } uint32_t MtpConnection::checkFolderStructure(const QStringList &dirs, Storage &store) { setMusicFolder(store); QString path; uint32_t parentId=store.musicFolderId; foreach (const QString &d, dirs) { path+=d+QChar('/'); uint32_t folderId=getFolder(path, store.id); if (0==folderId) { folderId=createFolder(d, path, parentId, store.id); if (0==folderId) { return parentId; } else { parentId=folderId; } } else { parentId=folderId; } } return parentId; } static char * createString(const QString &str) { return str.isEmpty() ? qstrdup("") : qstrdup(str.toUtf8()); } static LIBMTP_filetype_t mtpFileType(const QString &f) { if (f.endsWith(".mp3", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_MP3; } if (f.endsWith(".ogg", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_OGG; } if (f.endsWith(".wma", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_WMA; } if (f.endsWith(".m4a", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_M4A; // LIBMTP_FILETYPE_MP4 } if (f.endsWith(".aac", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_AAC; } if (f.endsWith(".flac", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_FLAC; } if (f.endsWith(".wav", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_WAV; } if (f.endsWith(".jpg", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_JPEG; } if (f.endsWith(".png", Qt::CaseInsensitive)) { return LIBMTP_FILETYPE_PNG; } return LIBMTP_FILETYPE_UNDEF_AUDIO; } static QTemporaryFile * saveImageToTemp(const QImage &img, const QString &name) { QTemporaryFile *temp=new QTemporaryFile(); int index=name.lastIndexOf('.'); if (index>0) { temp=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX"+name.mid(index)); } else { temp=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX"); } img.save(temp); temp->close(); return temp; } void MtpConnection::putSong(const Song &s, bool fixVa, const DeviceOptions &opts, bool overwrite, bool copyCover) { int status=Device::Failed; bool fixedVa=false; bool embedCoverImage=Device::constEmbedCover==opts.coverName; bool copiedCover=false; LIBMTP_track_t *meta=0; QString destName; Storage store=getStorage(opts.volumeId); uint32_t folderId=0; copyCover=copyCover && !embedCoverImage && Device::constNoCover!=opts.coverName; if (device) { meta=LIBMTP_new_track_t(); meta->item_id=0; Song song=s; QString fileName=song.file; QTemporaryFile *temp=0; if (fixVa || embedCoverImage) { // Need to 'workaround' broken various artists handling, and/or embedding cover, so write to a temporary file first... temp=Device::copySongToTemp(song); if (temp) { if (embedCoverImage) { Device::embedCover(song.file, song, opts.coverMaxSize); } if (fixVa && Device::fixVariousArtists(temp->fileName(), song, true)) { fixedVa=true; } song.file=temp->fileName(); } } struct stat statBuf; if (0==stat(QFile::encodeName(song.file).constData(), &statBuf)) { meta->filesize=statBuf.st_size; meta->modificationdate=statBuf.st_mtime; } // Check if storage has enough space, if not try to find one that does! if (store.freeSpace()filesize) { QList::Iterator it=storage.begin(); QList::Iterator end=storage.end(); for ( ;it!=end; ++it) { if ((*it).freeSpace()>=meta->filesize) { store=*it; break; } } } meta->parent_id=folderId=store.musicFolderId; meta->storage_id=store.id; destName=store.musicPath+opts.createFilename(song); QStringList dirs=destName.split('/', QString::SkipEmptyParts); if (dirs.count()>1) { destName=dirs.takeLast(); meta->parent_id=folderId=checkFolderStructure(dirs, store); } Folder &folder=folderMap[folderId]; QMap::ConstIterator it=folder.files.constBegin(); QMap::ConstIterator end=folder.files.constEnd(); for (; it!=end; ++it) { if ((*it).name==destName) { if (!overwrite || 0!=LIBMTP_Delete_Object(device, (*it).id)) { status=Device::SongExists; } break; } } if (status!=Device::SongExists) { meta->title=createString(song.title); meta->artist=createString(song.artist); meta->composer=createString(song.composer()); meta->genre=createString(song.genres[0]); meta->album=createString(song.album); meta->date=createString(QString().sprintf("%4d0101T0000.0", song.year)); meta->filename=createString(destName); meta->tracknumber=song.track; meta->duration=song.time*1000; meta->rating=0; meta->usecount=0; meta->filetype=mtpFileType(song.file); meta->next=0; switch (LIBMTP_Send_Track_From_File(device, fileName.toUtf8(), meta, &progressMonitor, this)) { case LIBMTP_ERROR_NONE: status=Device::Ok; break; case LIBMTP_ERROR_STORAGE_FULL: status=Device::NoSpace; break; default: status=Device::Failed; break; } } if (temp) { // Delete the temp file... temp->remove(); delete temp; } } if (Device::Ok==status) { Folder &folder=folderMap[folderId]; // LibMTP seems to reset parent_id to 0 - but we NEED the correct value for encodePath meta->parent_id=folderId; if (copyCover) { QMap::ConstIterator it=folder.covers.constBegin(); QMap::ConstIterator end=folder.covers.constEnd(); for (; it!=end; ++it) { if (it.value().name==opts.coverName) { copiedCover=true; copyCover=false; break; } } } // Send cover, as a plain file... if (copyCover) { QString srcFile; // If we have transcoded a song, the song.file will point to /tmp!!! Song orig = s; if (orig.hasExtraField(constOrigFileName)) { orig.file=orig.extraField(constOrigFileName); } Covers::Image coverImage=Covers::self()->getImage(orig); QTemporaryFile *temp=0; if (!coverImage.img.isNull() && !coverImage.fileName.isEmpty()) { if (opts.coverMaxSize && (coverImage.img.width()>(int)opts.coverMaxSize || coverImage.img.height()>(int)opts.coverMaxSize)) { temp=saveImageToTemp(coverImage.img.scaled(QSize(opts.coverMaxSize, opts.coverMaxSize), Qt::KeepAspectRatio, Qt::SmoothTransformation), opts.coverName); } else if (!coverImage.fileName.endsWith(".jpg", Qt::CaseInsensitive) || !QFile::exists(coverImage.fileName)) { temp=saveImageToTemp(coverImage.img, opts.coverName); } else { srcFile=coverImage.fileName; } if (temp) { srcFile=temp->fileName(); } } if (!srcFile.isEmpty()) { LIBMTP_file_t *fileMeta=LIBMTP_new_file_t(); fileMeta->item_id=0; fileMeta->parent_id=folderId; fileMeta->storage_id=store.id; fileMeta->filename=createString(opts.coverName); struct stat statBuf; if (0==stat(QFile::encodeName(srcFile).constData(), &statBuf)) { fileMeta->filesize=statBuf.st_size; fileMeta->modificationdate=statBuf.st_mtime; } meta->filetype=mtpFileType(opts.coverName); if (0==LIBMTP_Send_File_From_File(device, srcFile.toUtf8(), fileMeta, 0, 0)) { folder.covers.insert(fileMeta->item_id, File(opts.coverName, statBuf.st_size, fileMeta->item_id)); copiedCover=true; } LIBMTP_destroy_file_t(fileMeta); } if (temp) { temp->remove(); delete temp; } } folder.files.insert(meta->item_id, File(destName, meta->filesize, meta->item_id)); updateStorage(); } emit putSongStatus(status, meta ? encodePath(meta, destName, storage.count()>1 ? store.description : QString()) : QString(), fixedVa, copiedCover); if (meta) { LIBMTP_destroy_track_t(meta); } } MtpConnection::File MtpConnection::getCoverDetils(const Song &s) { File cover; Path path=decodePath(s.file); if (path.ok() && folderMap.contains(path.parent) && !folderMap[path.parent].covers.isEmpty()) { QMap &covers=folderMap[path.parent].covers; QMap::ConstIterator it=covers.constBegin(); QMap::ConstIterator end=covers.constEnd(); for (; it!=end; ++it) { if (it.value().size>cover.size) { cover=it.value(); } } } return cover; } void MtpConnection::getSong(const Song &song, const QString &dest, bool fixVa, bool copyCover) { bool copiedSong=device && 0==LIBMTP_Get_File_To_File(device, song.id, dest.toUtf8(), &progressMonitor, this); bool copiedCover=false; if (copiedSong && !abortRequested && copyCover) { QString destDir=Utils::getDir(dest); if (QDir(destDir).entryList(QStringList() << QLatin1String("*.jpg") << QLatin1String("*.png"), QDir::Files|QDir::Readable).isEmpty()) { File cover=getCoverDetils(song); if (0!=cover.id) { QString mpdCover=MPDConnection::self()->getDetails().coverName; if (mpdCover.isEmpty()) { mpdCover="cover"; } QString fileName=QString(destDir+mpdCover+(cover.name.endsWith(".jpg", Qt::CaseInsensitive) ? ".jpg" : ".png")); QByteArray fileNameUtf8=fileName.toUtf8(); copiedCover=0==LIBMTP_Get_File_To_File(device, cover.id, fileNameUtf8.constData(), 0, 0); if (copiedCover) { Utils::setFilePerms(fileName); } } } } if (copiedSong && fixVa && !abortRequested) { Song s(song); Device::fixVariousArtists(dest, s, false); } emit getSongStatus(copiedSong, copiedCover); } void MtpConnection::delSong(const Song &song) { Path path=decodePath(song.file); bool deletedSong=device && path.ok() && 0==LIBMTP_Delete_Object(device, path.id); if (deletedSong) { folderMap[path.parent].files.remove(path.id); #ifdef MTP_CLEAN_ALBUMS // Remove track from album. Remove album (and cover) if no tracks. LIBMTP_album_t *album=getAlbum(song); if (album) { if (0==album->no_tracks || (1==album->no_tracks && album->tracks[0]==(uint32_t)song.id)) { LIBMTP_Delete_Object(device, album->album_id); updateAlbums(); } else if (album->no_tracks>1) { // Remove track from album... uint32_t *tracks = (uint32_t *)malloc((album->no_tracks-1)*sizeof(uint32_t)); if (tracks) { bool found=false; for (uint32_t i=0, j=0; ino_tracks && j<(album->no_tracks-1); ++i) { if (album->tracks[i]!=(uint32_t)song.id) { tracks[j++]=album->tracks[i]; } else { found=true; } } if (found) { album->no_tracks--; free(album->tracks); album->tracks = tracks; LIBMTP_Update_Album(device, album); } else { free(tracks); } } } } #endif updateStorage(); } emit delSongStatus(deletedSong); } bool MtpConnection::removeFolder(uint32_t folderId) { QMap::iterator folder=folderMap.find(folderId); if (folderMap.end()!=folder && (*folder).folders.isEmpty() && (*folder).files.isEmpty()) { // Delete any cover files... QList toRemove=(*folder).covers.keys(); foreach (uint32_t cover, toRemove) { if (0==LIBMTP_Delete_Object(device, cover)) { (*folder).covers.remove(cover); } } // Delete folder, if it is now empty... if ((*folder).covers.isEmpty() && 0==LIBMTP_Delete_Object(device, folderId)) { if (folderMap.contains((*folder).parentId)) { folderMap[(*folder).parentId].folders.remove(folderId); } folderMap.remove(folderId); return true; } } return false; } void MtpConnection::cleanDirs(const QSet &dirs) { foreach (const QString &d, dirs) { Path path=decodePath(d); Storage &store=getStorage(path.storage); if (0==store.musicFolderId) { continue; } uint32_t folderId=path.parent; while (0!=folderId && folderId!=store.musicFolderId) { QMap::iterator it=folderMap.find(folderId); if (it!=folderMap.end()) { if (removeFolder(folderId)) { folderId=(*it).parentId; folderMap.erase(it); } else { break; } } else { break; } } } updateStorage(); emit cleanDirsStatus(true); } void MtpConnection::getCover(const Song &song) { File c=getCoverDetils(song); COVER_DBUG << c.name << c.id; if (0!=c.id) { QByteArray data; if (0==LIBMTP_Get_File_To_Handler(device, c.id, fileReceiver, &data, 0, 0)) { QImage img; if (img.loadFromData(data)) { COVER_DBUG << "loaded cover"; emit cover(song, img); } } } } void MtpConnection::stop() { if (thread) { disconnectFromDevice(false); thread->stop(); thread=0; } } #ifdef MTP_CLEAN_ALBUMS void MtpConnection::updateAlbums() { while (albums) { LIBMTP_album_t *album=albums; albums=albums->next; LIBMTP_destroy_album_t(album); } albums=LIBMTP_Get_Album_List(device); } LIBMTP_album_t * MtpConnection::getAlbum(const Song &song) { LIBMTP_album_t *al=albums; LIBMTP_album_t *possible=0; while (al) { if (QString::fromUtf8(al->name)==song.album) { // For some reason, MTP sometimes leaves blank albums behind. // So, when looking for an album if we find one with the same name, but no tracks - then save this as a possibility... if (0==al->no_tracks) { QString aa(QString::fromUtf8(al->artist)); if (aa.isEmpty() || aa==song.albumArtist()) { possible=al; } } else { for (uint32_t i=0; ino_tracks; ++i) { if (al->tracks[i]==(uint32_t)song.id) { return al; } } } } al=al->next; } return possible; } #endif void MtpConnection::destroyData() { folderMap.clear(); if (library) { delete library; library=0; } #ifdef MTP_CLEAN_ALBUMS while (albums) { LIBMTP_album_t *album=albums; albums=albums->next; LIBMTP_destroy_album_t(album); } #endif } QString cfgKey(Solid::Device &dev, const QString &serial) { QString key=QLatin1String("MTP-")+dev.vendor()+QChar('-')+dev.product()+QChar('-')+serial; key.replace('/', '_'); return key; } MtpDevice::MtpDevice(MusicLibraryModel *m, Solid::Device &dev, unsigned int busNum, unsigned int devNum) : Device(m, dev, #ifdef MTP_FAKE_ALBUMARTIST_SUPPORT true #else false #endif ) , pmp(dev.as()) , tempFile(0) , mtpUpdating(false) { static bool registeredTypes=false; if (!registeredTypes) { qRegisterMetaType >("QSet"); qRegisterMetaType("DeviceOptions"); registeredTypes=true; } connection=new MtpConnection(busNum, devNum, supportsAlbumArtistTag()); connect(this, SIGNAL(updateLibrary(const DeviceOptions &)), connection, SLOT(updateLibrary(const DeviceOptions &))); connect(connection, SIGNAL(libraryUpdated()), this, SLOT(libraryUpdated())); connect(connection, SIGNAL(progress(int)), this, SLOT(emitProgress(int))); connect(this, SIGNAL(putSong(const Song &, bool, const DeviceOptions &, bool, bool)), connection, SLOT(putSong(const Song &, bool, const DeviceOptions &, bool, bool))); connect(connection, SIGNAL(putSongStatus(int, const QString &, bool, bool)), this, SLOT(putSongStatus(int, const QString &, bool, bool))); connect(this, SIGNAL(getSong(const Song &, const QString &, bool, bool)), connection, SLOT(getSong(const Song &, const QString &, bool, bool))); connect(connection, SIGNAL(getSongStatus(bool, bool)), this, SLOT(getSongStatus(bool, bool))); connect(this, SIGNAL(delSong(const Song &)), connection, SLOT(delSong(const Song &))); connect(connection, SIGNAL(delSongStatus(bool)), this, SLOT(delSongStatus(bool))); connect(this, SIGNAL(cleanMusicDirs(const QSet &)), connection, SLOT(cleanDirs(const QSet &))); connect(this, SIGNAL(getCover(const Song &)), connection, SLOT(getCover(const Song &))); connect(connection, SIGNAL(cleanDirsStatus(bool)), this, SLOT(cleanDirsStatus(bool))); connect(connection, SIGNAL(statusMessage(const QString &)), this, SLOT(setStatusMessage(const QString &))); connect(connection, SIGNAL(deviceDetails(const QString &)), this, SLOT(deviceDetails(const QString &))); connect(connection, SIGNAL(updatePercentage(int)), this, SLOT(updatePercentage(int))); connect(connection, SIGNAL(cover(const Song &, const QImage &)), this, SIGNAL(cover(const Song &, const QImage &))); opts.fixVariousArtists=false; opts.coverName=constMtpDefaultCover; QTimer::singleShot(0, this, SLOT(rescan(bool))); defaultName=data(); if (!opts.name.isEmpty()) { DBUG << "setName" << opts.name; setData(opts.name); } icn=Icon("multimedia-player"); } MtpDevice::~MtpDevice() { stop(); } void MtpDevice::deviceDetails(const QString &s) { DBUG << s; if ((s!=serial || serial.isEmpty()) && solidDev.isValid()) { serial=s; QString configKey=cfgKey(solidDev, serial); opts.load(configKey); configured=Configuration().hasGroup(configKey); if (!opts.name.isEmpty() && opts.name!=defaultName) { DBUG << "setName" << opts.name; setData(opts.name); emit renamed(); } } } bool MtpDevice::isConnected() const { return solidDev.isValid() && pmp && pmp->isValid() && connection->isConnected(); } void MtpDevice::stop() { abortJob(); deleteTemp(); if (connection) { disconnect(connection, SIGNAL(libraryUpdated()), this, SLOT(libraryUpdated())); disconnect(connection, SIGNAL(progress(int)), this, SLOT(emitProgress(int))); disconnect(connection, SIGNAL(putSongStatus(int, const QString &, bool, bool)), this, SLOT(putSongStatus(int, const QString &, bool, bool))); disconnect(connection, SIGNAL(getSongStatus(bool, bool)), this, SLOT(getSongStatus(bool, bool))); disconnect(connection, SIGNAL(delSongStatus(bool)), this, SLOT(delSongStatus(bool))); disconnect(connection, SIGNAL(cleanDirsStatus(bool)), this, SLOT(cleanDirsStatus(bool))); disconnect(connection, SIGNAL(statusMessage(const QString &)), this, SLOT(setStatusMessage(const QString &))); disconnect(connection, SIGNAL(deviceDetails(const QString &)), this, SLOT(deviceDetails(const QString &))); disconnect(connection, SIGNAL(updatePercentage(int)), this, SLOT(updatePercentage(int))); disconnect(connection, SIGNAL(cover(const Song &, const QImage &)), this, SIGNAL(cover(const Song &, const QImage &))); metaObject()->invokeMethod(connection, "stop", Qt::QueuedConnection); connection->deleteLater(); connection=0; } } void MtpDevice::configure(QWidget *parent) { if (!isIdle()) { return; } DevicePropertiesDialog *dlg=new DevicePropertiesDialog(parent); connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &))); if (!configured) { connect(dlg, SIGNAL(cancelled()), SLOT(saveProperties())); } DeviceOptions o=opts; if (o.name.isEmpty()) { o.name=data(); } dlg->show(QString(), o, connection->getStorageList(), DevicePropertiesWidget::Prop_Name|DevicePropertiesWidget::Prop_CoversAll|DevicePropertiesWidget::Prop_Va|DevicePropertiesWidget::Prop_Transcoder); } void MtpDevice::rescan(bool full) { Q_UNUSED(full) if (mtpUpdating || !solidDev.isValid()) { return; } if (childCount()) { update=new MusicLibraryItemRoot(); applyUpdate(); } mtpUpdating=true; emit updating(solidDev.udi(), true); emit updateLibrary(opts); } void MtpDevice::addSong(const Song &s, bool overwrite, bool copyCover) { requestAbort(false); if (!isConnected()) { emit actionStatus(NotConnected); return; } needToFixVa=opts.fixVariousArtists && s.isVariousArtists(); if (!overwrite) { Song check=s; if (needToFixVa) { Device::fixVariousArtists(QString(), check, true); } if (songExists(check)) { emit actionStatus(SongExists); return; } } if (!QFile::exists(s.file)) { emit actionStatus(SourceFileDoesNotExist); return; } currentSong=s; Encoders::Encoder encoder; if (!opts.transcoderCodec.isEmpty()) { encoder=Encoders::getEncoder(opts.transcoderCodec); if (encoder.codec.isEmpty()) { emit actionStatus(CodecNotAvailable); return; } } transcoding = !opts.transcoderCodec.isEmpty() && (!opts.transcoderWhenDifferent || encoder.isDifferent(s.file)) && (!opts.transcoderWhenSourceIsLosssless || Device::isLossless(s.file)); if (transcoding) { deleteTemp(); tempFile=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX."+encoder.extension); tempFile->setAutoRemove(false); if (!tempFile->open()) { deleteTemp(); emit actionStatus(FailedToCreateTempFile); return; } QString destFile=tempFile->fileName(); tempFile->close(); if (QFile::exists(destFile)) { QFile::remove(destFile); } transcoding=true; TranscodingJob *job=new TranscodingJob(encoder, opts.transcoderValue, s.file, destFile); job->setProperty("overwrite", overwrite); job->setProperty("copyCover", copyCover); connect(job, SIGNAL(result(int)), SLOT(transcodeSongResult(int))); connect(job, SIGNAL(percent(int)), SLOT(transcodePercent(int))); job->start(); currentSong.setExtraField(constOrigFileName, currentSong.file); currentSong.file=destFile; } else { emit putSong(currentSong, needToFixVa, opts, overwrite, copyCover); } } void MtpDevice::copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover) { requestAbort(false); transcoding=false; if (!isConnected()) { emit actionStatus(NotConnected); return; } needToFixVa=opts.fixVariousArtists && s.isVariousArtists(); if (!overwrite) { Song check=s; if (needToFixVa) { Device::fixVariousArtists(QString(), check, false); } if (MpdLibraryModel::self()->songExists(check)) { emit actionStatus(SongExists); return; } } if (!songExists(s)) { emit actionStatus(SongDoesNotExist); return; } QString baseDir=MPDConnection::self()->getDetails().dir; currentDestFile=baseDir+musicPath; QDir dir(Utils::getDir(currentDestFile)); if (!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), baseDir)) { emit actionStatus(DirCreationFaild); return; } currentSong=s; emit getSong(s, currentDestFile, needToFixVa, copyCover); } void MtpDevice::removeSong(const Song &s) { requestAbort(false); if (!isConnected()) { emit actionStatus(NotConnected); return; } if (!songExists(s)) { emit actionStatus(SongDoesNotExist); return; } currentSong=s; emit delSong(s); } void MtpDevice::cleanDirs(const QSet &dirs) { requestAbort(false); if (!isConnected()) { emit actionStatus(NotConnected); return; } emit cleanMusicDirs(dirs); } Covers::Image MtpDevice::requestCover(const Song &song) { COVER_DBUG << song.file; requestAbort(false); if (isConnected()) { COVER_DBUG << "Get cover from connection"; emit getCover(song); } return Covers::Image(); } void MtpDevice::putSongStatus(int status, const QString &file, bool fixedVa, bool copiedCover) { deleteTemp(); if (jobAbortRequested) { return; } if (Ok!=status) { emit actionStatus(status); } else { currentSong.file=file; if (needToFixVa && fixedVa) { currentSong.fixVariousArtists(); } #ifndef MTP_FAKE_ALBUMARTIST_SUPPORT else if (!opts.fixVariousArtists) { // TODO: ALBUMARTIST: Remove when libMTP supports album artist! currentSong.albumartist=currentSong.artist; } #endif addSongToList(currentSong); emit actionStatus(Ok, copiedCover); } } void MtpDevice::transcodeSongResult(int status) { TranscodingJob *job=qobject_cast(sender()); if (!job) { return; } FileJob::finished(job); if (jobAbortRequested) { deleteTemp(); return; } if (Ok!=status) { emit actionStatus(status); } else { emit putSong(currentSong, needToFixVa, opts, job->property("overwrite").toBool(), job->property("copyCover").toBool()); } } void MtpDevice::transcodePercent(int percent) { if (jobAbortRequested) { FileJob *job=qobject_cast(sender()); if (job) { job->stop(); } return; } emit progress(percent/2); } void MtpDevice::emitProgress(int percent) { if (jobAbortRequested) { return; } emit progress(transcoding ? (50+(percent/2)) : percent); } void MtpDevice::getSongStatus(bool ok, bool copiedCover) { if (jobAbortRequested) { return; } if (!ok) { emit actionStatus(Failed); } else { currentSong.file=currentDestFile.mid(MPDConnection::self()->getDetails().dir.length()); QString origPath; if (MPDConnection::self()->isMopidy()) { origPath=currentSong.file; currentSong.file=Song::encodePath(currentSong.file); } if (needToFixVa) { currentSong.revertVariousArtists(); } Utils::setFilePerms(currentDestFile); // MusicLibraryModel::self()->addSongToList(currentSong); // DirViewModel::self()->addFileToList(origPath.isEmpty() ? currentSong.file : origPath, // origPath.isEmpty() ? QString() : currentSong.file); emit actionStatus(Ok, copiedCover); } } void MtpDevice::delSongStatus(bool ok) { if (jobAbortRequested) { return; } if (!ok) { emit actionStatus(Failed); } else { removeSongFromList(currentSong); emit actionStatus(Ok); } } void MtpDevice::cleanDirsStatus(bool ok) { emit actionStatus(ok ? Ok : Failed); } double MtpDevice::usedCapacity() { if (!isConnected()) { return -1.0; } return connection->capacity()>0 ? (connection->usedSpace()*1.0)/(connection->capacity()*1.0) : -1.0; } QString MtpDevice::capacityString() { if (!isConnected()) { return tr("Not Connected"); } return tr("%1 free").arg(Utils::formatByteSize(connection->capacity()-connection->usedSpace())); } qint64 MtpDevice::freeSpace() { if (!isConnected()) { return 0; } return connection->capacity()-connection->usedSpace(); } void MtpDevice::libraryUpdated() { if (update) { delete update; } update=connection->takeLibrary(); setStatusMessage(QString()); emit updating(id(), false); mtpUpdating=false; } void MtpDevice::saveProperties(const QString &, const DeviceOptions &newOpts) { if (configured && opts==newOpts) { return; } QString newName=newOpts.name.isEmpty() ? defaultName : newOpts.name; bool diffName=opts.name!=newName; opts=newOpts; if (diffName) { DBUG << "setName" << newName; setData(newName); } saveProperties(); if (diffName) { emit renamed(); } } void MtpDevice::saveProperties() { if (solidDev.isValid()) { configured=true; opts.save(cfgKey(solidDev, serial), false, true, false); // Dont save fileame scheme - cant be changed! emit configurationChanged(); } } void MtpDevice::saveOptions() { if (solidDev.isValid()) { opts.save(cfgKey(solidDev, serial), false, true, false); // Dont save fileame scheme - cant be changed! } } void MtpDevice::deleteTemp() { if (tempFile) { tempFile->remove(); delete tempFile; tempFile=0; } } cantata-2.2.0/devices/mtpdevice.h000066400000000000000000000153331316350454000167070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MTPDEVICE_H #define MTPDEVICE_H #include "fsdevice.h" #include "mpd-interface/song.h" #include "config.h" #include "solid-lite/portablemediaplayer.h" #include class MusicLibraryItemRoot; class Thread; class QTemporaryFile; class MtpConnection : public QObject { Q_OBJECT public: struct File { File(const QString &n=QString(), uint64_t s=0, uint32_t i=0) : name(n), size(s), id(i) { } QString name; uint64_t size; uint32_t id; }; struct Folder { Folder(const QString &pth=QString(), uint32_t i=0, uint32_t p=0, uint32_t s=0) : path(pth) , id(i) , parentId(p) , storageId(s) { } QString path; uint32_t id; uint32_t parentId; uint32_t storageId; QSet folders; QMap files; QMap covers; }; struct Storage : public DeviceStorage { Storage() : id(0), musicFolderId(0) { } uint32_t id; uint32_t musicFolderId; QString musicPath; }; MtpConnection(unsigned int bus, unsigned int dev, bool aaSupport); virtual ~MtpConnection(); bool isConnected() const { return 0!=device; } MusicLibraryItemRoot * takeLibrary(); uint64_t capacity() const { return size; } uint64_t usedSpace() const { return used; } QList getStorageList() const; void emitProgress(int percent); void trackListProgress(int percent); void requestAbort(bool r) { abortRequested=r; } bool abortWasRequested() const { return abortRequested; } public Q_SLOTS: void connectToDevice(); void disconnectFromDevice(bool showStatus=true); void updateLibrary(const DeviceOptions &opts); void putSong(const Song &song, bool fixVa, const DeviceOptions &opts, bool overwrite, bool copyCover); void getSong(const Song &song, const QString &dest, bool fixVa, bool copyCover); void delSong(const Song &song); void cleanDirs(const QSet &dirs); void getCover(const Song &song); void stop(); Q_SIGNALS: void statusMessage(const QString &message); void putSongStatus(int, const QString &, bool, bool); void getSongStatus(bool ok, bool copiedCover); void delSongStatus(bool); void cleanDirsStatus(bool ok); void libraryUpdated(); void progress(int); void deviceDetails(const QString &serialNumber); void updatePercentage(int); void cover(const Song &s, const QImage &img); private: File getCoverDetils(const Song &s); bool removeFolder(uint32_t folderId); void updateFilesAndFolders(); void listFolder(uint32_t storage, uint32_t parentDir, Folder *f=0); void updateStorage(); Storage & getStorage(const QString &volumeIdentifier); Storage & getStorage(uint32_t id); uint32_t createFolder(const QString &name, const QString &fullPath, uint32_t parentId, uint32_t storageId); uint32_t getFolder(const QString &path, uint32_t storageId); QString getPath(uint32_t folderId); uint32_t checkFolderStructure(const QStringList &dirs, Storage &store); void setMusicFolder(Storage &store); #ifdef MTP_CLEAN_ALBUMS void updateAlbums(); LIBMTP_album_t * getAlbum(const Song &song); #endif void destroyData(); private: Thread *thread; LIBMTP_mtpdevice_t *device; #ifdef MTP_CLEAN_ALBUMS LIBMTP_album_t *albums; #endif QMap folderMap; MusicLibraryItemRoot *library; uint32_t defaultMusicFolder; QList storage; uint64_t size; uint64_t used; int lastListPercent; bool abortRequested; unsigned int busNum; unsigned int devNum; bool supprtAlbumArtistTag; }; class MtpDevice : public Device { Q_OBJECT public: MtpDevice(MusicLibraryModel *m, Solid::Device &dev, unsigned int busNum, unsigned int devNum); virtual ~MtpDevice(); bool isConnected() const; bool isRefreshing() const { return mtpUpdating; } void stop(); void configure(QWidget *parent); QString path() const { return QString(); } // audioFolder; } void addSong(const Song &s, bool overwrite, bool copyCover); void copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover); void removeSong(const Song &s); void cleanDirs(const QSet &dirs); Covers::Image requestCover(const Song &song); double usedCapacity(); QString capacityString(); qint64 freeSpace(); DevType devType() const { return Mtp; } void saveOptions(); void abortJob() { requestAbort(true); } Q_SIGNALS: // These are for talking to connection thread... void updateLibrary(const DeviceOptions &opts); void putSong(const Song &song, bool fixVa, const DeviceOptions &opts, bool overwrite, bool copyCover); void getSong(const Song &song, const QString &dest, bool fixVa, bool copyCover); void delSong(const Song &song); void cleanMusicDirs(const QSet &dirs); void getCover(const Song &s); void cover(const Song &s, const QImage &img); private Q_SLOTS: void deviceDetails(const QString &s); void libraryUpdated(); void rescan(bool full=true); void putSongStatus(int status, const QString &file, bool fixedVa, bool copiedCover); void transcodeSongResult(int status); void transcodePercent(int percent); void emitProgress(int); void getSongStatus(bool ok, bool copiedCover); void delSongStatus(bool ok); void cleanDirsStatus(bool ok); void saveProperties(const QString &newPath, const DeviceOptions &opts); void saveProperties(); private: void deleteTemp(); void requestAbort(bool r) { if (connection) connection->requestAbort(r); jobAbortRequested=r; } private: Solid::PortableMediaPlayer *pmp; MtpConnection *connection; QTemporaryFile *tempFile; Song currentSong; bool mtpUpdating; QString serial; QString defaultName; friend class MtpConnection; }; #endif cantata-2.2.0/devices/musicbrainz.cpp000066400000000000000000000346751316350454000176220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* Copyright (C) 2005-2007 Richard Lärkäng This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "musicbrainz.h" #include "network/networkproxyfactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "support/thread.h" #include #include #include #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #include #elif defined(__linux__) #include #endif #include #define DBUG qDebug() static const int constFramesPerSecond=75; static const int constDataTrackAdjust=11400; static inline int secondsToFrames(int s) { return constFramesPerSecond *s; } static inline int framesToSeconds(int f) { return (f/(constFramesPerSecond*1.0))+0.5; } struct Track { Track(int o=0, bool d=false) : offset(o), isData(d) {} int offset; bool isData; }; static QString calculateDiscId(const QList &tracks) { if (tracks.isEmpty()) { return QString(); } // Code based on libmusicbrainz/lib/diskid.cpp int numTracks = tracks.count()-1; QCryptographicHash sha(QCryptographicHash::Sha1); char temp[9]; sprintf(temp, "%02X", 1); sha.addData(temp, strlen(temp)); sprintf(temp, "%02X", numTracks); sha.addData(temp, strlen(temp)); for(int i = 0; i < 100; i++) { long offset; if (0==i) { offset = tracks[numTracks].offset; } else if (i <= numTracks) { offset = tracks[i-1].offset; } else { offset = 0; } sprintf(temp, "%08lX", offset); sha.addData(temp, strlen(temp)); } QByteArray base64 = sha.result().toBase64(); // '/' '+' and '=' replaced for MusicBrainz return QString::fromLatin1(base64).replace(QLatin1Char( '/' ), QLatin1String( "_" )) .replace(QLatin1Char( '+' ), QLatin1String( "." )) .replace(QLatin1Char( '=' ), QLatin1String( "-" )); } static QString artistFromCreditList(MusicBrainz5::CArtistCredit *artistCredit ) { QString artistName; MusicBrainz5::CNameCreditList *artistList=artistCredit->NameCreditList(); if (artistList) { for (int i=0; i < artistList->NumItems(); i++) { MusicBrainz5::CNameCredit* name=artistList->Item(i); MusicBrainz5::CArtist* artist = name->Artist(); if (!name->Name().empty()) { artistName += QString::fromUtf8(name->Name().c_str()); } else { artistName += QString::fromUtf8(artist->Name().c_str()); } artistName += QString::fromUtf8(name->JoinPhrase().c_str()); } } return artistName; } MusicBrainz::MusicBrainz(const QString &device) : dev(device) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } MusicBrainz::~MusicBrainz() { thread->stop(); } void MusicBrainz::readDisc() { int fd=open(dev.toLocal8Bit(), O_RDONLY | O_NONBLOCK); if (fd < 0) { emit error(tr("Failed to open CD device")); return; } QList tracks; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) struct ioc_toc_header th; struct ioc_read_toc_single_entry te; struct ioc_read_subchannel cdsc; struct cd_sub_channel_info data; bzero(&cdsc,sizeof(cdsc)); cdsc.data = &data; cdsc.data_len = sizeof(data); cdsc.data_format = CD_CURRENT_POSITION; cdsc.address_format = CD_MSF_FORMAT; if (ioctl(fd, CDIOCREADSUBCHANNEL, (char *)&cdsc) >= 0 && 0==ioctl(fd, CDIOREADTOCHEADER, &th)) { te.address_format = CD_LBA_FORMAT; for (int i=th.starting_track; i<=th.ending_track; i++) { te.track = i; if (0==ioctl(fd, CDIOREADTOCENTRY, &te)) { tracks.append(Track(te.entry.addr.lba + secondsToFrames(2), te.entry.control&0x04)); } } te.track = 0xAA; if (0==ioctl(fd, CDIOREADTOCENTRY, &te)) { tracks.append((ntohl(te.entry.addr.lba)+secondsToFrames(2))/secondsToFrames(1)); } } #elif defined(__linux__) struct cdrom_tochdr th; struct cdrom_tocentry te; int status = ioctl(fd, CDROM_DISC_STATUS, CDSL_CURRENT); if ( (CDS_AUDIO==status || CDS_MIXED==status) && 0==ioctl(fd, CDROMREADTOCHDR, &th)) { te.cdte_format = CDROM_LBA; for (int i=th.cdth_trk0; i<=th.cdth_trk1; i++) { te.cdte_track = i; if (0==ioctl(fd, CDROMREADTOCENTRY, &te)) { tracks.append(Track(te.cdte_addr.lba + secondsToFrames(2), te.cdte_ctrl&CDROM_DATA_TRACK)); } } te.cdte_track = CDROM_LEADOUT; if (0==ioctl(fd, CDROMREADTOCENTRY, &te)) { tracks.append((te.cdte_addr.lba+secondsToFrames(2))/secondsToFrames(1)); } } #endif close(fd); initial.name=Song::unknown(); initial.artist=Song::unknown(); initial.genre=Song::unknown(); initial.isDefault=true; if (tracks.count()>1) { for (int i=0; i=3 && tracks.at(tracks.count()-2).isData) { tracks.takeLast(); Track last=tracks.takeLast(); last.offset-=constDataTrackAdjust; tracks.append(last); } } discId = calculateDiscId(tracks); emit initialDetails(initial); } void MusicBrainz::lookup(bool full) { bool isInitial=discId.isEmpty(); if (isInitial) { readDisc(); } if (!full) { return; } DBUG << "Should lookup " << discId; MusicBrainz5::CQuery Query("cantata-" PACKAGE_VERSION_STRING); QList m; QList proxies=NetworkProxyFactory::self()->queryProxy(QNetworkProxyQuery(QUrl("http://musicbrainz.org"))); foreach (const QNetworkProxy &p, proxies) { if (QNetworkProxy::HttpProxy==p.type() && 0!=p.port()) { Query.SetProxyHost(p.hostName().toLatin1().constData()); Query.SetProxyPort(p.port()); break; } } // Code adapted from libmusicbrainz/examples/cdlookup.cc try { MusicBrainz5::CMetadata Metadata=Query.Query("discid", discId.toLatin1().constData()); if (Metadata.Disc() && Metadata.Disc()->ReleaseList()) { MusicBrainz5::CReleaseList *releaseList=Metadata.Disc()->ReleaseList(); DBUG << "Found " << releaseList->NumItems() << " release(s)"; for (int i = 0; i < releaseList->NumItems(); i++) { MusicBrainz5::CRelease* release=releaseList->Item(i); //The releases returned from LookupDiscID don't contain full information MusicBrainz5::CQuery::tParamMap params; params["inc"]="artists labels recordings release-groups url-rels discids artist-credits"; std::string releaseId=release->ID(); MusicBrainz5::CMetadata Metadata2=Query.Query("release", releaseId, "", params); if (Metadata2.Release()) { MusicBrainz5::CRelease *fullRelease=Metadata2.Release(); //However, these releases will include information for all media in the release //So we need to filter out the only the media we want. MusicBrainz5::CMediumList mediaList=fullRelease->MediaMatchingDiscID(discId.toLatin1().constData()); if (mediaList.NumItems() > 0) { DBUG << "Found " << mediaList.NumItems() << " media item(s)"; for (int i=0; i < mediaList.NumItems(); i++) { MusicBrainz5::CMedium* medium= mediaList.Item(i); /*DBUG << "Found media: '" << medium.Title() << "', position " << medium.Position();*/ CdAlbum album; album.name=QString::fromUtf8(fullRelease->Title().c_str()); if (fullRelease->MediumList()->NumItems() > 1) { album.name = tr("%1 (Disc %2)").arg(album.name).arg(medium->Position()); album.disc=medium->Position(); } album.artist=artistFromCreditList(fullRelease->ArtistCredit()); album.genre=Song::unknown(); QString date = QString::fromUtf8(fullRelease->Date().c_str()); QRegExp yearRe("^(\\d{4,4})(-\\d{1,2}-\\d{1,2})?$"); if (yearRe.indexIn(date) > -1) { QString yearString = yearRe.cap(1); bool ok; album.year=yearString.toInt(&ok); if (!ok) { album.year = 0; } } MusicBrainz5::CTrackList *trackList=medium->TrackList(); if (trackList) { for (int i=0; i < trackList->NumItems(); i++) { // Ensure we have the same number of tracks are read from disc! if (album.tracks.count()>=initial.tracks.count()) { break; } MusicBrainz5::CTrack *track=trackList->Item(i); MusicBrainz5::CRecording *recording=track->Recording(); Song song; song.albumartist=album.artist; song.album=album.name; song.genres[0]=album.genre; song.id=song.track=track->Position(); song.time=track->Length()/1000; song.disc=album.disc; song.file=QString("%1.wav").arg(song.track); // Prefer title and artist from the track credits, but it appears to be empty if same as in Recording // Noticable in the musicbrainztest-fulldate test, where the title on the credits of track 18 are // "Bara om min älskade väntar", but the recording has title "Men bara om min älskade" if (recording && 0==track->ArtistCredit()) { song.artist=artistFromCreditList(recording->ArtistCredit()); } else { song.artist=artistFromCreditList(track->ArtistCredit()); } if (recording && track->Title().empty()) { song.title=QString::fromUtf8(recording->Title().c_str()); } else { song.title=QString::fromUtf8(track->Title().c_str()); } album.tracks.append(song); } } // Ensure we have the same number of tracks as read from disc! if (album.tracks.count() * */ /* Copyright (C) 2005 Richard Lärkäng This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MUSICBRAINZ_H #define MUSICBRAINZ_H #include "cdalbum.h" #include #include class Thread; class MusicBrainz : public QObject { Q_OBJECT public: MusicBrainz(const QString &device); ~MusicBrainz(); public Q_SLOTS: void lookup(bool full); Q_SIGNALS: void error(const QString &error); void initialDetails(const CdAlbum &); void matches(const QList &); private: void readDisc(); private: Thread *thread; QString dev; QString discId; CdAlbum initial; }; #endif // MUSICBRAINZ_H cantata-2.2.0/devices/remotedevicepropertiesdialog.cpp000066400000000000000000000070521316350454000232310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "remotedevicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "remotedevicepropertieswidget.h" #include "models/devicesmodel.h" #include "support/messagebox.h" #include "support/icon.h" #include #include #include RemoteDevicePropertiesDialog::RemoteDevicePropertiesDialog(QWidget *parent) : Dialog(parent) , isCreate(false) { setButtons(Ok|Cancel); setCaption(tr("Device Properties")); setAttribute(Qt::WA_DeleteOnClose); setWindowModality(Qt::WindowModal); tab=new QTabWidget(this); remoteProp=new RemoteDevicePropertiesWidget(tab); devProp=new DevicePropertiesWidget(tab); int margin=style()->pixelMetric(QStyle::PM_LayoutLeftMargin); if (margin<1) { margin=6; } devProp->layout()->setContentsMargins(margin, margin, margin, margin); tab->addTab(remoteProp, tr("Connection")); tab->addTab(devProp, tr("Music Library")); setMainWidget(tab); } void RemoteDevicePropertiesDialog::show(const DeviceOptions &opts, const RemoteFsDevice::Details &det, int props, int disabledProps, bool create, bool isConnected) { isCreate=create; if (isCreate) { setCaption(tr("Add Device")); } if (create) { devProp->setVisible(false); } else { tab->setCurrentIndex(isConnected ? 1 : 0); } devProp->setEnabled(!create && isConnected); devProp->showRemoteConnectionNote(!isConnected); devProp->update(QString(), opts, QList(), props, disabledProps); remoteProp->update(det, create, isConnected); connect(devProp, SIGNAL(updated()), SLOT(enableOkButton())); connect(remoteProp, SIGNAL(updated()), SLOT(enableOkButton())); Dialog::show(); enableButtonOk(false); } void RemoteDevicePropertiesDialog::enableOkButton() { bool useDevProp=devProp->isEnabled(); enableButtonOk(remoteProp->isSaveable() && (!useDevProp || devProp->isSaveable()) && (isCreate || remoteProp->isModified() || !useDevProp || devProp->isModified())); } void RemoteDevicePropertiesDialog::slotButtonClicked(int button) { switch (button) { case Ok: { RemoteFsDevice::Details d=remoteProp->details(); if (d.name!=remoteProp->origDetails().name && DevicesModel::self()->device(RemoteFsDevice::createUdi(d.name))) { MessageBox::error(this, tr("A remote device named '%1' already exists!\n\nPlease choose a different name.").arg(d.name)); } else { emit updatedSettings(devProp->settings(), remoteProp->details()); accept(); } break; } case Cancel: emit cancelled(); reject(); break; default: Dialog::slotButtonClicked(button); break; } } cantata-2.2.0/devices/remotedevicepropertiesdialog.h000066400000000000000000000033171316350454000226760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef REMOTEDEVICEPROPERTIESDIALOG_H #define REMOTEDEVICEPROPERTIESDIALOG_H #include "support/dialog.h" #include "remotefsdevice.h" class QTabWidget; class FilenameSchemeDialog; class DevicePropertiesWidget; class RemoteDevicePropertiesWidget; class RemoteDevicePropertiesDialog : public Dialog { Q_OBJECT public: RemoteDevicePropertiesDialog(QWidget *parent); void show(const DeviceOptions &opts, const RemoteFsDevice::Details &det, int props, int disabledProps=0, bool creating=false, bool isConnected=false); Q_SIGNALS: void updatedSettings(const DeviceOptions &opts, const RemoteFsDevice::Details &det); void cancelled(); private Q_SLOTS: void enableOkButton(); private: void slotButtonClicked(int button); private: QTabWidget *tab; RemoteDevicePropertiesWidget *remoteProp; DevicePropertiesWidget *devProp; bool isCreate; }; #endif cantata-2.2.0/devices/remotedevicepropertieswidget.cpp000066400000000000000000000216361316350454000232610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "remotedevicepropertieswidget.h" #include "filenameschemedialog.h" #include "gui/covers.h" #include #include #include #include "support/lineedit.h" #include "config.h" enum Type { Type_Samba, Type_SambaAvahi, Type_SshFs, Type_File }; RemoteDevicePropertiesWidget::RemoteDevicePropertiesWidget(QWidget *parent) : QWidget(parent) , modified(false) , saveable(false) { setupUi(this); if (qobject_cast(parent)) { verticalLayout->setMargin(4); } type->addItem(tr("Samba Share"), (int)Type_Samba); type->addItem(tr("Samba Share (Auto-discover host and port)"), (int)Type_SambaAvahi); type->addItem(tr("Secure Shell (sshfs)"), (int)Type_SshFs); type->addItem(tr("Locally Mounted Folder"), (int)Type_File); } void RemoteDevicePropertiesWidget::update(const RemoteFsDevice::Details &d, bool create, bool isConnected) { int t=create ? Type_Samba : d.isLocalFile() ? Type_File : d.url.scheme()==RemoteFsDevice::constSshfsProtocol ? Type_SshFs : d.url.scheme()==RemoteFsDevice::constSambaProtocol ? Type_Samba : Type_SambaAvahi; setEnabled(d.isLocalFile() || !isConnected); infoLabel->setVisible(create); orig=d; name->setText(d.name); sshPort->setValue(22); smbPort->setValue(445); connectionNote->setVisible(!d.isLocalFile() && isConnected); sshFolder->setText(QString()); sshHost->setText(QString()); sshUser->setText(QString()); fileFolder->setText(QString()); switch (t) { case Type_SshFs: { sshFolder->setText(d.url.path()); if (0!=d.url.port()) { sshPort->setValue(d.url.port()); } sshHost->setText(d.url.host()); sshUser->setText(d.url.userName()); sshExtra->setText(d.extraOptions); break; } case Type_File: fileFolder->setText(d.url.path()); break; case Type_Samba: { smbShare->setText(d.url.path()); if (0!=d.url.port()) { smbPort->setValue(d.url.port()); } smbHost->setText(d.url.host()); smbUser->setText(d.url.userName()); smbPassword->setText(d.url.password()); QUrlQuery q(d.url); if (q.hasQueryItem(RemoteFsDevice::constDomainQuery)) { smbDomain->setText(q.queryItemValue(RemoteFsDevice::constDomainQuery)); } else { smbDomain->setText(QString()); } break; } case Type_SambaAvahi: { smbAvahiShare->setText(d.url.path()); smbAvahiUser->setText(d.url.userName()); smbAvahiPassword->setText(d.url.password()); QUrlQuery q(d.url); if (q.hasQueryItem(RemoteFsDevice::constDomainQuery)) { smbAvahiDomain->setText(q.queryItemValue(RemoteFsDevice::constDomainQuery)); } else { smbAvahiDomain->setText(QString()); } if (q.hasQueryItem(RemoteFsDevice::constServiceNameQuery)) { smbAvahiName->setText(q.queryItemValue(RemoteFsDevice::constServiceNameQuery)); } else { smbAvahiName->setText(QString()); } break; } } name->setEnabled(d.isLocalFile() || !isConnected); connect(type, SIGNAL(currentIndexChanged(int)), this, SLOT(setType())); for (int i=1; icount(); ++i) { if (type->itemData(i).toInt()==t) { type->setCurrentIndex(i); stackedWidget->setCurrentIndex(i); break; } } connect(name, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(sshHost, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(sshUser, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(sshFolder, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(sshPort, SIGNAL(valueChanged(int)), this, SLOT(checkSaveable())); connect(sshExtra, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(fileFolder, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbHost, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbUser, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbPassword, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbDomain, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbShare, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbPort, SIGNAL(valueChanged(int)), this, SLOT(checkSaveable())); connect(smbAvahiName, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbAvahiUser, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbAvahiPassword, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbAvahiDomain, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); connect(smbAvahiShare, SIGNAL(textChanged(const QString &)), this, SLOT(checkSaveable())); modified=false; setType(); checkSaveable(); } void RemoteDevicePropertiesWidget::setType() { if (Type_SshFs==type->itemData(type->currentIndex()).toInt() && 0==sshPort->value()) { sshPort->setValue(22); } if (Type_Samba==type->itemData(type->currentIndex()).toInt() && 0==smbPort->value()) { smbPort->setValue(445); } } void RemoteDevicePropertiesWidget::checkSaveable() { RemoteFsDevice::Details det=details(); modified=det!=orig; saveable=!det.isEmpty(); if (saveable && Type_SambaAvahi==type->itemData(type->currentIndex()).toInt()) { saveable=!smbAvahiName->text().trimmed().isEmpty(); } emit updated(); } RemoteFsDevice::Details RemoteDevicePropertiesWidget::details() { int t=type->itemData(type->currentIndex()).toInt(); RemoteFsDevice::Details det; det.name=name->text().trimmed(); switch (t) { case Type_SshFs: { det.url.setHost(sshHost->text().trimmed()); det.url.setUserName(sshUser->text().trimmed()); det.url.setPath(sshFolder->text().trimmed()); det.url.setPort(sshPort->value()); det.url.setScheme(RemoteFsDevice::constSshfsProtocol); det.extraOptions=sshExtra->text().trimmed(); break; } case Type_File: { QString path=fileFolder->text().trimmed(); if (path.isEmpty()) { path="/"; } det.url.setPath(path); det.url.setScheme(RemoteFsDevice::constFileProtocol); break; } case Type_Samba: det.url.setHost(smbHost->text().trimmed()); det.url.setUserName(smbUser->text().trimmed()); det.url.setPath(smbShare->text().trimmed()); det.url.setPort(smbPort->value()); det.url.setScheme(RemoteFsDevice::constSambaProtocol); det.url.setPassword(smbPassword->text().trimmed()); if (!smbDomain->text().trimmed().isEmpty()) { QUrlQuery q; q.addQueryItem(RemoteFsDevice::constDomainQuery, smbDomain->text().trimmed()); det.url.setQuery(q); } break; case Type_SambaAvahi: det.url.setUserName(smbAvahiUser->text().trimmed()); det.url.setPath(smbAvahiShare->text().trimmed()); det.url.setPort(0); det.url.setScheme(RemoteFsDevice::constSambaAvahiProtocol); det.url.setPassword(smbAvahiPassword->text().trimmed()); if (!smbDomain->text().trimmed().isEmpty() || !smbAvahiName->text().trimmed().isEmpty()) { QUrlQuery q; if (!smbDomain->text().trimmed().isEmpty()) { q.addQueryItem(RemoteFsDevice::constDomainQuery, smbAvahiDomain->text().trimmed()); } if (!smbAvahiName->text().trimmed().isEmpty()) { det.serviceName=smbAvahiName->text().trimmed(); q.addQueryItem(RemoteFsDevice::constServiceNameQuery, det.serviceName); } det.url.setQuery(q); } break; } return det; } cantata-2.2.0/devices/remotedevicepropertieswidget.h000066400000000000000000000032371316350454000227230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef REMOTEDEVICEPROPERTIESWIDGET_H #define REMOTEDEVICEPROPERTIESWIDGET_H #include "ui_remotedevicepropertieswidget.h" #include "remotefsdevice.h" class RemoteDevicePropertiesWidget : public QWidget, Ui::RemoteDevicePropertiesWidget { Q_OBJECT public: RemoteDevicePropertiesWidget(QWidget *parent); virtual ~RemoteDevicePropertiesWidget() { } void update(const RemoteFsDevice::Details &d, bool create, bool isConnected); RemoteFsDevice::Details details(); const RemoteFsDevice::Details & origDetails() const { return orig; } bool isModified() const { return modified; } bool isSaveable() const { return saveable; } Q_SIGNALS: void updated(); private Q_SLOTS: void checkSaveable(); void setType(); private: RemoteFsDevice::Details orig; bool modified; bool saveable; }; #endif cantata-2.2.0/devices/remotedevicepropertieswidget.ui000066400000000000000000000361511316350454000231120ustar00rootroot00000000000000 RemoteDevicePropertiesWidget 0 0 516 447 These settings are only editable when the device is not connected. QFormLayout::ExpandingFieldsGrow Type: type Name: name Options 0 Host: smbHost Port: smbPort 65535 User: smbUser Domain: smbDomain Password: smbPassword QLineEdit::Password Share: smbShare If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Qt::Vertical 20 14 Service name: smbAvahiName User: smbAvahiUser Domain: smbAvahiDomain Password: smbAvahiPassword QLineEdit::Password Share: smbAvahiShare If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' true Qt::NoTextInteraction Qt::Vertical 20 14 Host: sshHost Port: sshPort 65535 User: sshUser Folder: sshFolder Extra Options: sshExtra Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. true Qt::NoTextInteraction Qt::Vertical 20 78 Folder: fileFolder This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. PathRequester QLineEdit
    support/pathrequester.h
    LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    PlainNoteLabel QLabel
    widgets/notelabel.h
    type type activated(int) stackedWidget setCurrentIndex(int) 65 22 101 94
    cantata-2.2.0/devices/remotefsdevice.cpp000066400000000000000000000522701316350454000202670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,f * Boston, MA 02110-1301, USA. */ #include "remotefsdevice.h" #include "config.h" #include "remotedevicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "actiondialog.h" #include "support/inputdialog.h" #include "support/utils.h" #include "http/httpserver.h" #include "support/configuration.h" #include "mountpoints.h" #include "mounterinterface.h" #include "avahi/avahi.h" #include "avahi/avahiservice.h" #include #include #include #include #include #include #include const QLatin1String RemoteFsDevice::constPromptPassword("-"); const QLatin1String RemoteFsDevice::constSshfsProtocol("sshfs"); const QLatin1String RemoteFsDevice::constFileProtocol("file"); const QLatin1String RemoteFsDevice::constDomainQuery("domain"); const QLatin1String RemoteFsDevice::constSambaProtocol("smb"); const QLatin1String RemoteFsDevice::constServiceNameQuery("name"); const QLatin1String RemoteFsDevice::constSambaAvahiProtocol("smb-avahi"); static const QLatin1String constCfgPrefix("RemoteFsDevice-"); static const QLatin1String constCfgKey("remoteFsDevices"); static QString mountPoint(const RemoteFsDevice::Details &details, bool create) { if (details.isLocalFile()) { return details.url.path(); } return Utils::cacheDir(QLatin1String("mount/")+details.name, create); } void RemoteFsDevice::Details::load(const QString &group) { Configuration cfg(constCfgPrefix+group); name=group; QString u=cfg.get("url", QString()); if (u.isEmpty()) { QString protocol=cfg.get("protocol", QString()); QString host=cfg.get("host", QString()); QString user=cfg.get("user", QString()); QString path=cfg.get("path", QString()); int port=cfg.get("port", 0); u=(protocol.isEmpty() ? "file" : protocol+"://")+user+(!user.isEmpty() && !host.isEmpty() ? "@" : "")+host+(0==port ? "" : (":"+QString::number(port)))+path; } if (!u.isEmpty()) { url=QUrl(u); } extraOptions=cfg.get("extraOptions", QString()); configured=cfg.get("configured", configured); } void RemoteFsDevice::Details::save() const { Configuration cfg(constCfgPrefix+name); cfg.set("url", url.toString()); cfg.set("extraOptions", extraOptions); cfg.set("configured", configured); } static inline bool isValid(const RemoteFsDevice::Details &d) { return d.isLocalFile() || RemoteFsDevice::constSshfsProtocol==d.url.scheme() || RemoteFsDevice::constSambaProtocol==d.url.scheme() || RemoteFsDevice::constSambaAvahiProtocol==d.url.scheme(); } static inline bool isMountable(const RemoteFsDevice::Details &d) { return RemoteFsDevice::constSshfsProtocol==d.url.scheme() || RemoteFsDevice::constSambaProtocol==d.url.scheme() || RemoteFsDevice::constSambaAvahiProtocol==d.url.scheme(); } QList RemoteFsDevice::loadAll(MusicLibraryModel *m) { QList devices; Configuration cfg; QStringList names=cfg.get(constCfgKey, QStringList()); foreach (const QString &n, names) { Details d; d.load(n); if (d.isEmpty()) { cfg.removeGroup(constCfgPrefix+n); } else if (isValid(d)) { devices.append(new RemoteFsDevice(m, d)); } } return devices; } Device * RemoteFsDevice::create(MusicLibraryModel *m, const DeviceOptions &options, const Details &d) { if (d.isEmpty()) { return 0; } Configuration cfg; QStringList names=cfg.get(constCfgKey, QStringList()); if (names.contains(d.name)) { return 0; } names.append(d.name); cfg.set(constCfgKey, names); d.save(); if (isValid(d)) { return new RemoteFsDevice(m, options, d); } return 0; } void RemoteFsDevice::destroy(bool removeFromConfig) { if (removeFromConfig) { Configuration cfg; QStringList names=cfg.get(constCfgKey, QStringList()); if (names.contains(details.name)) { names.removeAll(details.name); cfg.removeGroup(id()); cfg.set(constCfgKey, names); } } stopScanner(); if (isConnected()) { unmount(); } if (isMountable(details)) { QString mp=mountPoint(details, false); if (!mp.isEmpty()) { QDir d(mp); if (d.exists()) { d.rmdir(mp); } } } deleteLater(); } void RemoteFsDevice::serviceAdded(const QString &name) { if (name==details.serviceName && constSambaAvahiProtocol==details.url.scheme()) { sub=tr("Available"); updateStatus(); } } void RemoteFsDevice::serviceRemoved(const QString &name) { if (name==details.serviceName && constSambaAvahiProtocol==details.url.scheme()) { sub=tr("Not Available"); updateStatus(); } } QString RemoteFsDevice::createUdi(const QString &n) { return constCfgPrefix+n; } void RemoteFsDevice::renamed(const QString &oldName, const QString &newName) { Configuration cfg; QStringList names=cfg.get(constCfgKey, QStringList()); if (names.contains(oldName)) { names.removeAll(oldName); cfg.removeGroup(createUdi(oldName)); } if (!names.contains(newName)) { names.append(newName); } cfg.set(constCfgKey, names); } RemoteFsDevice::RemoteFsDevice(MusicLibraryModel *m, const DeviceOptions &options, const Details &d) : FsDevice(m, d.name, createUdi(d.name)) , mountToken(0) , currentMountStatus(false) , details(d) , proc(0) , mounterIface(0) , messageSent(false) { opts=options; // details.path=Utils::fixPath(details.path); load(); mount(); icn=details.isLocalFile() ? Icon("inode-directory") : constSshfsProtocol==details.url.scheme() ? Icon(QStringList() << "folder-network" << "utilities-terminal") : Icon(QStringList() << "folder-samba" << "network-server"); } RemoteFsDevice::RemoteFsDevice(MusicLibraryModel *m, const Details &d) : FsDevice(m, d.name, createUdi(d.name)) , mountToken(0) , currentMountStatus(false) , details(d) , proc(0) , mounterIface(0) , messageSent(false) { // details.path=Utils::fixPath(details.path); setup(); icn=Icon(details.isLocalFile() ? "inode-directory" : (constSshfsProtocol==details.url.scheme() ? "utilities-terminal" : "network-server")); } RemoteFsDevice::~RemoteFsDevice() { } void RemoteFsDevice::toggle() { if (isConnected()) { stopScanner(); unmount(); } else { mount(); } } MpdCantataMounterInterface * RemoteFsDevice::mounter() { if (!mounterIface) { if (!QDBusConnection::systemBus().interface()->isServiceRegistered(MpdCantataMounterInterface::staticInterfaceName())) { QDBusConnection::systemBus().interface()->startService(MpdCantataMounterInterface::staticInterfaceName()); } mounterIface=new MpdCantataMounterInterface(MpdCantataMounterInterface::staticInterfaceName(), "/Mounter", QDBusConnection::systemBus(), this); connect(mounterIface, SIGNAL(mountStatus(const QString &, int, int)), SLOT(mountStatus(const QString &, int, int))); connect(mounterIface, SIGNAL(umountStatus(const QString &, int, int)), SLOT(umountStatus(const QString &, int, int))); } return mounterIface; } void RemoteFsDevice::mount() { if (details.isLocalFile()) { return; } if (isConnected() || proc) { return; } if (messageSent) { return; } if (constSambaAvahiProtocol==details.url.scheme()) { Details det=details; AvahiService *srv=Avahi::self()->getService(det.serviceName); if (!srv || srv->getHost().isEmpty() || 0==srv->getPort()) { emit error(tr("Failed to resolve connection details for %1").arg(details.name)); return; } if (constPromptPassword==det.url.password()) { bool ok=false; QString passwd=InputDialog::getPassword(QString(), &ok, QApplication::activeWindow()); if (!ok) { return; } det.url.setPassword(passwd); } det.url.setScheme(constSambaProtocol); det.url.setHost(srv->getHost()); det.url.setPort(srv->getPort()); mounter()->mount(det.url.toString(), mountPoint(details, true), getuid(), getgid(), getpid()); setStatusMessage(tr("Connecting...")); messageSent=true; return; } if (constSambaProtocol==details.url.scheme()) { Details det=details; if (constPromptPassword==det.url.password()) { bool ok=false; QString passwd=InputDialog::getPassword(QString(), &ok, QApplication::activeWindow()); if (!ok) { return; } det.url.setPassword(passwd); } mounter()->mount(det.url.toString(), mountPoint(details, true), getuid(), getgid(), getpid()); setStatusMessage(tr("Connecting...")); messageSent=true; return; } QString cmd; QStringList args; if (!details.isLocalFile() && !details.isEmpty()) { // If user has added 'IdentityFile' to extra options, then no password prompting is required... bool needAskPass=!details.extraOptions.contains("IdentityFile="); if (needAskPass) { if (ttyname(0)) { emit error(tr("Password prompting does not work when cantata is started from the commandline.")); return; } QStringList askPassList; if (Utils::KDE==Utils::currentDe()) { askPassList << QLatin1String("ksshaskpass") << QLatin1String("ssh-askpass") << QLatin1String("ssh-askpass-gnome"); } else { askPassList << QLatin1String("ssh-askpass-gnome") << QLatin1String("ssh-askpass") << QLatin1String("ksshaskpass"); } QString askPass; foreach (const QString &ap, askPassList) { askPass=Utils::findExe(ap); if (!askPass.isEmpty()) { break; } } if (askPass.isEmpty()) { emit error(tr("No suitable ssh-askpass application installed! This is required for entering passwords.")); return; } } cmd=Utils::findExe("sshfs"); if (!cmd.isEmpty()) { if (!QDir(mountPoint(details, true)).entryList(QDir::NoDot|QDir::NoDotDot|QDir::AllEntries|QDir::Hidden).isEmpty()) { emit error(tr("Mount point (\"%1\") is not empty!").arg(mountPoint(details, true))); return; } args << details.url.userName()+QChar('@')+details.url.host()+QChar(':')+details.url.path()<< QLatin1String("-p") << QString::number(details.url.port()) << mountPoint(details, true) << QLatin1String("-o") << QLatin1String("ServerAliveInterval=15"); //<< QLatin1String("-o") << QLatin1String("Ciphers=arcfour"); if (!details.extraOptions.isEmpty()) { args << details.extraOptions.split(' ', QString::SkipEmptyParts); } } else { emit error(tr("\"sshfs\" is not installed!")); } } if (!cmd.isEmpty()) { setStatusMessage(tr("Connecting...")); proc=new QProcess(this); proc->setProperty("mount", true); connect(proc, SIGNAL(finished(int)), SLOT(procFinished(int))); proc->start(cmd, args, QIODevice::ReadOnly); } } void RemoteFsDevice::unmount() { if (details.isLocalFile()) { return; } if (!isConnected() || proc) { return; } if (messageSent) { return; } if (constSambaProtocol==details.url.scheme() || constSambaAvahiProtocol==details.url.scheme()) { mounter()->umount(mountPoint(details, false), getpid()); setStatusMessage(tr("Disconnecting...")); messageSent=true; return; } QString cmd; QStringList args; if (!details.isLocalFile()) { QString mp=mountPoint(details, false); if (!mp.isEmpty()) { cmd=Utils::findExe("fusermount"); if (!cmd.isEmpty()) { args << QLatin1String("-u") << QLatin1String("-z") << mp; } else { emit error(tr("\"fusermount\" is not installed!")); } } } if (!cmd.isEmpty()) { setStatusMessage(tr("Disconnecting...")); proc=new QProcess(this); proc->setProperty("unmount", true); connect(proc, SIGNAL(finished(int)), SLOT(procFinished(int))); proc->start(cmd, args, QIODevice::ReadOnly); } } void RemoteFsDevice::procFinished(int exitCode) { bool wasMount=proc->property("mount").isValid(); proc->deleteLater(); proc=0; if (0!=exitCode) { emit error(wasMount ? tr("Failed to connect to \"%1\"").arg(details.name) : tr("Failed to disconnect from \"%1\"").arg(details.name)); setStatusMessage(QString()); } else if (wasMount) { setStatusMessage(tr("Updating tracks...")); load(); emit connectionStateHasChanged(id(), true); } else { setStatusMessage(QString()); update=new MusicLibraryItemRoot; scanned=false; emit updating(id(), false); emit connectionStateHasChanged(id(), false); } } void RemoteFsDevice::mountStatus(const QString &mp, int pid, int st) { if (pid==getpid() && mp==mountPoint(details, false)) { messageSent=false; if (0!=st) { emit error(tr("Failed to connect to \"%1\"").arg(details.name)); setStatusMessage(QString()); } else { setStatusMessage(tr("Updating tracks...")); load(); emit connectionStateHasChanged(id(), true); } } } void RemoteFsDevice::umountStatus(const QString &mp, int pid, int st) { if (pid==getpid() && mp==mountPoint(details, false)) { messageSent=false; if (0!=st) { emit error(tr("Failed to disconnect from \"%1\"").arg(details.name)); setStatusMessage(QString()); } else { setStatusMessage(QString()); update=new MusicLibraryItemRoot; emit updating(id(), false); emit connectionStateHasChanged(id(), false); } } } bool RemoteFsDevice::isConnected() const { if (details.isLocalFile()) { if (QDir(details.url.path()).exists()) { return true; } clear(); return false; } if (mountToken==MountPoints::self()->currentToken()) { return currentMountStatus; } QString mp=mountPoint(details, false); if (mp.isEmpty()) { clear(); return false; } if (opts.useCache && !audioFolder.isEmpty() && QFile::exists(cacheFileName())) { currentMountStatus=true; return true; } mountToken=MountPoints::self()->currentToken(); currentMountStatus=MountPoints::self()->isMounted(mp); if (currentMountStatus) { return true; } clear(); return false; } bool RemoteFsDevice::isOldSshfs() { return constSshfsProtocol==details.url.scheme() && 0==spaceInfo.used() && 1073741824000==spaceInfo.size(); } double RemoteFsDevice::usedCapacity() { if (cacheProgress>-1) { return (cacheProgress*1.0)/100.0; } if (!isConnected()) { return -1.0; } spaceInfo.setPath(mountPoint(details, false)); if (isOldSshfs()) { return -1.0; } return spaceInfo.size()>0 ? (spaceInfo.used()*1.0)/(spaceInfo.size()*1.0) : -1.0; } QString RemoteFsDevice::capacityString() { if (cacheProgress>-1) { return statusMessage(); } if (!isConnected()) { return tr("Not Connected"); } spaceInfo.setPath(mountPoint(details, false)); if (isOldSshfs()) { return tr("Capacity Unknown"); } return tr("%1 free").arg(Utils::formatByteSize(spaceInfo.size()-spaceInfo.used())); } qint64 RemoteFsDevice::freeSpace() { if (!isConnected()) { // || !details.isLocalFile()) { return 0; } spaceInfo.setPath(mountPoint(details, false)); if (isOldSshfs()) { return 0; } return spaceInfo.size()-spaceInfo.used(); } void RemoteFsDevice::load() { if (RemoteFsDevice::constSambaAvahiProtocol==details.url.scheme()) { // Start Avahi listener... Avahi::self(); QUrlQuery q(details.url); if (q.hasQueryItem(constServiceNameQuery)) { details.serviceName=q.queryItemValue(constServiceNameQuery); } if (!details.serviceName.isEmpty()) { AvahiService *srv=Avahi::self()->getService(details.serviceName); if (!srv || srv->getHost().isEmpty()) { sub=tr("Not Available"); } else { sub=tr("Available"); } } connect(Avahi::self(), SIGNAL(serviceAdded(QString)), SLOT(serviceAdded(QString))); connect(Avahi::self(), SIGNAL(serviceRemoved(QString)), SLOT(serviceRemoved(QString))); } if (isConnected()) { setAudioFolder(); readOpts(settingsFileName(), opts, true); rescan(false); // Read from cache if we have it! } } void RemoteFsDevice::setup() { details.load(details.name); configured=details.configured; if (isConnected()) { readOpts(settingsFileName(), opts, true); } load(); } void RemoteFsDevice::setAudioFolder() const { audioFolder=Utils::fixPath(mountPoint(details, true)); } void RemoteFsDevice::configure(QWidget *parent) { if (isRefreshing()) { return; } RemoteDevicePropertiesDialog *dlg=new RemoteDevicePropertiesDialog(parent); connect(dlg, SIGNAL(updatedSettings(const DeviceOptions &, const RemoteFsDevice::Details &)), SLOT(saveProperties(const DeviceOptions &, const RemoteFsDevice::Details &))); if (!configured) { connect(dlg, SIGNAL(cancelled()), SLOT(saveProperties())); } dlg->show(opts, details, DevicePropertiesWidget::Prop_All-(DevicePropertiesWidget::Prop_Name+DevicePropertiesWidget::Prop_Folder+DevicePropertiesWidget::Prop_AutoScan), 0, false, isConnected()); } bool RemoteFsDevice::canPlaySongs() const { return details.isLocalFile() || HttpServer::self()->isAlive(); } static inline QString toString(bool b) { return b ? QLatin1String("true") : QLatin1String("false"); } void RemoteFsDevice::saveOptions() { opts.save(id()); } void RemoteFsDevice::saveProperties() { saveProperties(opts, details); } void RemoteFsDevice::saveProperties(const DeviceOptions &newOpts, const Details &nd) { bool connected=isConnected(); if (configured && (!connected || opts==newOpts) && (connected || details==nd)) { return; } bool isLocal=details.isLocalFile(); if (connected) { if (!configured) { details.configured=configured=true; details.save(); } if (opts.useCache!=newOpts.useCache) { if (opts.useCache) { saveCache(); } else if (opts.useCache && !newOpts.useCache) { removeCache(); } } opts=newOpts; writeOpts(settingsFileName(), opts, true); } if (!connected || isLocal) { Details newDetails=nd; Details oldDetails=details; bool newName=!oldDetails.name.isEmpty() && oldDetails.name!=newDetails.name; bool newDir=oldDetails.url.path()!=newDetails.url.path(); if (isLocal && newDir && opts.useCache) { removeCache(); } details=newDetails; details.configured=configured=true; details.save(); if (newName) { if (!details.isLocalFile()) { QString oldMount=mountPoint(oldDetails, false); if (!oldMount.isEmpty() && QDir(oldMount).exists()) { ::rmdir(QFile::encodeName(oldMount).constData()); } } setData(details.name); renamed(oldDetails.name, details.name); deviceId=createUdi(details.name); emit udiChanged(); m_itemData=details.name; setStatusMessage(QString()); } if (isLocal && newDir && scanned) { rescan(true); } } emit configurationChanged(); } QString RemoteFsDevice::settingsFileName() const { if (audioFolder.isEmpty()) { setAudioFolder(); } return audioFolder+constCantataSettingsFile; } cantata-2.2.0/devices/remotefsdevice.h000066400000000000000000000100521316350454000177240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef REMOTEFSDEVICE_H #define REMOTEFSDEVICE_H #include "fsdevice.h" #include "config.h" #include class MpdCantataMounterInterface; class QProcess; class RemoteFsDevice : public FsDevice { Q_OBJECT public: struct Details { Details() : configured(false) { } void load(const QString &group=QString()); void save() const; bool operator==(const Details &o) const { return url==o.url && extraOptions==o.extraOptions; } bool operator!=(const Details &o) const { return !(*this==o); } bool isEmpty() const { return name.isEmpty() || url.isEmpty(); } bool isLocalFile() const { return url.scheme().isEmpty() || constFileProtocol==url.scheme(); } QString name; QUrl url; QString serviceName; QString extraOptions; bool configured; }; static const QLatin1String constPromptPassword; static const QLatin1String constSshfsProtocol; static const QLatin1String constFileProtocol; static const QLatin1String constDomainQuery; static const QLatin1String constSambaProtocol; static const QLatin1String constServiceNameQuery; static const QLatin1String constSambaAvahiProtocol; static QList loadAll(MusicLibraryModel *m); static Device *create(MusicLibraryModel *m, const DeviceOptions &options, const Details &d); static void renamed(const QString &oldName, const QString &newName); static QString createUdi(const QString &n); RemoteFsDevice(MusicLibraryModel *m, const DeviceOptions &options, const Details &d); RemoteFsDevice(MusicLibraryModel *m, const Details &d); virtual ~RemoteFsDevice(); void toggle(); void mount(); void unmount(); bool supportsDisconnect() const { return !details.isLocalFile(); } bool isConnected() const; double usedCapacity(); QString capacityString(); qint64 freeSpace(); void saveOptions(); void configure(QWidget *parent); DevType devType() const { return RemoteFs; } bool canPlaySongs() const; void destroy(bool removeFromConfig=true); const Details & getDetails() const { return details; } QString subText() { return sub; } private Q_SLOTS: void serviceAdded(const QString &name); void serviceRemoved(const QString &name); Q_SIGNALS: void udiChanged(); void connectionStateHasChanged(const QString &id, bool connected); protected: void load(); void setup(); void setAudioFolder() const; private: bool isOldSshfs(); MpdCantataMounterInterface * mounter(); QString settingsFileName() const; protected Q_SLOTS: void saveProperties(); void saveProperties(const DeviceOptions &newOpts, const RemoteFsDevice::Details &newDetails); void procFinished(int exitCode); void mountStatus(const QString &mp, int pid, int st); void umountStatus(const QString &mp, int pid, int st); protected: mutable int mountToken; mutable bool currentMountStatus; Details details; QProcess *proc; // QString audioFolderSetting; MpdCantataMounterInterface *mounterIface; bool messageSent; QString sub; friend class DevicesModel; }; #endif cantata-2.2.0/devices/splitlabelwidget.cpp000066400000000000000000000062371316350454000206240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "splitlabelwidget.h" #include "support/squeezedtextlabel.h" #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater() SplitLabelWidget::SplitLabelWidget(QWidget *p) : QStackedWidget(p) { QWidget *singlePage=new QWidget(this); QBoxLayout *singleLayout=new QBoxLayout(QBoxLayout::TopToBottom, singlePage); single=new QLabel(singlePage); single->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter); single->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); singleLayout->addWidget(single); multiplePage=new QWidget(this); QFormLayout *multipleLayout=new QFormLayout(multiplePage); message=new QLabel(multiplePage); multipleLayout->setSpacing(0); multipleLayout->setWidget(0, QFormLayout::SpanningRole, message); addWidget(singlePage); addWidget(multiplePage); setCurrentIndex(0); } void SplitLabelWidget::setText(const QString &text) { setCurrentIndex(0); single->setText(text); } void SplitLabelWidget::setText(const QList > &details, const QString &msg) { if (details.isEmpty()) { setText(msg); return; } setCurrentIndex(1); if (details.count()!=labels.count()) { if (details.count()(multiplePage->layout()); int diff=details.count()-labels.count(); for (int i=0; iaddRow(l, v); labels.append(l); values.append(v); } } } if (msg.isEmpty()) { message->setVisible(false); } else { message->setVisible(true); message->setText(msg); } for (int i=0; isetText(details.at(i).first); values.at(i)->setText(QLatin1String(" ")+details.at(i).second); } } cantata-2.2.0/devices/splitlabelwidget.h000066400000000000000000000026051316350454000202640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SPLIT_LABEL_WIDGET #define SPLIT_LABEL_WIDGET #include #include #include class QLabel; class QStackedWidget; class SqueezedTextLabel; class SplitLabelWidget : public QStackedWidget { public: SplitLabelWidget(QWidget *p); void setText(const QString &text); void setText(const QList > &details, const QString &msg=QString()); private: QLabel *single; QWidget *multiplePage; QLabel *message; QList labels; QList values; }; #endif cantata-2.2.0/devices/synccollectionwidget.cpp000066400000000000000000000170261316350454000215170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "synccollectionwidget.h" #include "widgets/treeview.h" #include "widgets/toolbutton.h" #include "widgets/icons.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemalbum.h" #include "models/musiclibraryitemsong.h" #include "support/icon.h" #include "support/actioncollection.h" #include #include SyncCollectionWidget::SyncCollectionWidget(QWidget *parent, const QString &title) : QWidget(parent) , performedSearch(false) , searchTimer(0) { setupUi(this); titleLabel->setText(title); cfgButton->setIcon(Icons::self()->configureIcon); connect(cfgButton, SIGNAL(clicked(bool)), SIGNAL(configure())); proxy.setSourceModel(&model); tree->setModel(&proxy); tree->setPageDefaults(); tree->setUseSimpleDelegate(); search->setText(QString()); search->setPlaceholderText(tr("Search")); connect(&proxy, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); connect(search, SIGNAL(returnPressed()), this, SLOT(delaySearchItems())); connect(search, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems())); checkAction=new Action(tr("Check Items"), this); connect(checkAction, SIGNAL(triggered()), SLOT(checkItems())); unCheckAction=new Action(tr("Uncheck Items"), this); connect(unCheckAction, SIGNAL(triggered()), SLOT(unCheckItems())); tree->addAction(checkAction); tree->addAction(unCheckAction); tree->setContextMenuPolicy(Qt::ActionsContextMenu); QAction *expand=ActionCollection::get()->action("expandall"); QAction *collapse=ActionCollection::get()->action("collapseall"); if (expand && collapse) { tree->addAction(expand); tree->addAction(collapse); addAction(expand); addAction(collapse); connect(expand, SIGNAL(triggered()), this, SLOT(expandAll())); connect(collapse, SIGNAL(triggered()), this, SLOT(collapseAll())); } connect(tree, SIGNAL(itemsSelected(bool)), checkAction, SLOT(setEnabled(bool))); connect(tree, SIGNAL(itemsSelected(bool)), unCheckAction, SLOT(setEnabled(bool))); connect(tree, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(tree, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); } SyncCollectionWidget::~SyncCollectionWidget() { } void SyncCollectionWidget::update(const QSet &songs) { model.setSongs(songs); } QList SyncCollectionWidget::checkedSongs() const { QList songs; foreach (const Song *s, checked) { songs.append(*s); } qSort(songs); return songs; } void SyncCollectionWidget::dataChanged(const QModelIndex &tl, const QModelIndex &br) { bool haveChecked=numCheckedSongs()>0; QModelIndex firstIndex = proxy.mapToSource(tl); QModelIndex lastIndex = proxy.mapToSource(br); const MusicLibraryItem *item=static_cast(firstIndex.internalPointer()); switch (item->itemType()) { case MusicLibraryItem::Type_Artist: for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) { QModelIndex index=model.index(i, 0, firstIndex.parent()); const MusicLibraryItemArtist *artist=static_cast(index.internalPointer()); foreach (const MusicLibraryItem *alItem, artist->childItems()) { foreach (const MusicLibraryItem *sItem, static_cast(alItem)->childItems()) { songToggled(static_cast(sItem)); } } } break; case MusicLibraryItem::Type_Album: for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) { QModelIndex index=model.index(i, 0, firstIndex.parent()); const MusicLibraryItemAlbum *album=static_cast(index.internalPointer()); foreach (const MusicLibraryItem *sItem, album->childItems()) { songToggled(static_cast(sItem)); } } break; case MusicLibraryItem::Type_Song: for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) { QModelIndex index=model.index(i, 0, firstIndex.parent()); songToggled(static_cast(index.internalPointer())); } default: break; } if (haveChecked!=(numCheckedSongs()>0)) { emit selectionChanged(); } } void SyncCollectionWidget::songToggled(const MusicLibraryItemSong *song) { const Song &s=song->song(); if (Qt::Checked==song->checkState()) { checked.insert(&s); spaceRequired+=s.size; } else { checked.remove(&s); spaceRequired-=s.size; } } void SyncCollectionWidget::checkItems() { checkItems(true); } void SyncCollectionWidget::unCheckItems() { checkItems(false); } void SyncCollectionWidget::checkItems(bool c) { const QModelIndexList selected = tree->selectedIndexes(); if (0==selected.size()) { return; } foreach (const QModelIndex &idx, selected) { model.setData(proxy.mapToSource(idx), c, Qt::CheckStateRole); } } void SyncCollectionWidget::delaySearchItems() { if (search->text().trimmed().isEmpty()) { if (searchTimer) { searchTimer->stop(); } if (performedSearch) { tree->collapseToLevel(0); } searchItems(); performedSearch=false; } else { if (!searchTimer) { searchTimer=new QTimer(this); searchTimer->setSingleShot(true); connect(searchTimer, SIGNAL(timeout()), SLOT(searchItems())); } searchTimer->start(500); } } void SyncCollectionWidget::searchItems() { QString text=search->text().trimmed(); proxy.update(text); if (proxy.enabled() && !text.isEmpty()) { tree->expandAll(); } performedSearch=true; } void SyncCollectionWidget::expandAll() { QWidget *f=QApplication::focusWidget(); if (f && qobject_cast(f)) { static_cast(f)->expandAll(); } } void SyncCollectionWidget::collapseAll() { QWidget *f=QApplication::focusWidget(); if (f && qobject_cast(f)) { static_cast(f)->collapseAll(); } } void SyncCollectionWidget::itemClicked(const QModelIndex &index) { if (TreeView::getForceSingleClick() && !tree->checkBoxClicked(index)) { tree->setExpanded(index, !tree->isExpanded(index)); } } void SyncCollectionWidget::itemActivated(const QModelIndex &index) { if (!TreeView::getForceSingleClick()) { tree->setExpanded(index, !tree->isExpanded(index)); } } cantata-2.2.0/devices/synccollectionwidget.h000066400000000000000000000044771316350454000211720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _SYNCCOLLECTIONWIDGET_H_ #define _SYNCCOLLECTIONWIDGET_H_ #include "ui_synccollectionwidget.h" #include "mpd-interface/song.h" #include "models/musiclibrarymodel.h" #include "models/musiclibraryproxymodel.h" #include class QTimer; class Action; class Icon; class SyncCollectionWidget : public QWidget, Ui::SyncCollectionWidget { Q_OBJECT public: SyncCollectionWidget(QWidget *parent, const QString &title); virtual ~SyncCollectionWidget(); void clear() { model.clear(); } void update(const QSet &songs); void setSupportsAlbumArtistTag(bool s) { model.setSupportsAlbumArtistTag(s); } int numArtists() { return model.rowCount(); } int numCheckedSongs() const { return checked.count(); } QList checkedSongs() const; Q_SIGNALS: void selectionChanged(); void configure(); private Q_SLOTS: void dataChanged(const QModelIndex &tl, const QModelIndex &br); void checkItems(); void unCheckItems(); void delaySearchItems(); void searchItems(); void expandAll(); void collapseAll(); void itemClicked(const QModelIndex &index); void itemActivated(const QModelIndex &index); private: void checkItems(bool c); void songToggled(const MusicLibraryItemSong *song); private: bool performedSearch; MusicLibraryModel model; MusicLibraryProxyModel proxy; QTimer *searchTimer; QSet checked; quint64 spaceRequired; Action *checkAction; Action *unCheckAction; }; #endif cantata-2.2.0/devices/synccollectionwidget.ui000066400000000000000000000023701316350454000213460ustar00rootroot00000000000000 SyncCollectionWidget 0 75 true ToolButton QToolButton
    widgets/toolbutton.h
    LineEdit QLineEdit
    support/lineedit.h
    TreeView QTreeView
    widgets/treeview.h
    cantata-2.2.0/devices/syncdialog.cpp000066400000000000000000000203451316350454000174150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "syncdialog.h" #include "synccollectionwidget.h" #include "actiondialog.h" #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "mpd-interface/song.h" #include "mpd-interface/mpdconnection.h" #include "models/mpdlibrarymodel.h" #include "models/devicesmodel.h" #include "support/messagebox.h" #include "support/squeezedtextlabel.h" #include "widgets/icons.h" #include #include struct SyncSong : public Song { SyncSong(const Song &o) : Song(o) { } bool operator==(const SyncSong &o) const { return title==o.title && album==o.album && albumArtist()==o.albumArtist(); } bool operator<(const SyncSong &o) const { int compare=albumArtist().compare(o.albumArtist()); if (0!=compare) { return compare<0; } compare=album.compare(o.album); if (0!=compare) { return compare<0; } return title.compare(o.title)<0; } }; inline uint qHash(const SyncSong &key) { return qHash(key.albumArtist()+key.artist+key.title); } static void getDiffs(const QSet &s1, const QSet &s2, QSet &in1, QSet &in2) { QSet a; QSet b; foreach (const Song &s, s1) { a.insert(s); } foreach (const Song &s, s2) { b.insert(s); } QSet r=a-b; foreach (const Song &s, r) { in1.insert(s); } r=b-a; foreach (const Song &s, r) { in2.insert(s); } } static int iCount=0; int SyncDialog::instanceCount() { return iCount; } SyncDialog::SyncDialog(QWidget *parent) : Dialog(parent, "SyncDialog", QSize(680, 680)) , state(State_Lists) , currentDev(0) { iCount++; QWidget *mw=new QWidget(this); QVBoxLayout *l=new QVBoxLayout(mw); QSplitter *splitter=new QSplitter(mw); libWidget=new SyncCollectionWidget(splitter, tr("Library:")); devWidget=new SyncCollectionWidget(splitter, tr("Device:")); statusLabel=new SqueezedTextLabel(this); statusLabel->setText(tr("Loading all songs from library, please wait...")); splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); NoteLabel *noteLabel=new NoteLabel(this); noteLabel->setText(tr("Library lists only songs that are in your library, but not on the device. Likewise Device lists " "songs that are only on the device.
    " "Select songs from Library that you would like to copy to Device, " "and select songs from Device that you would like to copy to Library. " "Then press the Synchronize button.")); l->addWidget(splitter); l->addWidget(statusLabel); l->addWidget(noteLabel); libWidget->setEnabled(false); devWidget->setEnabled(false); setMainWidget(mw); setButtons(Cancel|Ok); setButtonText(Ok, tr("Synchronize")); enableButtonOk(false); setAttribute(Qt::WA_DeleteOnClose); setCaption(tr("Synchronize")); connect(libWidget, SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(devWidget, SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(libWidget, SIGNAL(configure()), SLOT(configure())); connect(devWidget, SIGNAL(configure()), SLOT(configure())); libOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); } SyncDialog::~SyncDialog() { iCount--; } void SyncDialog::sync(const QString &udi) { devUdi=udi; connect(MpdLibraryModel::self(), SIGNAL(songListing(QList,double)), this, SLOT(librarySongs(QList,double))); MpdLibraryModel::self()->listSongs(); show(); } void SyncDialog::copy(const QList &songs) { Device *dev=getDevice(); if (!dev) { return; } bool fromDev=sender()==devWidget; ActionDialog *dlg=new ActionDialog(this); connect(dlg, SIGNAL(completed()), SLOT(updateSongs())); dlg->copy(fromDev ? dev->id() : QString(), fromDev ? QString() : dev->id(), songs); } void SyncDialog::updateSongs() { Device *dev=getDevice(); if (!dev) { deleteLater(); hide(); return; } QSet devSongs=dev->allSongs(dev->options().fixVariousArtists); QSet inDev; QSet inLib; getDiffs(devSongs, libSongs, inDev, inLib); if (0==inDev.count() && 0==inLib.count()) { MessageBox::information(isVisible() ? this : parentWidget(), tr("Device and library are in sync.")); deleteLater(); hide(); return; } devWidget->setSupportsAlbumArtistTag(dev->supportsAlbumArtistTag()); devWidget->update(inDev); libWidget->update(inLib); libWidget->setEnabled(true); devWidget->setEnabled(true); libSongs.clear(); } void SyncDialog::librarySongs(const QList &songs, double pc) { if (songs.isEmpty()) { statusLabel->hide(); disconnect(MpdLibraryModel::self(), SIGNAL(songListing(QList,double)), this, SLOT(librarySongs(QList,double))); updateSongs(); } else { libSongs+=songs.toSet(); statusLabel->setText(tr("Loading all songs from library, please wait...%1%...").arg(pc)); } } void SyncDialog::selectionChanged() { enableButtonOk(libWidget->numCheckedSongs() || devWidget->numCheckedSongs()); } void SyncDialog::configure() { if (libWidget==sender()) { DevicePropertiesDialog *dlg=new DevicePropertiesDialog(this); connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &))); dlg->setCaption(tr("Local Music Library Properties")); dlg->show(MPDConnection::self()->getDetails().dir, libOptions, DevicePropertiesWidget::Prop_Basic|DevicePropertiesWidget::Prop_FileName); } else { Device *dev=getDevice(); if (dev) { dev->configure(this); } } } void SyncDialog::saveProperties(const QString &path, const DeviceOptions &opts) { Q_UNUSED(path) libOptions=opts; libOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true, false); } void SyncDialog::slotButtonClicked(int button) { switch(button) { case Ok: { Device *dev=getDevice(); if (dev) { setVisible(false); ActionDialog *dlg=new ActionDialog(parentWidget()); QList songs=libWidget->checkedSongs(); QString devId; devId=dev->id(); dlg->sync(devId, libWidget->checkedSongs(), devWidget->checkedSongs()); Dialog::slotButtonClicked(button); } break; } case Cancel: MpdLibraryModel::self()->cancelListing(); Dialog::slotButtonClicked(button); break; default: break; } } Device * SyncDialog::getDevice() { Device *dev=DevicesModel::self()->device(devUdi); if (!dev) { MessageBox::error(isVisible() ? this : parentWidget(), tr("Device has been removed!")); return 0; } if (currentDev && dev!=currentDev) { MessageBox::error(isVisible() ? this : parentWidget(), tr("Device has been changed?")); return 0; } if (dev->isIdle()) { return dev; } MessageBox::error(isVisible() ? this : parentWidget(), tr("Device is busy?")); return 0; } cantata-2.2.0/devices/syncdialog.h000066400000000000000000000036121316350454000170600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _SYNC_DIALOG_H_ #define _SYNC_DIALOG_H_ #include "support/dialog.h" #include "mpd-interface/song.h" #include "deviceoptions.h" class Device; class SyncCollectionWidget; class SqueezedTextLabel; class SyncDialog : public Dialog { Q_OBJECT enum State { State_Lists, State_CopyToDevice, State_CopyToLib }; public: static int instanceCount(); SyncDialog(QWidget *parent); virtual ~SyncDialog(); void sync(const QString &udi); private Q_SLOTS: void copy(const QList &songs); void librarySongs(const QList &songs, double pc); void selectionChanged(); void configure(); void saveProperties(const QString &path, const DeviceOptions &opts); private: void updateSongs(); void slotButtonClicked(int button); Device * getDevice(); private: State state; SqueezedTextLabel *statusLabel; QString devUdi; Device *currentDev; SyncCollectionWidget *devWidget; SyncCollectionWidget *libWidget; QSet libSongs; DeviceOptions libOptions; }; #endif cantata-2.2.0/devices/transcodingjob.cpp000066400000000000000000000103441316350454000202650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, */ #include "transcodingjob.h" #include "device.h" #include TranscodingJob::TranscodingJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const DeviceOptions &d, int co, const Song &s) : CopyJob(src, dest, d, co, s) , encoder(enc) , value(val) , process(0) , duration(-1) { } TranscodingJob::~TranscodingJob() { delete process; } void TranscodingJob::run() { QString src(updateTagsLocal()); if (src.isEmpty()) { return; } if (stopRequested) { emit result(Device::Cancelled); } else { QStringList parameters=encoder.params(value, src, destFile); process = new QProcess; process->setReadChannelMode(QProcess::MergedChannels); process->setReadChannel(QProcess::StandardOutput); connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(processOutput())); connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); QString cmd=parameters.takeFirst(); process->start(cmd, parameters); } } void TranscodingJob::stop() { if (process) { process->close(); process->deleteLater(); process=0; emit result(Device::Cancelled); } } void TranscodingJob::finished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) if (!process) { return; } if (stopRequested) { emit result(Device::Cancelled); return; } if (0==exitCode) { updateTagsDest(); copyCover(srcFile); } emit result(0==exitCode ? Device::Ok : Device::TranscodeFailed); } void TranscodingJob::processOutput() { if (stopRequested) { emit result(Device::Cancelled); return; } QString output = process->readAllStandardOutput().data(); if(output.simplified().isEmpty()) { return; } if (!data.isEmpty()) { output=data+output; } if (-1==duration) { duration = computeDuration(output); } if (duration>0) { qint64 prog = computeProgress(output); if (prog>-1) { setPercent((prog*100)/duration); } } if (!output.endsWith('\n') && !output.endsWith('\r')) { int last=output.lastIndexOf('\n'); if (-1==last) { last=output.lastIndexOf('\r'); } if (last>-1) { data=output.mid(last+1); } else { data=output; } } } inline qint64 TranscodingJob::computeDuration(const QString &output) { //We match something like "Duration: 00:04:33.60" QRegExp matchDuration("Duration: (\\d{2,}):(\\d{2}):(\\d{2})\\.(\\d{2})"); if(output.contains(matchDuration)) { //duration is in csec return matchDuration.cap(1).toLong() * 60 * 60 * 100 + matchDuration.cap(2).toInt() * 60 * 100 + matchDuration.cap(3).toInt() * 100 + matchDuration.cap(4).toInt(); } else { return -1; } } inline qint64 TranscodingJob::computeProgress(const QString &output) { //Output is like size= 323kB time=18.10 bitrate= 146.0kbits/s //We're going to use the "time" column, which counts the elapsed time in seconds. QRegExp matchTime("time=(\\d+)\\.(\\d{2})"); if(output.contains(matchTime)) { return matchTime.cap(1).toLong() * 100 + matchTime.cap(2).toInt(); } else { return -1; } } cantata-2.2.0/devices/transcodingjob.h000066400000000000000000000043771316350454000177430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond */ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * 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 . * ****************************************************************************************/ #ifndef TRANSCODING_JOB_H #define TRANSCODING_JOB_H #include "filejob.h" #include "encoders.h" #include class TranscodingJob : public CopyJob { Q_OBJECT public: explicit TranscodingJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const DeviceOptions &d=DeviceOptions(), int co=0, const Song &s=Song()); virtual ~TranscodingJob(); void stop(); private: void run(); private Q_SLOTS: void processOutput(); void finished(int exitCode, QProcess::ExitStatus exitStatus); private: inline qint64 computeDuration(const QString &output); inline qint64 computeProgress(const QString &output); private: Encoders::Encoder encoder; int value; QProcess *process; qint64 duration; //in csec QString data; }; #endif //TRANSCODING_JOB_H cantata-2.2.0/devices/umsdevice.cpp000066400000000000000000000227731316350454000172540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "umsdevice.h" #include "support/utils.h" #include "devicepropertiesdialog.h" #include "devicepropertieswidget.h" #include "actiondialog.h" #include #include #include #include #include "solid-lite/storagedrive.h" static const QLatin1String constSettingsFile("/.is_audio_player"); static const QLatin1String constMusicFolderKey("audio_folder"); static const QLatin1String constCollectionNameKey("collection_name"); UmsDevice::UmsDevice(MusicLibraryModel *m, Solid::Device &dev) : FsDevice(m, dev) , access(dev.as()) { spaceInfo.setPath(access->filePath()); QString details=QLatin1String(" (")+Utils::formatByteSize(spaceInfo.size()); QStringList udiParts=dev.udi().split(QLatin1Char('/'), QString::SkipEmptyParts); if (udiParts.length()>1) { details+=QLatin1String(" - ")+udiParts.last(); } if (!details.isEmpty()) { details+=QLatin1Char(')'); } defaultName=data()+details; setData(defaultName); setup(); icn=Icon(QStringList() << "drive-removable-media-usb-pendrive" << "drive-removable-media-usb" << "multimedia-player"); } UmsDevice::~UmsDevice() { } void UmsDevice::connectionStateChanged() { if (isConnected()) { spaceInfo.setPath(access->filePath()); setup(); if (opts.autoScan || scanned){ // Only scan if we are set to auto scan, or we have already scanned before... rescan(false); // Read from cache if we have it! } else { setStatusMessage(tr("Not Scanned")); } } else { clear(); } } void UmsDevice::toggle() { if (solidDev.isValid() && access && access->isValid()) { if (access->isAccessible()) { stopScanner(); access->teardown(); } else { access->setup(); } } } bool UmsDevice::isConnected() const { return solidDev.isValid() && access && access->isValid() && access->isAccessible(); } double UmsDevice::usedCapacity() { if (cacheProgress>-1) { return (cacheProgress*1.0)/100.0; } if (!isConnected()) { return -1.0; } return spaceInfo.size()>0 ? (spaceInfo.used()*1.0)/(spaceInfo.size()*1.0) : -1.0; } QString UmsDevice::capacityString() { if (cacheProgress>-1) { return statusMessage(); } if (!isConnected()) { return tr("Not Connected"); } return tr("%1 free").arg(Utils::formatByteSize(spaceInfo.size()-spaceInfo.used())); } qint64 UmsDevice::freeSpace() { if (!isConnected()) { return 0; } return spaceInfo.size()-spaceInfo.used(); } void UmsDevice::setup() { if (!isConnected()) { return; } QString path=spaceInfo.path(); audioFolder = path; QFile file(path+constSettingsFile); QString audioFolderSetting; QString n=data(); bool haveOpts=FsDevice::readOpts(path+constCantataSettingsFile, opts, false); if (file.open(QIODevice::ReadOnly|QIODevice::Text)) { configured=true; QTextStream in(&file); while (!in.atEnd()) { QString line = in.readLine(); if (line.startsWith(constMusicFolderKey+"=")) { audioFolderSetting=audioFolder=Utils::cleanPath(path+'/'+line.section('=', 1, 1)); if (!QDir(audioFolder).exists()) { audioFolder = path; } } else if (line.startsWith(constMusicFilenameSchemeKey+"=")) { QString scheme = line.section('=', 1, 1); //protect against empty setting. if( !scheme.isEmpty() ) { opts.scheme = scheme; } } else if (line.startsWith(constVfatSafeKey+"=")) { opts.vfatSafe = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constAsciiOnlyKey+"=")) { opts.asciiOnly = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constIgnoreTheKey+"=")) { opts.ignoreThe = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constReplaceSpacesKey+"=")) { opts.replaceSpaces = QLatin1String("true")==line.section('=', 1, 1); } else if (line.startsWith(constCollectionNameKey+"=")) { opts.name = line.section('=', 1, 1).trimmed(); } else { unusedParams+=line; } } } if (!configured) { configured=haveOpts; } if (opts.coverName.isEmpty()) { opts.coverName=constDefCoverFileName; } // No setting, see if any standard dirs exist in path... if (audioFolderSetting.isEmpty() || audioFolderSetting!=audioFolder) { QStringList dirs; dirs << QLatin1String("Music") << QLatin1String("MUSIC") << QLatin1String("Albums") << QLatin1String("ALBUMS"); foreach (const QString &d, dirs) { if (QDir(path+d).exists()) { audioFolder=path+d; break; } } } if (!audioFolder.endsWith('/')) { audioFolder+='/'; } if (opts.autoScan || scanned){ // Only scan if we are set to auto scan, or we have already scanned before... rescan(false); // Read from cache if we have it! } else { setStatusMessage(tr("Not Scanned")); } if (!opts.name.isEmpty() && opts.name!=n) { setData(opts.name); emit renamed(); } } void UmsDevice::configure(QWidget *parent) { if (!isIdle()) { return; } DevicePropertiesDialog *dlg=new DevicePropertiesDialog(parent); connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &))); if (!configured) { connect(dlg, SIGNAL(cancelled()), SLOT(saveProperties())); } DeviceOptions o=opts; if (o.name.isEmpty()) { o.name=data(); } dlg->show(audioFolder, o, DevicePropertiesWidget::Prop_All, qobject_cast(parent) ? DevicePropertiesWidget::Prop_Folder : 0); } void UmsDevice::saveProperties() { saveProperties(audioFolder, opts); } static inline QString toString(bool b) { return b ? QLatin1String("true") : QLatin1String("false"); } void UmsDevice::saveOptions() { if (!isConnected()) { return; } QString path=spaceInfo.path(); QFile file(path+constSettingsFile); QString fixedPath(audioFolder); if (fixedPath.startsWith(path)) { fixedPath=QLatin1String("./")+fixedPath.mid(path.length()); } DeviceOptions def; if (file.open(QIODevice::WriteOnly|QIODevice::Text)) { QTextStream out(&file); if (!fixedPath.isEmpty()) { out << constMusicFolderKey << '=' << fixedPath << '\n'; } if (opts.scheme!=def.scheme) { out << constMusicFilenameSchemeKey << '=' << opts.scheme << '\n'; } if (opts.scheme!=def.scheme) { out << constVfatSafeKey << '=' << toString(opts.vfatSafe) << '\n'; } if (opts.asciiOnly!=def.asciiOnly) { out << constAsciiOnlyKey << '=' << toString(opts.asciiOnly) << '\n'; } if (opts.ignoreThe!=def.ignoreThe) { out << constIgnoreTheKey << '=' << toString(opts.ignoreThe) << '\n'; } if (opts.replaceSpaces!=def.replaceSpaces) { out << constReplaceSpacesKey << '=' << toString(opts.replaceSpaces) << '\n'; } if (!opts.name.isEmpty() && opts.name!=defaultName) { out << constCollectionNameKey << '=' << opts.name << '\n'; } foreach (const QString &u, unusedParams) { out << u << '\n'; } } } void UmsDevice::saveProperties(const QString &newPath, const DeviceOptions &newOpts) { QString nPath=Utils::fixPath(newPath); if (configured && opts==newOpts && nPath==audioFolder) { return; } configured=true; QString newName=newOpts.name.isEmpty() ? defaultName : newOpts.name; bool diffName=opts.name!=newName; bool diffCacheSettings=opts.useCache!=newOpts.useCache; opts=newOpts; if (diffName) { setData(newName); } if (diffCacheSettings) { if (opts.useCache) { saveCache(); } else { removeCache(); } } emit configurationChanged(); QString oldPath=audioFolder; if (!isConnected()) { return; } audioFolder=nPath; saveOptions(); FsDevice::writeOpts(spaceInfo.path()+constCantataSettingsFile, opts, false); if (oldPath!=audioFolder) { rescan(); // Path changed, so we can ignore cache... } if (diffName) { emit renamed(); } } cantata-2.2.0/devices/umsdevice.h000066400000000000000000000032151316350454000167070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef UMSDEVICE_H #define UMSDEVICE_H #include "fsdevice.h" #include "solid-lite/storageaccess.h" class UmsDevice : public FsDevice { Q_OBJECT public: UmsDevice(MusicLibraryModel *m, Solid::Device &dev); virtual ~UmsDevice(); void connectionStateChanged(); void toggle(); bool isConnected() const; double usedCapacity(); QString capacityString(); qint64 freeSpace(); DevType devType() const { return Ums; } void saveOptions(); void configure(QWidget *parent); bool supportsDisconnect() const { return true; } private: void setup(); private Q_SLOTS: void saveProperties(); void saveProperties(const QString &newPath, const DeviceOptions &opts); private: QString defaultName; Solid::StorageAccess *access; QStringList unusedParams; }; #endif cantata-2.2.0/devices/valueslider.cpp000066400000000000000000000100101316350454000175640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * 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 . * ****************************************************************************************/ #include "valueslider.h" #include ValueSlider::ValueSlider(QWidget *parent) : QWidget(parent) { defaultSetting=0; QGridLayout *layout = new QGridLayout(this); valueTypeLabel = new QLabel(this); valueTypeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout->addWidget(valueTypeLabel, 0, 0, 1, 3); slider = new QSlider(this); slider->setOrientation(Qt::Horizontal); slider->setTickPosition(QSlider::TicksBelow); slider->setTickInterval(1); slider->setPageStep(2); slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout->addWidget(slider, 1, 0, 1, 3); leftLabel = new QLabel(this); layout->addWidget(leftLabel, 2, 0, 1, 1); midLabel = new QLabel(this); connect(slider, SIGNAL(valueChanged(int)), this, SLOT(onSliderChanged(int))); rightLabel = new QLabel(this); rightLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); layout->addWidget(rightLabel, 2, 2, 1, 1); layout->addWidget(midLabel, 3, 1, 1, 1); valueTypeLabel->setBuddy(slider); midLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); } void ValueSlider::setValues(const Encoders::Encoder &enc) { slider->setToolTip(enc.tooltip); valueTypeLabel->setToolTip(enc.tooltip); slider->setWhatsThis(enc.tooltip); valueTypeLabel->setWhatsThis(enc.tooltip); leftLabel->setText(QString()); midLabel->setText(QString()); rightLabel->setText(QString()); valueTypeLabel->setText(QString()); settings=enc.values; defaultSetting=0; if (enc.values.count()>1) { defaultSetting=enc.defaultValueIndex; valueTypeLabel->setText(enc.valueLabel); slider->setRange(0, enc.values.count()-1); slider->setValue(defaultSetting); onSliderChanged(defaultSetting); leftLabel->setText(enc.low); rightLabel->setText(enc.high); } onSliderChanged(defaultSetting); } void ValueSlider::setValue(int value) { if (settings.count()>1) { bool increase=settings.at(0).value=value) || (!increase && s.value<=value)) { break; } else { index++; } } slider->setValue(index); } } void ValueSlider::onSliderChanged(int value) { QString text=valuesetText(text); emit valueChanged(value); } cantata-2.2.0/devices/valueslider.h000066400000000000000000000040531316350454000172430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /**************************************************************************************** * Copyright (c) 2010 Téo Mrnjavac * * * * 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 . * ****************************************************************************************/ #ifndef VALUESLIDER_H #define VALUESLIDER_H #include #include #include "encoders.h" class ValueSlider : public QWidget { Q_OBJECT public: explicit ValueSlider(QWidget *parent=0); void setValues(const Encoders::Encoder &enc); void setValue(int value); int value() const { return slider->value(); } Q_SIGNALS: void valueChanged(int value); private Q_SLOTS: void onSliderChanged(int value); private: QLabel *valueTypeLabel; QSlider *slider; QLabel *midLabel; QLabel *leftLabel; QLabel *rightLabel; int defaultSetting; QList settings; }; #endif cantata-2.2.0/gui/000077500000000000000000000000001316350454000137135ustar00rootroot00000000000000cantata-2.2.0/gui/application.cpp000066400000000000000000000045371316350454000167330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "application.h" #include "settings.h" #include "support/proxystyle.h" #include "models/mpdlibrarymodel.h" #include "support/utils.h" #include "mpd-interface/mpdstats.h" #include "mpd-interface/mpdstatus.h" #include "support/thread.h" #include "tags/taghelperiface.h" #include "scrobbling/scrobbler.h" #include "support/fancytabwidget.h" #include "widgets/itemview.h" #include "widgets/groupedview.h" #include "widgets/actionitemdelegate.h" #include "http/httpserver.h" #include "config.h" void Application::init() { #if defined Q_OS_WIN ProxyStyle *proxy=new ProxyStyle(ProxyStyle::VF_Side); #elif defined Q_OS_MAC ProxyStyle *proxy=new ProxyStyle(ProxyStyle::VF_Side|ProxyStyle::VF_Top); #else ProxyStyle *proxy=new ProxyStyle(0); #endif QString theme = Settings::self()->style(); if (!theme.isEmpty()) { QStyle *s=QApplication::setStyle(theme); if (s) { proxy->setBaseStyle(s); } } qApp->setStyle(proxy); // Ensure these objects are created in the GUI thread... ThreadCleaner::self(); MPDStatus::self(); MPDStats::self(); #ifdef ENABLE_TAGLIB TagHelperIface::self(); #endif Scrobbler::self(); MpdLibraryModel::self(); // Ensure this is started before any MPD connection HttpServer::self(); Utils::initRand(); Song::initTranslations(); // Init sizes (before any widgets constructed!) ItemView::setup(); FancyTabWidget::setup(); GroupedView::setup(); ActionItemDelegate::setup(); } cantata-2.2.0/gui/application.h000066400000000000000000000021151316350454000163660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef APPLICATION_H #define APPLICATION_H #include "config.h" #include #if defined Q_OS_WIN #include "application_win.h" #elif defined Q_OS_MAC #include "application_mac.h" #else #include "application_qt.h" #endif #endif cantata-2.2.0/gui/application_mac.cpp000066400000000000000000000032131316350454000175410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "application_mac.h" #include "support/utils.h" #include "config.h" #include #include #include Application::Application(int &argc, char **argv) : SingleApplication(argc, argv) { setAttribute(Qt::AA_DontShowIconsInMenus, true); // Setup icon path... QStringList paths=QIcon::themeSearchPaths(); QString path=Utils::systemDir("icons"); if (!paths.contains(path)) { QIcon::setThemeSearchPaths(QStringList() << path << paths); } QIcon::setThemeName(QLatin1String("cantata")); // Set DYLD_LIBRARY_PATH so that Qt finds our openSSL libs QDir dir(argv[0]); dir.cdUp(); QByteArray ldPath = qgetenv("DYLD_LIBRARY_PATH"); if (!ldPath.isEmpty()) { ldPath=':'+ldPath; } ldPath = dir.absolutePath().toLocal8Bit()+ldPath; } cantata-2.2.0/gui/application_mac.h000066400000000000000000000021171316350454000172100ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef APPLICATION_MAC_H #define APPLICATION_MAC_H #include "singleapplication.h" class Application : public SingleApplication { public: static void init(); Application(int &argc, char **argv); virtual ~Application() { }; }; #endif cantata-2.2.0/gui/application_qt.cpp000066400000000000000000000040431316350454000174270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "application_qt.h" #include "config.h" #include #include #include #include static void setupIconTheme(Application *app) { QIcon::setThemeSearchPaths(QStringList() << CANTATA_SYS_ICONS_DIR << QIcon::themeSearchPaths()); QIcon::setThemeName(QLatin1String("cantata")); if (Utils::KDE!=Utils::currentDe()) { app->setAttribute(Qt::AA_DontShowIconsInMenus, true); } } Application::Application(int &argc, char **argv) : QApplication(argc, argv) { } bool Application::start() { if (QDBusConnection::sessionBus().registerService(CANTATA_REV_URL)) { setupIconTheme(this); return true; } loadFiles(); // ...and activate window! QDBusConnection::sessionBus().send(QDBusMessage::createMethodCall("mpd.cantata", "/org/mpris/MediaPlayer2", "", "Raise")); return false; } void Application::loadFiles() { QStringList args(arguments()); if (args.count()>1) { args.takeAt(0); QDBusMessage m = QDBusMessage::createMethodCall("mpd.cantata", "/cantata", "", "load"); QList a; a.append(args); m.setArguments(a); QDBusConnection::sessionBus().send(m); } } cantata-2.2.0/gui/application_qt.h000066400000000000000000000021441316350454000170740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef APPLICATION_QT_H #define APPLICATION_QT_H #include class Application : public QApplication { public: static void init(); Application(int &argc, char **argv); virtual ~Application() { } bool start(); void loadFiles(); }; #endif cantata-2.2.0/gui/application_win.cpp000066400000000000000000000026421316350454000176030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "application_win.h" #include "config.h" #include #include Application::Application(int &argc, char **argv) : SingleApplication(argc, argv) { installNativeEventFilter(this); QIcon::setThemeName(QLatin1String("cantata")); setAttribute(Qt::AA_DontShowIconsInMenus, true); } bool Application::nativeEventFilter(const QByteArray &, void *message, long *result) { MSG *msg = static_cast(message); if (msg && WM_POWERBROADCAST==msg->message && PBT_APMRESUMEAUTOMATIC==msg->wParam) { emit reconnect(); } return false; } cantata-2.2.0/gui/application_win.h000066400000000000000000000023401316350454000172430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef APPLICATION_WIN_H #define APPLICATION_WIN_H #include "singleapplication.h" #include class Application : public SingleApplication, public QAbstractNativeEventFilter { public: static void init(); Application(int &argc, char **argv); virtual ~Application() { } bool nativeEventFilter(const QByteArray &, void *message, long *result); }; #endif cantata-2.2.0/gui/cachesettings.cpp000066400000000000000000000264421316350454000172530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "cachesettings.h" #include "context/artistview.h" #include "context/albumview.h" #include "context/songview.h" #include "context/contextwidget.h" #include "context/wikipediasettings.h" #include "covers.h" #include "support/utils.h" #include "support/messagebox.h" #include "config.h" #include "support/thread.h" #include "settings.h" #include "widgets/basicitemdelegate.h" #include "models/streamsmodel.h" #include "online/podcastsearchdialog.h" #include "support/squeezedtextlabel.h" #include "scrobbling/scrobbler.h" #include #include #include #include #include #include #include #include static const int constMaxRecurseLevel=4; static void calculate(const QString &d, const QStringList &types, int &items, quint64 &space, int level=0) { if (!d.isEmpty() && levelstart(); } CacheItemCounter::~CacheItemCounter() { if (thread) { thread->stop(); } } void CacheItemCounter::getCount() { int items=0; quint64 space=0; calculate(dir, types, items, space); emit count(items, space); } void CacheItemCounter::deleteAll() { ::deleteAll(dir, types); getCount(); } CacheItem::CacheItem(const QString &title, const QString &d, const QStringList &t, QTreeWidget *p, Type ty) : QTreeWidgetItem(p, QStringList() << title) , counter(new CacheItemCounter(title, d, t)) , empty(true) , usedSpace(0) , type(ty) { connect(this, SIGNAL(getCount()), counter, SLOT(getCount()), Qt::QueuedConnection); connect(this, SIGNAL(deleteAll()), counter, SLOT(deleteAll()), Qt::QueuedConnection); connect(counter, SIGNAL(count(int, quint64)), this, SLOT(update(int, quint64)), Qt::QueuedConnection); connect(this, SIGNAL(updated()), p, SIGNAL(itemSelectionChanged())); } CacheItem::~CacheItem() { delete counter; } void CacheItem::update(int itemCount, quint64 space) { setText(1, QString::number(itemCount)); setText(2, Utils::formatByteSize(space)); empty=0==itemCount; usedSpace=space; setStatus(); emit updated(); } void CacheItem::setStatus(const QString &str) { QFont f(font(0)); if (!str.isEmpty()) { f.setItalic(true); setText(1, str); setText(2, str); } setFont(1, f); setFont(2, f); } void CacheItem::clean() { setStatus(tr("Deleting...")); emit deleteAll(); switch (type) { case Type_Covers: Covers::self()->clearNameCache(); break; case Type_ScaledCovers: Covers::self()->clearScaleCache(); break; default: break; } } void CacheItem::calculate() { setStatus(tr("Calculating...")); emit getCount(); } static inline void setResizeMode(QHeaderView *hdr, int idx, QHeaderView::ResizeMode mode) { hdr->setSectionResizeMode(idx, mode); } CacheTree::CacheTree(QWidget *parent) : QTreeWidget(parent) , calculated(false) { setHeaderLabels(QStringList() << tr("Name") << tr("Item Count") << tr("Space Used")); setAllColumnsShowFocus(true); setSelectionMode(QAbstractItemView::ExtendedSelection); setRootIsDecorated(false); setSortingEnabled(false); //setSortingEnabled(true); //sortByColumn(0, Qt::AscendingOrder); setResizeMode(header(), 0, QHeaderView::Stretch); setResizeMode(header(), 1, QHeaderView::Stretch); setResizeMode(header(), 2, QHeaderView::Stretch); header()->setStretchLastSection(true); setAlternatingRowColors(false); setItemDelegate(new BasicItemDelegate(this)); } CacheTree::~CacheTree() { } void CacheTree::showEvent(QShowEvent *e) { if (!calculated) { for (int i=0; i(topLevelItem(i))->calculate(); } calculated=true; } QTreeWidget::showEvent(e); } SpaceLabel::SpaceLabel(QWidget *p) : SqueezedTextLabel(p) { QFont f=font(); f.setItalic(true); setFont(f); update(tr("Calculating...")); } void SpaceLabel::update(int space) { update(Utils::formatByteSize(space)); } void SpaceLabel::update(const QString &text) { setText(tr("Total space used: %1").arg(text)); } CacheSettings::CacheSettings(QWidget *parent) : QWidget(parent) { int spacing=Utils::layoutSpacing(this); QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); int row=0; int col=0; QLabel *label=new QLabel(tr("Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's " "current cache usage."), this); label->setWordWrap(true); layout->addWidget(label, row++, col, 1, 2); layout->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Fixed, QSizePolicy::Fixed), row++, 0); tree=new CacheTree(this); layout->addWidget(tree, row++, col, 1, 2); new CacheItem(tr("Covers"), Utils::cacheDir(Covers::constCoverDir, false), QStringList() << "*.jpg" << "*.png", tree, CacheItem::Type_Covers); new CacheItem(tr("Scaled Covers"), Utils::cacheDir(Covers::constScaledCoverDir, false), QStringList() << "*.jpg" << "*.png", tree, CacheItem::Type_ScaledCovers); new CacheItem(tr("Backdrops"), Utils::cacheDir(ContextWidget::constCacheDir, false), QStringList() << "*.jpg" << "*.png", tree); new CacheItem(tr("Lyrics"), Utils::cacheDir(SongView::constLyricsDir, false), QStringList() << "*"+SongView::constExtension, tree); new CacheItem(tr("Artist Information"), Utils::cacheDir(ArtistView::constCacheDir, false), QStringList() << "*"+ArtistView::constInfoExt << "*"+ArtistView::constSimilarInfoExt << "*.json.gz" << "*.jpg" << "*.png", tree); new CacheItem(tr("Album Information"), Utils::cacheDir(AlbumView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt << "*.jpg" << "*.png", tree); new CacheItem(tr("Track Information"), Utils::cacheDir(SongView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt, tree); new CacheItem(tr("Stream Listings"), Utils::cacheDir(StreamsModel::constSubDir, false), QStringList() << "*"+StreamsModel::constCacheExt, tree); new CacheItem(tr("Podcast Directories"), Utils::cacheDir(PodcastSearchDialog::constCacheDir, false), QStringList() << "*"+PodcastSearchDialog::constExt, tree); new CacheItem(tr("Wikipedia Languages"), Utils::cacheDir(WikipediaSettings::constSubDir, false), QStringList() << "*.xml.gz", tree); new CacheItem(tr("Scrobble Tracks"), Utils::cacheDir(Scrobbler::constCacheDir, false), QStringList() << "*.xml.gz", tree); for (int i=0; itopLevelItemCount(); ++i) { connect(static_cast(tree->topLevelItem(i)), SIGNAL(updated()), this, SLOT(updateSpace())); } spaceLabel=new SpaceLabel(this); button=new QPushButton(tr("Delete All"), this); layout->addWidget(spaceLabel, row, 0, 1, 1); layout->addWidget(button, row++, 1, 1, 1); button->setEnabled(false); connect(tree, SIGNAL(itemSelectionChanged()), this, SLOT(controlButton())); connect(button, SIGNAL(clicked()), this, SLOT(deleteAll())); } CacheSettings::~CacheSettings() { } void CacheSettings::controlButton() { button->setEnabled(false); for (int i=0; itopLevelItemCount(); ++i) { CacheItem *item=static_cast(tree->topLevelItem(i)); if (item->isSelected() && !item->isEmpty()) { button->setEnabled(true); break; } } } void CacheSettings::deleteAll() { QList toDelete; for (int i=0; itopLevelItemCount(); ++i) { CacheItem *item=static_cast(tree->topLevelItem(i)); if (item->isSelected() && !item->isEmpty()) { toDelete.append(item); } } if (!toDelete.isEmpty()) { if (1==toDelete.count()) { if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Delete all '%1' items?").arg(toDelete.at(0)->name()), tr("Delete Cache Items"), StdGuiItem::del(), StdGuiItem::cancel())) { toDelete.first()->clean(); } } else if (toDelete.count()>1) { static const QChar constBullet(0x2022); QString items; foreach (CacheItem *i, toDelete) { items+=constBullet+QLatin1Char(' ')+i->name()+QLatin1Char('\n'); } if (MessageBox::No==MessageBox::warningYesNo(this, tr("Delete items from all selected categories?")+QLatin1String("\n\n")+items, tr("Delete Cache Items"), StdGuiItem::del(), StdGuiItem::cancel())) { return; } foreach (CacheItem *i, toDelete) { i->clean(); } } } } void CacheSettings::updateSpace() { quint64 space=0; for (int i=0; itopLevelItemCount(); ++i) { space+=static_cast(tree->topLevelItem(i))->spaceUsed(); } spaceLabel->update(space); } cantata-2.2.0/gui/cachesettings.h000066400000000000000000000054571316350454000167230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CACHESETTINGS_H #define CACHESETTINGS_H #include #include #include #include #include "support/squeezedtextlabel.h" class QPushButton; class Thread; class SpaceLabel : public SqueezedTextLabel { Q_OBJECT public: SpaceLabel(QWidget *p); void update(int space); void update(const QString &text); }; class CacheItemCounter : public QObject { Q_OBJECT public: CacheItemCounter(const QString &name, const QString &d, const QStringList &t); ~CacheItemCounter(); Q_SIGNALS: void count(int num, quint64 space); public Q_SLOTS: void getCount(); void deleteAll(); private: QString dir; QStringList types; Thread *thread; }; class CacheItem : public QObject, public QTreeWidgetItem { Q_OBJECT public: enum Type { Type_Covers, Type_ScaledCovers, Type_Other }; public: CacheItem(const QString &title, const QString &d, const QStringList &t, QTreeWidget *parent, Type ty=Type_Other); ~CacheItem(); void calculate(); void clean(); bool isEmpty() const { return empty; } QString name() const { return text(0); } int spaceUsed() const { return usedSpace; } Q_SIGNALS: void getCount(); void deleteAll(); void updated(); private Q_SLOTS: void update(int itemCount, quint64 space); private: void setStatus(const QString &str=QString()); private: CacheItemCounter *counter; bool empty; quint64 usedSpace; Type type; }; class CacheTree : public QTreeWidget { Q_OBJECT public: CacheTree(QWidget *parent); ~CacheTree(); void showEvent(QShowEvent *e); private: bool calculated; }; class CacheSettings : public QWidget { Q_OBJECT public: CacheSettings(QWidget *parent); ~CacheSettings(); private Q_SLOTS: void controlButton(); void deleteAll(); void updateSpace(); private: QTreeWidget *tree; SpaceLabel *spaceLabel; QPushButton *button; }; #endif cantata-2.2.0/gui/coverdialog.cpp000066400000000000000000001274711316350454000167310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "coverdialog.h" #include "support/messagebox.h" #include "widgets/listview.h" #include "network/networkaccessmanager.h" #include "settings.h" #include "mpd-interface/mpdconnection.h" #include "support/utils.h" #include "support/spinner.h" #include "widgets/messageoverlay.h" #include "support/icon.h" #include "widgets/icons.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DBUG if (Covers::debugEnabled()) qWarning() << "CoverDialog" << __FUNCTION__ static int iCount=0; static const int constMaxTempFiles=20; static QImage cropImage(QImage img, bool isArtist) { if (isArtist && img.width()!=img.height()) { int size=qMin(img.width(), img.height()); return img.copy((img.width()-size)/2, 0, size, size); } return img; } int CoverDialog::instanceCount() { return iCount; } // Only really want square-ish covers! bool canUse(int w, int h) { return w>90 && h>90 && (w==h || (h<=(w*1.1) && w<=(h*1.1))); } enum Providers { Prov_LastFm = 0x0001, Prov_Google = 0x0002, Prov_CoverArt = 0x0004, Prov_Deezer = 0x0008, Prov_Spotify = 0x0010, Prov_ITunes = 0x0020, Prov_All = 0x003F }; class CoverItem : public QListWidgetItem { public: CoverItem(const QString &u, const QString &tu, const QImage &img, const QString &text, QListWidget *parent, int w=-1, int h=-1, int sz=-1) : QListWidgetItem(parent) , imgUrl(u) , thmbUrl(tu) , list(parent) { setSizeHint(parent->gridSize()); setTextAlignment(Qt::AlignHCenter | Qt::AlignTop); setToolTip(u); if (!img.isNull()) { setImage(img); } if (sz>0) { setText(QObject::tr("%1\n%2 x %3 (%4)", "name\nwidth x height (file size)").arg(text).arg(w).arg(h).arg(Utils::formatByteSize(sz))); } else if (w>0 && h>0) { setText(QObject::tr("%1\n%2 x %3", "name\nwidth x height").arg(text).arg(w).arg(h)); } else { setText(text); } } const QString & url() const { return imgUrl; } const QString & thumbUrl() const { return thmbUrl; } virtual bool isLocal() const { return false; } virtual bool isExisting() const { return false; } protected: void setImage(const QImage &img) { int size=list && list->parentWidget() && qobject_cast(list->parentWidget()) ? static_cast(list->parentWidget())->imageSize() : 100; QPixmap pix=QPixmap::fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (pix.width()setRange(0, 100); imageLabel->setBackgroundRole(QPalette::Base); imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); imageLabel->setScaledContents(true); scrollArea = new QScrollArea(mw); scrollArea->setBackgroundRole(QPalette::Dark); scrollArea->setWidget(imageLabel); layout->addWidget(loadingLabel); layout->addWidget(pbar); layout->addWidget(scrollArea); layout->setMargin(0); setMainWidget(mw); } void CoverPreview::showImage(const QImage &img, const QString &u) { if (u==url) { zoom=1.0; url=u; loadingLabel->hide(); pbar->hide(); imageLabel->setPixmap(QPixmap::fromImage(img)); imageLabel->adjustSize(); scrollArea->show(); QApplication::processEvents(); adjustSize(); QStyleOptionFrame opt; opt.init(scrollArea); int fw=style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, scrollArea); if (fw<0) { fw=2; } fw*=2; QRect desktop = qApp->desktop()->screenGeometry(this); int maxWidth=desktop.width()*0.75; int maxHeight=desktop.height()*0.75; int lrPad=width()-mainWidget()->width(); int tbPad=height()-mainWidget()->height(); imgW=img.width(); imgH=img.height(); resize(lrPad+qMax(100, qMin(maxWidth, imgW+fw)), tbPad+qMax(100, qMin(maxHeight, imgH+fw))); setWindowTitle(tr("Image (%1 x %2 %3%)", "Image (width x height zoom%)").arg(imgW).arg(imgH).arg(zoom*100)); show(); } } void CoverPreview::downloading(const QString &u) { url=u; if (!url.isEmpty()) { loadingLabel->show(); scrollArea->hide(); pbar->show(); pbar->setValue(0); int spacing=Utils::layoutSpacing(this); QApplication::processEvents(); adjustSize(); resize(qMin(200, loadingLabel->width()+32), loadingLabel->height()+pbar->height()+(3*spacing)); show(); } } void CoverPreview::progress(qint64 rx, qint64 total) { pbar->setValue((int)(((rx*100.0)/(total*1.0))+0.5)); } void CoverPreview::scaleImage(int adjust) { double newZoom=zoom+(adjust*0.25); if (newZoom<0.25 || newZoom>4.0 || (fabs(newZoom-zoom)<0.01)) { return; } zoom=newZoom; imageLabel->resize(zoom * imageLabel->pixmap()->size()); setWindowTitle(tr("Image (%1 x %2 %3%)", "Image (width x height zoom%)").arg(imgW).arg(imgH).arg(zoom*100)); } void CoverPreview::wheelEvent(QWheelEvent *event) { if (scrollArea->isVisible() && QApplication::keyboardModifiers() & Qt::ControlModifier) { const int numDegrees = event->delta() / 8; const int numSteps = numDegrees / 15; if (0!=numSteps) { scaleImage(numSteps); } event->accept(); return; } Dialog::wheelEvent(event); } CoverDialog::CoverDialog(QWidget *parent) : Dialog(parent, "CoverDialog") , existing(0) , currentQueryProviders(0) , preview(0) , saving(false) , isArtist(false) , spinner(0) , msgOverlay(0) , page(0) , menu(0) , showAction(0) , removeAction(0) { Configuration cfg(objectName()); enabledProviders=cfg.get("enabledProviders", (int)Prov_All); iCount++; QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); setAttribute(Qt::WA_DeleteOnClose); setButtons(Cancel|Ok); enableButton(Ok, false); connect(list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(showImage(QListWidgetItem*))); connect(list, SIGNAL(itemSelectionChanged()), SLOT(checkStatus())); connect(search, SIGNAL(clicked()), SLOT(sendQuery())); connect(query, SIGNAL(returnPressed()), SLOT(sendQuery())); connect(addFileButton, SIGNAL(clicked()), SLOT(addLocalFile())); connect(list, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(menuRequested(const QPoint&))); QFont f(list->font()); QFontMetrics origFm(f); iSize=origFm.height()*7; iSize=((iSize/10)*10)+((iSize%10) ? 10 : 0); f.setPointSizeF(f.pointSizeF()*0.75); QFontMetrics fm(f); list->setFont(f); list->setAcceptDrops(false); list->setContextMenuPolicy(Qt::CustomContextMenu); list->setDragDropMode(QAbstractItemView::NoDragDrop); list->setDragEnabled(false); list->setDropIndicatorShown(false); list->setMovement(QListView::Static); list->setGridSize(QSize(imageSize()+10, imageSize()+10+(2.25*fm.height()))); list->setIconSize(QSize(imageSize(), imageSize())); int spacing=style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical); if (spacing<0) { spacing=4; } list->setSpacing(spacing); list->setViewMode(QListView::IconMode); list->setResizeMode(QListView::Adjust); list->setMinimumSize((list->gridSize().width()*3)+style()->pixelMetric(QStyle::PM_ScrollBarExtent)+spacing, list->gridSize().height()*2); list->setSortingEnabled(false); addFileButton->setIcon(Icons::self()->folderListIcon); addFileButton->setAutoRaise(true); configureButton->setIcon(Icons::self()->configureIcon); configureButton->setAutoRaise(true); QMenu *configMenu=new QMenu(configureButton); addProvider(configMenu, QLatin1String("Last.fm"), Prov_LastFm, enabledProviders); addProvider(configMenu, tr("CoverArt Archive"), Prov_CoverArt, enabledProviders); addProvider(configMenu, QLatin1String("Google"), Prov_Google, enabledProviders); addProvider(configMenu, QLatin1String("Deezer"), Prov_Deezer, enabledProviders); addProvider(configMenu, QLatin1String("Spotify"), Prov_Spotify, enabledProviders); addProvider(configMenu, QLatin1String("iTunes"), Prov_ITunes, enabledProviders); configureButton->setMenu(configMenu); configureButton->setPopupMode(QToolButton::InstantPopup); setAcceptDrops(true); } CoverDialog::~CoverDialog() { Configuration cfg(objectName()); cfg.set("enabledProviders", enabledProviders); iCount--; cancelQuery(); clearTempFiles(); } void CoverDialog::show(const Song &s, const Covers::Image ¤t) { song=s; isArtist=song.isArtistImageRequest(); Covers::Image img=current.img.isNull() ? Covers::locateImage(song) : current; if (img.validFileName() && !QFileInfo(img.fileName).isWritable()) { MessageBox::error(parentWidget(), (isArtist ? tr("An image already exists for this artist, and the file is not writeable.") : tr("A cover already exists for this album, and the file is not writeable."))+ QString("

    %1").arg(img.fileName)); deleteLater(); return; } note->setVisible(!isArtist); if (isArtist) { setCaption(tr("'%1' Artist Image").arg(song.albumArtist())); } else { setCaption(tr("'%1 - %2' Album Cover", "'Artist - Album' Album Cover").arg(song.albumArtist()).arg(song.album)); } if (!img.img.isNull()) { existing=new ExistingCover(isArtist ? Covers::Image(cropImage(img.img, true), img.fileName) : img, list); list->addItem(existing); } /* Add other images in the source folder? #716 if (!isArtist) { QString dirName=MPDConnection::self()->getDetails().dir+Utils::getDir(s.file); QStringList files=QDir(dirName).entryList(QStringList() << QLatin1String("*.jpg") << QLatin1String("*.png"), QDir::Files|QDir::Readable); qWarning() << files; foreach (const QString &f, files) { QString fileName=dirName+f; if (fileName!=img.fileName) { QImage i(fileName); if (!i.isNull()) { currentLocalCovers.insert(fileName); insertItem(new LocalCover(fileName, i, list)); } } } } */ query->setText(isArtist ? song.albumArtist() : QString(song.albumArtist()+QLatin1String(" ")+song.album)); Dialog::show(); sendQuery(); } static const char * constHostProperty="host"; static const char * constLargeProperty="large"; static const char * constThumbProperty="thumb"; static const char * constWidthProperty="w"; static const char * constHeightProperty="h"; static const char * constSizeProperty="sz"; static const char * constTypeProperty="type"; static const char * constLastFmHost="ws.audioscrobbler.com"; static const char * constGoogleHost="images.google.com"; static const char * constCoverArtArchiveHost="coverartarchive.org"; static const char * constSpotifyHost="ws.spotify.com"; static const char * constITunesHost="itunes.apple.com"; static const char * constDeezerHost="api.deezer.com"; void CoverDialog::queryJobFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } reply->deleteLater(); if (!currentQuery.contains(reply)) { return; } DBUG << reply->origUrl().toString() << reply->ok(); currentQuery.remove(reply); if (reply->ok()) { QString host=reply->property(constHostProperty).toString(); QByteArray resp=reply->readAll(); if (constLastFmHost==host) { parseLastFmQueryResponse(resp); } else if (constGoogleHost==host) { parseGoogleQueryResponse(resp); } else if (constCoverArtArchiveHost==host) { parseCoverArtArchiveQueryResponse(resp); } else if (constSpotifyHost==host) { parseSpotifyQueryResponse(resp); } else if (constITunesHost==host) { parseITunesQueryResponse(resp); } else if (constDeezerHost==host) { parseDeezerQueryResponse(resp); } } if (currentQuery.isEmpty()) { setSearching(false); } } void CoverDialog::insertItem(CoverItem *item) { list->addItem(item); if (item->isLocal()) { list->scrollToItem(item); list->setItemSelected(item, true); } } void CoverDialog::downloadJobFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } reply->deleteLater(); if (!currentQuery.contains(reply)) { return; } DownloadType dlType=(DownloadType)reply->property(constTypeProperty).toInt(); if (DL_LargeSave==dlType) { saving=false; } DBUG << reply->origUrl().toString() << reply->ok(); currentQuery.remove(reply); if (reply->ok()) { QString host=reply->property(constHostProperty).toString(); QString url=reply->url().toString(); QByteArray data=reply->readAll(); const char *format=Covers::imageFormat(data); QImage img=QImage::fromData(data, format); if (!img.isNull()) { bool isLarge=reply->property(constThumbProperty).toString().isEmpty(); QTemporaryFile *temp=0; if (isLarge || (reply->property(constThumbProperty).toString()==reply->property(constLargeProperty).toString())) { temp=new QTemporaryFile(QDir::tempPath()+"/cantata_XXXXXX."+(format ? QString(QLatin1String(format)).toLower() : "png")); if (temp->open()) { if (!format) { img.save(temp, "PNG"); } else { temp->write(data); } if (tempFiles.size()>=constMaxTempFiles) { QTemporaryFile *last=tempFiles.takeLast(); last->remove(); delete last; } temp->close(); temp->setProperty(constLargeProperty, reply->property(constLargeProperty)); tempFiles.prepend(temp); } else { delete temp; temp=0; } } if (isLarge) { if (DL_LargePreview==dlType) { previewDialog()->showImage(cropImage(img, isArtist), reply->property(constLargeProperty).toString()); } else if (DL_LargeSave==dlType) { if (!temp) { MessageBox::error(this, tr("Failed to set cover!\n\nCould not download to temporary file!")); } else if (saveCover(temp->fileName(), img)) { accept(); } } } else { CoverItem *item=0; img=cropImage(img, isArtist); if (constLastFmHost==host) { item=new LastFmCover(reply->property(constLargeProperty).toString(), url, img, list); } else if (constGoogleHost==host) { item=new GoogleCover(reply->property(constLargeProperty).toString(), url, img, reply->property(constWidthProperty).toInt(), reply->property(constHeightProperty).toInt(), reply->property(constSizeProperty).toInt(), list); } else if (constCoverArtArchiveHost==host) { item=new CoverArtArchiveCover(reply->property(constLargeProperty).toString(), url, img, list); } else if (constSpotifyHost==host) { item=new SpotifyCover(reply->property(constLargeProperty).toString(), url, img, list); } else if (constITunesHost==host) { item=new ITunesCover(reply->property(constLargeProperty).toString(), url, img, list); } else if (constDeezerHost==host) { item=new DeezerCover(reply->property(constLargeProperty).toString(), url, img, list); } if (item) { insertItem(item); } } } } else if (reply->property(constThumbProperty).toString().isEmpty()) { if (preview && preview->aboutToShow(reply->property(constLargeProperty).toString())) { preview->hide(); } MessageBox::error(this, tr("Failed to download image!")); } if (currentQuery.isEmpty()) { setSearching(false); } } void CoverDialog::showImage(QListWidgetItem *item) { if (saving) { return; } CoverItem *cover=static_cast(item); if (cover->isExisting()) { previewDialog()->downloading(cover->url()); previewDialog()->showImage(static_cast(cover)->image(), cover->url()); } else if (cover->isLocal()) { previewDialog()->downloading(cover->url()); previewDialog()->showImage(static_cast(cover)->image(), cover->url()); } else { previewDialog()->downloading(cover->url()); NetworkJob *j=downloadImage(cover->url(), DL_LargePreview); if (j) { j->setProperty(constLargeProperty, cover->url()); connect(j, SIGNAL(downloadProgress(qint64, qint64)), preview, SLOT(progress(qint64, qint64))); } } } CoverPreview *CoverDialog::previewDialog() { if (!preview) { preview=new CoverPreview(this); } return preview; } void CoverDialog::sendQuery() { if (saving) { return; } QString fixedQuery(query->text().trimmed()); fixedQuery.remove(QChar('?')); if (fixedQuery.isEmpty()) { return; } if (currentQueryString==fixedQuery && enabledProviders==currentQueryProviders) { page++; } else { page=0; } if (0==page) { QList keep; while (list->count()) { CoverItem *item=static_cast(list->takeItem(0)); if (item->isExisting() || item->isLocal()) { keep.append(item); } else { currentUrls.remove(item->url()); currentUrls.remove(item->thumbUrl()); delete item; } } foreach (CoverItem *item, keep) { list->addItem(item); } cancelQuery(); } currentQueryString=fixedQuery; if (enabledProviders&Prov_LastFm) { sendLastFmQuery(fixedQuery, page); } if (enabledProviders&Prov_Google) { sendGoogleQuery(fixedQuery, page); } if (page==0) { if (enabledProviders&Prov_Spotify) { sendSpotifyQuery(fixedQuery); } if (enabledProviders&Prov_ITunes) { sendITunesQuery(fixedQuery); } if (enabledProviders&Prov_Deezer) { sendDeezerQuery(fixedQuery); } } currentQueryProviders=enabledProviders; setSearching(!currentQuery.isEmpty()); } void CoverDialog::sendLastFmQuery(const QString &fixedQuery, int page) { QUrl url; QUrlQuery query; url.setScheme("https"); url.setHost(constLastFmHost); url.setPath("/2.0/"); query.addQueryItem("api_key", Covers::constLastFmApiKey); query.addQueryItem("limit", QString::number(20)); query.addQueryItem("page", QString::number(page+1)); query.addQueryItem(isArtist ? "artist" : "album", fixedQuery); query.addQueryItem("method", isArtist ? "artist.search" : "album.search"); url.setQuery(query); sendQueryRequest(url); } void CoverDialog::sendGoogleQuery(const QString &fixedQuery, int page) { QUrl url; QUrlQuery query; url.setScheme("http"); url.setHost(constGoogleHost); url.setPath("/images"); query.addQueryItem("q", fixedQuery); query.addQueryItem("gbv", QChar('1')); query.addQueryItem("filter", QChar('1')); query.addQueryItem("start", QString::number(20 * page)); url.setQuery(query); sendQueryRequest(url); } void CoverDialog::sendSpotifyQuery(const QString &fixedQuery) { #ifdef QT_NO_SSL return; #else if (!QSslSocket::supportsSsl()) { return; } QUrl url; QUrlQuery query; url.setScheme("http"); url.setHost(constSpotifyHost); url.setPath(isArtist ? "/search/1/artist.json" : "/search/1/album.json"); query.addQueryItem("q", fixedQuery); url.setQuery(query); sendQueryRequest(url); #endif } void CoverDialog::sendITunesQuery(const QString &fixedQuery) { if (isArtist) { // TODO??? return; } QUrl url; QUrlQuery query; url.setScheme("http"); url.setHost(constITunesHost); url.setPath("/search"); query.addQueryItem("term", fixedQuery); query.addQueryItem("limit", QString::number(10)); query.addQueryItem("media", "music"); query.addQueryItem("entity", "album"); url.setQuery(query); sendQueryRequest(url); } void CoverDialog::sendDeezerQuery(const QString &fixedQuery) { QUrl url; QUrlQuery query; url.setScheme("http"); url.setHost(constDeezerHost); url.setPath(isArtist ? "/search/artist" : "/search/album"); query.addQueryItem("q", fixedQuery); query.addQueryItem("nb_items", QString::number(10)); query.addQueryItem("output", "json"); url.setQuery(query); sendQueryRequest(url); } void CoverDialog::checkStatus() { QList items=list->selectedItems(); enableButtonOk(1==items.size() && !static_cast(items.at(0))->isExisting()); } void CoverDialog::cancelQuery() { foreach (NetworkJob *job, currentQuery) { job->cancelAndDelete(); } currentQuery.clear(); setSearching(false); } void CoverDialog::addLocalFile() { QString fileName=QFileDialog::getOpenFileName(this, tr("Load Local Cover"), QDir::homePath(), tr("Images (*.png *.jpg)")); if (!fileName.isEmpty()) { if (currentLocalCovers.contains(fileName)) { MessageBox::error(this, tr("File is already in list!")); } else { QImage img(fileName); if (img.isNull()) { MessageBox::error(this, tr("Failed to read image!")); } else { currentLocalCovers.insert(fileName); insertItem(new LocalCover(fileName, img, list)); } } } } void CoverDialog::menuRequested(const QPoint &pos) { if (!menu) { menu=new QMenu(list); showAction=menu->addAction(Icon("zoom-original"), tr("Display")); removeAction=menu->addAction(Icon("list-remove"), tr("Remove")); connect(showAction, SIGNAL(triggered()), SLOT(showImage())); connect(removeAction, SIGNAL(triggered()), SLOT(removeImages())); } QList items=list->selectedItems(); showAction->setEnabled(1==items.count()); removeAction->setEnabled(!items.isEmpty()); if (removeAction->isEnabled()) { foreach (QListWidgetItem *i, items) { if (static_cast(i)->isExisting()) { removeAction->setEnabled(false); } } } menu->popup(list->mapToGlobal(pos)); } void CoverDialog::showImage() { QList items=list->selectedItems(); if (1==items.count()) { showImage(items.at(0)); } } void CoverDialog::removeImages() { QList items=list->selectedItems(); foreach (QListWidgetItem *i, items) { delete i; } } void CoverDialog::updateProviders() { QAction *s=qobject_cast(sender()); enabledProviders=0; if (s) { if (Prov_LastFm==s->data().toUInt() && !s->isChecked()) { providers[Prov_CoverArt]->setChecked(false); } else if (Prov_CoverArt==s->data().toUInt() && s->isChecked()) { providers[Prov_LastFm]->setChecked(true); } } foreach (const QAction *act, configureButton->menu()->actions()) { if (act->isChecked()) { enabledProviders|=act->data().toUInt(); } } DBUG << enabledProviders; } void CoverDialog::clearTempFiles() { foreach (QTemporaryFile *file, tempFiles) { file->remove(); delete file; } } void CoverDialog::sendQueryRequest(const QUrl &url, const QString &host) { DBUG << url.toString(); NetworkJob *j=NetworkAccessManager::self()->get(QNetworkRequest(url)); j->setProperty(constHostProperty, host.isEmpty() ? url.host() : host); j->setProperty(constTypeProperty, (int)DL_Query); connect(j, SIGNAL(finished()), this, SLOT(queryJobFinished())); currentQuery.insert(j); } NetworkJob * CoverDialog::downloadImage(const QString &url, DownloadType dlType) { DBUG << url << dlType; if (DL_Thumbnail==dlType) { if (currentUrls.contains(url)) { return 0; } currentUrls.insert(url); } else { foreach (QTemporaryFile *tmp, tempFiles) { if (tmp->property(constLargeProperty).toString()==url) { QImage img; if (img.load(tmp->fileName())) { if (DL_LargePreview==dlType) { previewDialog()->downloading(url); previewDialog()->showImage(img, url); } else if (DL_LargeSave==dlType) { if (saveCover(tmp->fileName(), img)) { accept(); } } return 0; } tmp->remove(); delete tmp; tempFiles.removeAll(tmp); break; } } } if (DL_LargeSave==dlType) { saving=true; } NetworkJob *j=NetworkAccessManager::self()->get(QNetworkRequest(QUrl(url))); connect(j, SIGNAL(finished()), this, SLOT(downloadJobFinished())); currentQuery.insert(j); j->setProperty(constTypeProperty, (int)dlType); if (saving) { previewDialog()->downloading(url); connect(j, SIGNAL(downloadProgress(qint64, qint64)), preview, SLOT(progress(qint64, qint64))); } return j; } void CoverDialog::downloadThumbnail(const QString &thumbUrl, const QString &largeUrl, const QString &host, int w, int h, int sz) { if (thumbUrl.isEmpty() || largeUrl.isEmpty()) { return; } NetworkJob *j=downloadImage(thumbUrl, DL_Thumbnail); if (j) { j->setProperty(constThumbProperty, thumbUrl); j->setProperty(constLargeProperty, largeUrl); j->setProperty(constHostProperty, host); if (w>0) { j->setProperty(constWidthProperty, w); } if (h>0) { j->setProperty(constHeightProperty, h); } if (sz>0) { j->setProperty(constSizeProperty, sz); } } } typedef QMap SizeMap; void CoverDialog::parseLastFmQueryResponse(const QByteArray &resp) { bool inSection=false; QXmlStreamReader doc(resp); SizeMap urls; QList entries; QStringList musibBrainzIds; doc.setNamespaceProcessing(false); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (!inSection && QLatin1String(isArtist ? "artist" : "album")==doc.name()) { inSection=true; urls.clear(); } else if (inSection && QLatin1String("image")==doc.name()) { QString size=doc.attributes().value("size").toString(); QString url=doc.readElementText(); if (!size.isEmpty() && !url.isEmpty()) { urls.insert(size, url); } } else if (inSection && QLatin1String("mbid")==doc.name()) { QString id=doc.readElementText(); if (id.length()>4) { musibBrainzIds.append(id); } } } else if (doc.isEndElement() && inSection && QLatin1String(isArtist ? "artist" : "album")==doc.name()) { if (!urls.isEmpty()) { entries.append(urls); urls.clear(); } inSection=false; } } QStringList largeUrls=QStringList() << "extralarge" << "large"; QStringList thumbUrls=QStringList() << "large" << "medium" << "small"; foreach (const SizeMap &urls, entries) { QString largeUrl; QString thumbUrl; foreach (const QString &u, largeUrls) { if (urls.contains(u)) { largeUrl=urls[u]; break; } } foreach (const QString &u, thumbUrls) { if (urls.contains(u)) { thumbUrl=urls[u]; break; } } downloadThumbnail(thumbUrl, largeUrl, constLastFmHost); } if (enabledProviders&Prov_CoverArt) { foreach (const QString &id, musibBrainzIds) { QUrl coverartUrl; coverartUrl.setScheme("http"); coverartUrl.setHost(constCoverArtArchiveHost); coverartUrl.setPath("/release/"+id); sendQueryRequest(coverartUrl); } } } void CoverDialog::parseGoogleQueryResponse(const QByteArray &resp) { // Code based on Audex CDDA Extractor QRegExp rx("[\\s\\n]*]+src=\"([^\"]+)\""); rx.setCaseSensitivity(Qt::CaseInsensitive); rx.setMinimal(true); int pos = 0; QString xml(QString::fromUtf8(resp)); QString html = xml.replace(QLatin1String("&"), QLatin1String("&")); while (-1!=(pos=rx.indexIn(html, pos))) { QUrl u("http://www.google.com"+rx.cap(1)); QUrlQuery url(u); int width=url.queryItemValue("w").toInt(); int height=url.queryItemValue("h").toInt(); if (canUse(width, height)) { downloadThumbnail(rx.cap(2), url.queryItemValue("imgurl"), constGoogleHost, width, height, url.queryItemValue("sz").toInt()); } pos += rx.matchedLength(); } } void CoverDialog::parseCoverArtArchiveQueryResponse(const QByteArray &resp) { QJsonParseError jsonParseError; QVariantMap parsed=QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap(); bool ok=QJsonParseError::NoError==jsonParseError.error; if (ok && parsed.contains("images")) { QVariantList images=parsed["images"].toList(); foreach (const QVariant &i, images) { QVariantMap im=i.toMap(); if (im.contains("front") && im["front"].toBool() && im.contains("image") && im.contains("thumbnails")) { QVariantMap thumb=im["thumbnails"].toMap(); QString largeUrl=im["image"].toString(); QString thumbUrl; if (thumb.contains("small")) { thumbUrl=thumb["small"].toString(); } else if (thumb.contains("large")) { thumbUrl=thumb["large"].toString(); } downloadThumbnail(thumbUrl, largeUrl, constCoverArtArchiveHost); } } } } void CoverDialog::parseSpotifyQueryResponse(const QByteArray &resp) { QJsonParseError jsonParseError; QVariantMap parsed=QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap(); bool ok=QJsonParseError::NoError==jsonParseError.error; if (ok) { if (parsed.contains("info")) { // Initial query response... const QString key=QLatin1String(isArtist ? "artists" : "albums"); const QString href=QLatin1String("spotify:")+QLatin1String(isArtist ? "artist:" : "album:"); const QString baseUrl=QLatin1String("https://embed.spotify.com/oembed/?url=http://open.spotify.com/")+QLatin1String(isArtist ? "artist/" : "album/"); if (parsed.contains(key)) { QVariantList results=parsed[key].toList(); foreach (const QVariant &res, results) { QVariantMap item=res.toMap(); if (item.contains("href")) { QString url=item["href"].toString(); if (url.contains(href)) { url=baseUrl+url.remove(href); sendQueryRequest(QUrl(url), constSpotifyHost); } } } } } else if (parsed.contains("provider_url") && parsed.contains("thumbnail_url")) { // Response to query above QString thumbUrl=parsed["thumbnail_url"].toString(); downloadThumbnail(thumbUrl, QString(thumbUrl).replace("/cover/", "/640/"), constSpotifyHost); } } } void CoverDialog::parseITunesQueryResponse(const QByteArray &resp) { QJsonParseError jsonParseError; QVariantMap parsed=QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap(); bool ok=QJsonParseError::NoError==jsonParseError.error; if (ok && parsed.contains("results")) { QVariantList results=parsed["results"].toList(); foreach (const QVariant &res, results) { QVariantMap item=res.toMap(); if (item.contains("artworkUrl100")) { QString thumbUrl=item["artworkUrl100"].toString(); downloadThumbnail(thumbUrl, QString(thumbUrl).replace("100x100", "600x600"), constITunesHost); } } } } void CoverDialog::parseDeezerQueryResponse(const QByteArray &resp) { const QString key=QLatin1String(isArtist ? "picture" : "cover"); QJsonParseError jsonParseError; QVariantMap parsed=QJsonDocument::fromJson(resp, &jsonParseError).toVariant().toMap(); bool ok=QJsonParseError::NoError==jsonParseError.error; if (ok && parsed.contains("data")) { QVariantList results=parsed["data"].toList(); foreach (const QVariant &res, results) { QVariantMap item=res.toMap(); if (item.contains(key)) { QString thumbUrl=item[key].toString(); downloadThumbnail(thumbUrl, thumbUrl+"&size=big", constDeezerHost); } } } } void CoverDialog::slotButtonClicked(int button) { switch (button) { case Ok: { QList items=list->selectedItems(); if (1==items.size()) { CoverItem *cover=static_cast(items.at(0)); if (cover->isLocal()) { if (saveCover(cover->url(), static_cast(cover)->image())) { accept(); } } else if (!cover->isExisting()) { NetworkJob *j=downloadImage(cover->url(), DL_LargeSave); if (j) { j->setProperty(constLargeProperty, cover->url()); } } } break; } case Cancel: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } bool CoverDialog::saveCover(const QString &src, const QImage &img) { QString filePath=song.filePath(); if (song.isCdda()) { QString dir = Utils::cacheDir(Covers::constCddaCoverDir, true); if (!dir.isEmpty()) { QString destName=dir+filePath.mid(Song::constCddaProtocol.length())+src.mid(src.length()-4); if (QFile::exists(destName)) { QFile::remove(destName); } if (QFile::copy(src, destName)) { emit selectedCover(img, destName); return true; } } MessageBox::error(this, tr("Failed to set cover!\n\nCould not make copy!")); return false; } QString existingBackup; if (existing && !existing->url().isEmpty() && !existing->url().startsWith(Covers::constCoverInTagPrefix)) { static const QLatin1String constBakExt(".bak"); existingBackup=existing->url()+constBakExt; if (!QFile::rename(existing->url(), existingBackup)) { MessageBox::error(this, tr("Failed to set cover!\n\nCould not backup original!")); return false; } } QString destName; QString mpdDir; QString dirName; bool saveInMpd=Settings::self()->storeCoversInMpdDir(); if (saveInMpd) { bool haveAbsPath=filePath.startsWith('/'); mpdDir=MPDConnection::self()->getDetails().dir; if (haveAbsPath || !mpdDir.isEmpty()) { dirName=filePath.endsWith('/') ? (haveAbsPath ? QString() : mpdDir)+filePath : Utils::getDir((haveAbsPath ? QString() : mpdDir)+filePath); } saveInMpd=QDir(dirName).exists(); } if (isArtist) { if (saveInMpd && !mpdDir.isEmpty() && dirName.startsWith(mpdDir) && 2==dirName.mid(mpdDir.length()).split('/', QString::SkipEmptyParts).count()) { QDir d(dirName); d.cdUp(); destName=d.absolutePath()+'/'+Covers::artistFileName(song)+src.mid(src.length()-4); } else { destName=Utils::cacheDir(Covers::constCoverDir, true)+Covers::encodeName(song.albumArtist())+src.mid(src.length()-4); } } else { if (saveInMpd) { destName=dirName+Covers::albumFileName(song)+src.mid(src.length()-4); } else { // Save to cache dir... QString dir(Utils::cacheDir(Covers::constCoverDir+Covers::encodeName(song.albumArtist()), true)); destName=dir+Covers::encodeName(song.album)+src.mid(src.length()-4); } } if (!destName.startsWith("http://") && QFile::copy(src, destName)) { Utils::setFilePerms(destName); if (!existingBackup.isEmpty() && QFile::exists(existingBackup)) { QFile::remove(existingBackup); } Covers::self()->updateCover(song, img, destName); return true; } else { if (existing && !existingBackup.isEmpty()) { QFile::rename(existingBackup, existing->url()); } MessageBox::error(this, tr("Failed to set cover!\n\nCould not copy file to '%1'!").arg(destName)); return false; } } static bool hasMimeType(QDropEvent *event) { return event->mimeData()->formats().contains(QLatin1String("text/uri-list")); } void CoverDialog::dragEnterEvent(QDragEnterEvent *event) { if (hasMimeType(event)) { event->acceptProposedAction(); } } void CoverDialog::dropEvent(QDropEvent *event) { if (hasMimeType(event) && Qt::CopyAction==event->dropAction()) { event->acceptProposedAction(); QList urls(event->mimeData()->urls()); foreach (const QUrl &url, urls) { if (url.scheme().isEmpty() || "file"==url.scheme()) { QString path=url.path(); if (!currentLocalCovers.contains(path) && (path.endsWith(".jpg", Qt::CaseInsensitive) || path.endsWith(".png", Qt::CaseInsensitive))) { QImage img(path); if (!img.isNull()) { currentLocalCovers.insert(path); insertItem(new LocalCover(path, img, list)); } } } } } } void CoverDialog::setSearching(bool s) { if (!spinner && !s) { return; } if (!spinner) { spinner=new Spinner(this); spinner->setWidget(list); } if (!msgOverlay) { msgOverlay=new MessageOverlay(this); msgOverlay->setWidget(list); connect(msgOverlay, SIGNAL(cancel()), SLOT(cancelQuery())); } if (s) { spinner->start(); msgOverlay->setText(tr("Searching...")); } else { spinner->stop(); msgOverlay->setText(QString()); } } void CoverDialog::addProvider(QMenu *mnu, const QString &name, int bit, int value) { QAction *act=mnu->addAction(name); act->setData(bit); act->setCheckable(true); act->setChecked(value&bit); connect(act, SIGNAL(toggled(bool)), this, SLOT(updateProviders())); providers.insert(bit, act); } cantata-2.2.0/gui/coverdialog.h000066400000000000000000000110461316350454000163640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COVER_DIALOG_H #define COVER_DIALOG_H #include "config.h" #include "support/dialog.h" #include "mpd-interface/song.h" #include "ui_coverdialog.h" #include "covers.h" #include #include #include class NetworkJob; class QUrl; class QTemporaryFile; class QListWidgetItem; class QLabel; class QProgressBar; class QScrollArea; class QWheelEvent; class CoverItem; class ExistingCover; class Spinner; class MessageOverlay; class QAction; class QMenu; class CoverPreview : public Dialog { Q_OBJECT public: CoverPreview(QWidget *p); virtual ~CoverPreview() { } void showImage(const QImage &img, const QString &u); void downloading(const QString &u); bool aboutToShow(const QString &u) const { return u==url; } private Q_SLOTS: void progress(qint64 rx, qint64 total); private: void scaleImage(int adjust); void wheelEvent(QWheelEvent *event); private: QString url; QLabel *loadingLabel; QProgressBar *pbar; QLabel *imageLabel; QScrollArea *scrollArea; double zoom; int imgW; int imgH; }; class CoverDialog : public Dialog, public Ui::CoverDialog { Q_OBJECT public: static int instanceCount(); enum DownloadType { DL_Query, DL_Thumbnail, DL_LargePreview, DL_LargeSave }; CoverDialog(QWidget *parent); virtual ~CoverDialog(); void show(const Song &s, const Covers::Image ¤t=Covers::Image()); int imageSize() const { return iSize; } Q_SIGNALS: void selectedCover(const QImage &img, const QString &fileName); private Q_SLOTS: void queryJobFinished(); void downloadJobFinished(); void showImage(QListWidgetItem *item); void sendQuery(); void cancelQuery(); void checkStatus(); void addLocalFile(); void menuRequested(const QPoint &pos); void showImage(); void removeImages(); void updateProviders(); private: void sendLastFmQuery(const QString &fixedQuery, int page); void sendGoogleQuery(const QString &fixedQuery, int page); void sendSpotifyQuery(const QString &fixedQuery); void sendITunesQuery(const QString &fixedQuery); void sendDeezerQuery(const QString &fixedQuery); CoverPreview *previewDialog(); void insertItem(CoverItem *item); NetworkJob * downloadImage(const QString &url, DownloadType dlType); void downloadThumbnail(const QString &thumbUrl, const QString &largeUrl, const QString &host, int w=-1, int h=-1, int sz=-1); void clearTempFiles(); void sendQueryRequest(const QUrl &url, const QString &host=QString()); void parseLastFmQueryResponse(const QByteArray &resp); void parseGoogleQueryResponse(const QByteArray &resp); void parseCoverArtArchiveQueryResponse(const QByteArray &resp); void parseSpotifyQueryResponse(const QByteArray &resp); void parseITunesQueryResponse(const QByteArray &resp); void parseDeezerQueryResponse(const QByteArray &resp); void slotButtonClicked(int button); bool saveCover(const QString &src, const QImage &img); void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event); void setSearching(bool s); void addProvider(QMenu *mnu, const QString &name, int bit, int value); private: int enabledProviders; Song song; ExistingCover *existing; QString currentQueryString; int currentQueryProviders; QSet currentQuery; QSet currentUrls; QSet currentLocalCovers; QList tempFiles; CoverPreview *preview; bool saving; bool isArtist; int iSize; Spinner *spinner; MessageOverlay *msgOverlay; int page; QMenu *menu; QAction *showAction; QAction *removeAction; QMap providers; }; #endif cantata-2.2.0/gui/coverdialog.ui000066400000000000000000000040421316350454000165500ustar00rootroot00000000000000 CoverDialog 0 0 417 304 0 Search Add a local file Configure This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. FlatToolButton QToolButton
    support/flattoolbutton.h
    LineEdit QLineEdit
    support/lineedit.h
    MenuButton QToolButton
    widgets/menubutton.h
    NoteLabel QLabel
    widgets/notelabel.h
    cantata-2.2.0/gui/covers.cpp000066400000000000000000002037551316350454000157340ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "covers.h" #include "mpd-interface/song.h" #include "support/utils.h" #include "mpd-interface/mpdconnection.h" #include "network/networkaccessmanager.h" #include "settings.h" #include "config.h" #include "devices/deviceoptions.h" #include "support/thread.h" #include "online/onlineservice.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/device.h" #include "models/devicesmodel.h" #endif #ifdef TAGLIB_FOUND #include "tags/tags.h" #endif #include "support/globalstatic.h" #include "widgets/icons.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include GLOBAL_STATIC(Covers, instance) #include static bool debugIsEnabled=false; #define DBUG_CLASS(CLASS) if (debugIsEnabled) qWarning() << CLASS << QThread::currentThread()->objectName() << __FUNCTION__ #define DBUG DBUG_CLASS(metaObject()->className()) void Covers::enableDebug() { debugIsEnabled=true; } bool Covers::debugEnabled() { return debugIsEnabled; } const QLatin1String Covers::constLastFmApiKey("5a854b839b10f8d46e630e8287c2299b"); const QLatin1String Covers::constCoverDir("covers/"); const QLatin1String Covers::constScaledCoverDir("covers-scaled/"); const QLatin1String Covers::constCddaCoverDir("cdda/"); const QLatin1String Covers::constFileName("cover"); const QLatin1String Covers::constArtistImage("artist"); const QLatin1String Covers::constComposerImage("composer"); const QString Covers::constCoverInTagPrefix=QLatin1String("{tag}"); static const char * constExtensions[]={".jpg", ".png", 0}; static bool saveInMpdDir=true; static bool fetchCovers=true; static QString constNoCover=QLatin1String("{nocover}"); static double devicePixelRatio=1.0; // Only scale images to device pixel ratio if un-scaled size is less then 300pixels. static const int constRetinaScaleMaxSize=300; #ifdef USE_JPEG_FOR_SCALED_CACHE static const QLatin1String constScaledExtension(".jpg"); static const QLatin1String constScaledPrevExtension(".png"); static const char * constScaledFormat="JPG"; #else static const QLatin1String constScaledExtension(".png"); static const QLatin1String constScaledPrevExtension(".jpg"); static const char * constScaledFormat="PNG"; #endif static QImage scale(const Song &song, const QImage &img, int size) { if (song.isArtistImageRequest() || song.isComposerImageRequest()) { QImage scaled=img.scaled(QSize(size, size), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); if (scaled.width()>size || scaled.height()>size) { scaled=scaled.copy((scaled.width()-size)/2, 0, size, size); } return scaled; } return img.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } static bool canSaveTo(const QString &dir) { QString mpdDir=MPDConnection::self()->getDetails().dir; return !dir.isEmpty() && !mpdDir.isEmpty() && !mpdDir.startsWith(QLatin1String("http://")) && QDir(mpdDir).exists() && dir.startsWith(mpdDir); } static const QString typeFromRaw(const QByteArray &raw) { if (Covers::isJpg((raw))) { return constExtensions[0]; } else if (Covers::isPng(raw)) { return constExtensions[1]; } return QString(); } static QString save(const QString &mimeType, const QString &extension, const QString &filePrefix, const QImage &img, const QByteArray &raw) { if (!mimeType.isEmpty() && extension==mimeType) { if (QFile::exists(filePrefix+mimeType)) { return filePrefix+mimeType; } QFile f(filePrefix+mimeType); if (f.open(QIODevice::WriteOnly) && raw.size()==f.write(raw)) { if (!MPDConnection::self()->getDetails().dir.isEmpty() && filePrefix.startsWith(MPDConnection::self()->getDetails().dir)) { Utils::setFilePerms(filePrefix+mimeType); } return filePrefix+mimeType; } } if (extension!=mimeType) { if (QFile::exists(filePrefix+extension)) { return filePrefix+extension; } if (img.save(filePrefix+extension)) { if (!MPDConnection::self()->getDetails().dir.isEmpty() && filePrefix.startsWith(MPDConnection::self()->getDetails().dir)) { Utils::setFilePerms(filePrefix+mimeType); } return filePrefix+extension; } } return QString(); } static QImage loadImage(const QString &fileName) { QImage img(fileName); if (img.isNull()) { // Failed to load, perhaps extension is wrong? If so try PNG for .jpg, and vice versa... QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { QByteArray header=f.read(10); f.reset(); img.load(&f, typeFromRaw(header).toLatin1()); if (!img.isNull()) { DBUG_CLASS("Covers") << fileName << "has wrong extension!"; } } } return img; } static inline bool isOnlineServiceImage(const Song &s) { return OnlineService::showLogoAsCover(s); } static Covers::Image serviceImage(const Song &s) { Covers::Image img; img.fileName=OnlineService::iconPath(s.onlineService()); if (!img.fileName.isEmpty()) { img.img=loadImage(img.fileName); if (!img.img.isNull()) { DBUG_CLASS("Covers") << s.onlineService(); } } return img; } static inline QString albumKey(const Song &s) { if (Song::SingleTracks==s.type) { return QLatin1String("-single-tracks-"); } if (s.isStandardStream()) { return QLatin1String("-stream-"); } if (isOnlineServiceImage(s)) { return s.onlineService(); } return "{"+s.albumArtist()+"}{"+s.albumId()+"}"; } static inline QString artistKey(const Song &s) { return "{"+s.albumArtist()+"}"; } static inline QString composerKey(const Song &s) { return "{"+s.composer()+"}"; } static inline QString songKey(const Song &s) { return s.isArtistImageRequest() ? artistKey(s) : s.isComposerImageRequest() ? composerKey(s) : albumKey(s); } static inline QString cacheKey(const Song &song, int size) { if (song.isArtistImageRequest() && song.isVariousArtists()) { return QLatin1String("va")+QString::number(size); } else if (Song::SingleTracks==song.type) { return QLatin1String("single")+QString::number(size); } else if (song.isStandardStream()) { return QLatin1String("str")+QString::number(size); } else if (isOnlineServiceImage(song)) { return song.onlineService()+QString::number(size); } return songKey(song)+QString::number(size); } static QString getScaledCoverName(const Song &song, int size, bool createDir) { if (song.isArtistImageRequest()) { QString dir=Utils::cacheDir(Covers::constScaledCoverDir+QString::number(size)+QLatin1Char('/'), createDir); return dir.isEmpty() ? QString() : (dir+Covers::encodeName(song.albumArtist())+constScaledExtension); } if (song.isComposerImageRequest()) { QString dir=Utils::cacheDir(Covers::constScaledCoverDir+QString::number(size)+QLatin1Char('/'), createDir); return dir.isEmpty() ? QString() : (dir+Covers::encodeName(song.composer())+constScaledExtension); } QString dir=Utils::cacheDir(Covers::constScaledCoverDir+QString::number(size)+QLatin1Char('/')+Covers::encodeName(song.albumArtist()), createDir); return dir.isEmpty() ? QString() : (dir+Covers::encodeName(song.albumId())+constScaledExtension); } static void clearScaledCache(const Song &song) { QString dirName=Utils::cacheDir(Covers::constScaledCoverDir, false); if (dirName.isEmpty()) { return; } QDir d(dirName); if (!d.exists()) { return; } DBUG_CLASS("Covers") << song.file << song.artist << song.albumartist << song.album; QStringList sizeDirNames=d.entryList(QStringList() << "*", QDir::Dirs|QDir::NoDotAndDotDot); if (song.isArtistImageRequest() || song.isComposerImageRequest()) { bool artistImage=song.isArtistImageRequest(); for (int i=0; constExtensions[i]; ++i) { QString fileName=Covers::encodeName(artistImage ? song.artist : song.composer())+constExtensions[i]; foreach (const QString &sizeDirName, sizeDirNames) { QString fname=dirName+sizeDirName+QLatin1Char('/')+fileName; if (QFile::exists(fname)) { QFile::remove(fname); } } } } else { QString subDir=Covers::encodeName(song.artist); for (int i=0; constExtensions[i]; ++i) { QString fileName=Covers::encodeName(song.album)+constExtensions[i]; foreach (const QString &sizeDirName, sizeDirNames) { QString fname=dirName+sizeDirName+QLatin1Char('/')+subDir+QLatin1Char('/')+fileName; if (QFile::exists(fname)) { QFile::remove(fname); } } } } } static QImage loadScaledCover(const Song &song, int size) { QString fileName=getScaledCoverName(song, size, false); if (!fileName.isEmpty()) { if (QFile::exists(fileName)) { QImage img(fileName, constScaledFormat); if (!img.isNull() && (img.width()==size || img.height()==size)) { DBUG_CLASS("Covers") << song.albumArtist() << song.albumId() << size << "scaled cover found" << fileName; return img; } } else { // Remove any previous PNG/JPEG scaled cover... fileName=Utils::changeExtension(fileName, constScaledPrevExtension); if (QFile::exists(fileName)) { QFile::remove(fileName); } } } return QImage(); } bool Covers::isJpg(const QByteArray &data) { return data.size()>9 && /*data[0]==0xFF && data[1]==0xD8 && data[2]==0xFF*/ data[6]=='J' && data[7]=='F' && data[8]=='I' && data[9]=='F'; } bool Covers::isPng(const QByteArray &data) { return data.size()>4 && /*data[0]==0x89 &&*/ data[1]=='P' && data[2]=='N' && data[3]=='G'; } const char * Covers::imageFormat(const QByteArray &data) { return isJpg(data) ? "JPG" : (isPng(data) ? "PNG" : 0); } QString Covers::encodeName(QString name) { name.replace(QLatin1Char('/'), QLatin1Char('_')); #if defined Q_OS_WIN name.replace(QLatin1Char('?'), QLatin1Char('_')); name.replace(QLatin1Char(':'), QLatin1Char('_')); name.replace(QLatin1Char('<'), QLatin1Char('_')); name.replace(QLatin1Char('>'), QLatin1Char('_')); name.replace(QLatin1Char('\\'), QLatin1Char('_')); name.replace(QLatin1Char('*'), QLatin1Char('_')); name.replace(QLatin1Char('|'), QLatin1Char('_')); name.replace(QLatin1Char('\"'), QLatin1Char('_')); #elif defined Q_OS_MAC name.replace(QLatin1Char(':'), QLatin1Char('_')); if (name.startsWith(QLatin1Char('.'))) { name[0]=QLatin1Char('_'); } if (name.length()>255) { name=name.left(255); } #else // Linux if (name.startsWith(QLatin1Char('.'))) { name[0]=QLatin1Char('_'); } #endif return name; } QString Covers::albumFileName(const Song &song) { QString coverName=MPDConnection::self()->getDetails().coverName; if (coverName.isEmpty()) { coverName=Covers::constFileName; } else if (coverName.contains(QLatin1Char('%'))) { coverName.replace(DeviceOptions::constAlbumArtist, encodeName(song.albumArtist())); coverName.replace(DeviceOptions::constTrackArtist, encodeName(song.albumArtist())); coverName.replace(DeviceOptions::constAlbumTitle, encodeName(song.album)); coverName.replace(QLatin1String("%"), QLatin1String("")); } return coverName; } QString Covers::artistFileName(const Song &song) { QString coverName=MPDConnection::self()->getDetails().coverName; if (coverName.contains(QLatin1Char('%'))) { QString rv=encodeName(song.isVariousArtists() ? song.basicArtist() : song.albumArtist()); if (!rv.isEmpty()) { return rv; } } return constArtistImage; } QString Covers::composerFileName(const Song &song) { QString coverName=MPDConnection::self()->getDetails().coverName; if (coverName.contains(QLatin1Char('%'))) { QString rv=encodeName(song.composer()); if (!rv.isEmpty()) { return rv; } } return constComposerImage; } QString Covers::fixArtist(const QString &artist) { if (artist.isEmpty()) { return artist; } static QMap artistMap; static bool initialised=false; if (!initialised) { initialised=true; QStringList dirs=QStringList() << Utils::dataDir() << CANTATA_SYS_CONFIG_DIR; foreach (const QString &dir, dirs) { if (dir.isEmpty()) { continue; } QFile f(dir+QLatin1String("/tag_fixes.xml")); if (f.open(QIODevice::ReadOnly)) { QXmlStreamReader doc(&f); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("artist")==doc.name()) { QString from=doc.attributes().value("from").toString(); QString to=doc.attributes().value("to").toString(); if (!from.isEmpty() && !to.isEmpty() && from!=to && !artistMap.contains(from)) { artistMap.insert(from, to); } } } } } } QMap::ConstIterator it=artistMap.find(artist); return it==artistMap.constEnd() ? artist : it.value(); } const QSize Covers::constMaxSize(600, 600); //bool Covers::isCoverFile(const QString &file) //{ // return standardNames().contains(file); //} static bool fExists(const QString &dir, const QString &file) { return QFile::exists(dir+file); } static bool fCopy(const QString &sDir, const QString &sFile, const QString &dDir, const QString &dFile) { return QFile::copy(sDir+sFile, dDir+dFile); } bool Covers::copyImage(const QString &sourceDir, const QString &destDir, const QString &coverFile, const QString &destName, unsigned short maxSize) { QImage img(sourceDir+coverFile); bool ok=false; if (maxSize>0 && (img.width()>maxSize || img.height()>maxSize)) { // Need to scale image... img=img.scaled(QSize(maxSize, maxSize), Qt::KeepAspectRatio, Qt::SmoothTransformation); ok=img.save(destDir+destName); } else if (destName.right(4)!=coverFile.right(4)) { // Diff extensions, so need to convert image type... ok=img.save(destDir+destName); } else { // no scaling, and same image type, so we can just copy... ok=fCopy(sourceDir, coverFile, destDir, destName); } Utils::setFilePerms(destDir+destName); return ok; } bool Covers::copyCover(const Song &song, const QString &sourceDir, const QString &destDir, const QString &name, unsigned short maxSize) { // First, check if dir already has a cover file! QStringList names=standardNames(); for (int e=0; constExtensions[e]; ++e) { names+=song.album+constExtensions[e]; } QString mpdCover=albumFileName(song); for (int e=0; constExtensions[e]; ++e) { if (!names.contains(mpdCover+constExtensions[e])) { names.prepend(mpdCover+constExtensions[e]); } } for (int e=0; constExtensions[e]; ++e) { names+=song.albumArtist()+QLatin1String(" - ")+song.album+constExtensions[e]; } foreach (const QString &coverFile, names) { if (fExists(destDir, coverFile)) { return true; } } // No cover found, try to copy from source folder foreach (const QString &coverFile, names) { if (fExists(sourceDir, coverFile)) { QString destName(name); if (destName.isEmpty()) { // copying into mpd dir, so we want cover.jpg/png... if (standardNames().at(0)!=coverFile) { // source is not 'cover.xxx' QString ext(coverFile.endsWith(constExtensions[0]) ? constExtensions[0] : constExtensions[1]); destName=mpdCover+ext; } else { destName=coverFile; } } copyImage(sourceDir, destDir, coverFile, destName, maxSize); return true; } } QString destName(name); if (!destName.isEmpty()) { // Copying ONTO a device // None in source folder. Do we have a cached cover? QString artist=encodeName(song.albumArtist()); QString album=encodeName(song.album); QString dir(Utils::cacheDir(constCoverDir+artist, false)); for (int e=0; constExtensions[e]; ++e) { if (QFile::exists(dir+album+constExtensions[e])) { copyImage(dir, destDir, album+constExtensions[e], destName, maxSize); return true; } } } return false; } const QStringList & Covers::standardNames() { static QStringList *coverFileNames; if (!coverFileNames) { coverFileNames=new QStringList(); QStringList fileNames; fileNames << Covers::constFileName << QLatin1String("AlbumArt") << QLatin1String("folder"); foreach (const QString &fileName, fileNames) { for (int e=0; constExtensions[e]; ++e) { *coverFileNames << fileName+constExtensions[e]; } } } return *coverFileNames; } CoverDownloader::CoverDownloader() : manager(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } void CoverDownloader::stop() { thread->stop(); } void CoverDownloader::download(const Song &song) { DBUG << song.file << song.artist << song.albumartist << song.album; if (song.isFromOnlineService()) { QString serviceName=song.onlineService(); QString imageUrl=song.extraField(Song::OnlineImageUrl); Job job(song, QString()); job.type=JobHttpJpg; DBUG << "Online image url" << imageUrl; if (!imageUrl.isEmpty()) { NetworkJob *j=network()->get(imageUrl); jobs.insert(j, job); connect(j, SIGNAL(finished()), this, SLOT(onlineJobFinished())); } else { failed(job); } return; } if (jobs.end()!=findJob(Job(song, QString()))) { return; } QString dirName; QString fileName=song.filePath(); bool haveAbsPath=fileName.startsWith(Utils::constDirSep); if (haveAbsPath || !MPDConnection::self()->getDetails().dir.isEmpty()) { dirName=fileName.endsWith(Utils::constDirSep) ? (haveAbsPath ? QString() : MPDConnection::self()->getDetails().dir)+fileName : Utils::getDir((haveAbsPath ? QString() : MPDConnection::self()->getDetails().dir)+fileName); } Job job(song, dirName); if (!MPDConnection::self()->getDetails().dir.isEmpty() && MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http://"))) { downloadViaHttp(job, JobHttpJpg); } else if (fetchCovers) { downloadViaLastFm(job); } else { failed(job); } } bool CoverDownloader::downloadViaHttp(Job &job, JobType type) { QUrl u; QString coverName=job.song.isArtistImageRequest() ? Covers::artistFileName(job.song) : job.song.isComposerImageRequest() ? Covers::composerFileName(job.song) : Covers::albumFileName(job.song); coverName+=constExtensions[JobHttpJpg==type ? 0 : 1]; QString dir=Utils::getDir(job.filePath); if (job.song.isArtistImageRequest() || job.song.isComposerImageRequest()) { if (job.level) { QStringList parts=dir.split(Utils::constDirSep, QString::SkipEmptyParts); if (parts.size()getDetails().dir+dir+coverName.toLatin1()); job.type=type; NetworkJob *j=network()->get(u); connect(j, SIGNAL(finished()), this, SLOT(jobFinished())); jobs.insert(j, job); DBUG << u.toString(); return true; } void CoverDownloader::downloadViaLastFm(Job &job) { QUrl url("https://ws.audioscrobbler.com/2.0/"); QUrlQuery query; query.addQueryItem("method", job.song.isArtistImageRequest() || job.song.isComposerImageRequest() ? "artist.getInfo" : "album.getInfo"); query.addQueryItem("api_key", Covers::constLastFmApiKey); query.addQueryItem("autocorrect", "1"); query.addQueryItem("artist", Covers::fixArtist(job.song.albumArtist())); if (!job.song.isArtistImageRequest()) { query.addQueryItem("album", job.song.album); } url.setQuery(query); NetworkJob *j = network()->get(url); connect(j, SIGNAL(finished()), this, SLOT(lastFmCallFinished())); job.type=JobLastFm; jobs.insert(j, job); DBUG << url.toString(); } void CoverDownloader::lastFmCallFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QHash::Iterator it(jobs.find(reply)); QHash::Iterator end(jobs.end()); if (it!=end) { Job job=it.value(); jobs.erase(it); QString url; if(reply->ok()) { QXmlStreamReader doc(reply->readAll()); QString largeUrl; bool inSection=false; bool isArtistImage=job.song.isArtistImageRequest(); doc.setNamespaceProcessing(false); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (!inSection && QLatin1String(isArtistImage ? "artist" : "album")==doc.name()) { inSection=true; } else if (inSection && QLatin1String("image")==doc.name()) { QString size=doc.attributes().value("size").toString(); if (QLatin1String("extralarge")==size) { url = doc.readElementText(); } else if (QLatin1String("large")==size) { largeUrl = doc.readElementText(); } if (!url.isEmpty() && !largeUrl.isEmpty()) { break; } } } else if (doc.isEndElement() && inSection && QLatin1String(isArtistImage ? "artist" : "album")==doc.name()) { break; } } if (url.isEmpty() && !largeUrl.isEmpty()) { url=largeUrl; } } if (!url.isEmpty()) { NetworkJob *j=network()->get(QNetworkRequest(QUrl(url))); connect(j, SIGNAL(finished()), this, SLOT(jobFinished())); DBUG << "download" << url; jobs.insert(j, job); } else { failed(job); } } reply->deleteLater(); } void CoverDownloader::jobFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QHash::Iterator it(jobs.find(reply)); QHash::Iterator end(jobs.end()); if (it!=end) { QByteArray data=reply->ok() ? reply->readAll() : QByteArray(); Covers::Image img; img.img= data.isEmpty() ? QImage() : QImage::fromData(data, Covers::imageFormat(data)); Job job=it.value(); if (!img.img.isNull() && img.img.size().width()<32) { img.img = QImage(); } jobs.remove(it.key()); if (img.img.isNull() && JobLastFm!=job.type) { if (JobHttpJpg==job.type) { if (!job.level || !downloadViaHttp(job, JobHttpJpg)) { job.level=0; downloadViaHttp(job, JobHttpPng); } } else if (fetchCovers && JobHttpPng==job.type && (!job.level || !downloadViaHttp(job, JobHttpPng)) && !job.song.isComposerImageRequest()) { downloadViaLastFm(job); } else { failed(job); } } else { if (!img.img.isNull()) { if (img.img.size().width()>Covers::constMaxSize.width() || img.img.size().height()>Covers::constMaxSize.height()) { img.img=img.img.scaled(Covers::constMaxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } img.fileName=saveImg(job, img.img, data); if (!img.fileName.isEmpty()) { clearScaledCache(job.song); } } if (job.song.isArtistImageRequest()) { DBUG << "artist image, null?" << img.img.isNull(); emit artistImage(job.song, img.img, img.fileName); } else if (job.song.isComposerImageRequest()) { DBUG << "compser image, null?" << img.img.isNull(); emit composerImage(job.song, img.img, img.fileName); } else if (img.img.isNull()) { DBUG << "failed to download cover image"; emit cover(job.song, QImage(), QString()); } else { DBUG << "got cover image" << img.fileName; emit cover(job.song, img.img, img.fileName); } } } reply->deleteLater(); } void CoverDownloader::onlineJobFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } reply->deleteLater(); DBUG << "status" << reply->error() << reply->errorString(); QHash::Iterator it(jobs.find(reply)); QHash::Iterator end(jobs.end()); if (it!=end) { QByteArray data=QNetworkReply::NoError==reply->error() ? reply->readAll() : QByteArray(); if (data.isEmpty()) { DBUG << reply->url().toString() << "empty!"; return; } Job job=it.value(); const Song &song=job.song; QString id=song.onlineService(); QString fileName; QImage img=data.isEmpty() ? QImage() : QImage::fromData(data, Covers::imageFormat(data)); bool png=Covers::isPng(data); DBUG << "Got image" << id << song.artist << song.album << png; if (!img.isNull()) { if (img.size().width()>Covers::constMaxSize.width() || img.size().height()>Covers::constMaxSize.height()) { img=img.scaled(Covers::constMaxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QString cacheName=song.extraField(Song::OnlineImageCacheName); fileName=cacheName.isEmpty() ? Utils::cacheDir(id.toLower(), true)+Covers::encodeName(song.album.isEmpty() ? song.artist : (song.artist+" - "+song.album))+(png ? ".png" : ".jpg") : cacheName; QFile f(fileName); if (f.open(QIODevice::WriteOnly)) { DBUG << "Saved image to" << fileName; f.write(data); } } emit cover(job.song, img, fileName); } } void CoverDownloader::failed(const Job &job) { if (job.song.isArtistImageRequest()) { DBUG << "artist image" << job.song.artist; emit artistImage(job.song, QImage(), QString()); } else if (job.song.isComposerImageRequest()) { DBUG << "composer image" << job.song.composer(); emit composerImage(job.song, QImage(), QString()); } else { DBUG << "cover image" << job.song.artist << job.song.album; emit cover(job.song, QImage(), QString()); } } QString CoverDownloader::saveImg(const Job &job, const QImage &img, const QByteArray &raw) { QString mimeType=typeFromRaw(raw); QString extension=mimeType.isEmpty() ? constExtensions[0] : mimeType; QString savedName; if (job.song.isCdda()) { QString dir = Utils::cacheDir(Covers::constCddaCoverDir, true); if (!dir.isEmpty()) { savedName=save(mimeType, extension, dir+job.filePath.mid(7), img, raw); if (!savedName.isEmpty()) { DBUG << job.song.file << savedName; return savedName; } } return QString(); } if (job.song.isArtistImageRequest() || job.song.isComposerImageRequest()) { if (saveInMpdDir && !job.song.isNonMPD() && canSaveTo(job.dir)) { QString mpdDir=MPDConnection::self()->getDetails().dir; if (!mpdDir.isEmpty() && job.dir.startsWith(mpdDir) && 2==job.dir.mid(mpdDir.length()).split(Utils::constDirSep, QString::SkipEmptyParts).count()) { QDir d(job.dir); d.cdUp(); savedName=save(mimeType, extension, d.absolutePath()+Utils::constDirSep+ (job.song.isArtistImageRequest() ? Covers::artistFileName(job.song) : Covers::composerFileName(job.song)), img, raw); if (!savedName.isEmpty()) { DBUG << job.song.file << savedName; return savedName; } } } QString dir = Utils::cacheDir(Covers::constCoverDir, true); if (!dir.isEmpty()) { savedName=save(mimeType, extension, dir+Covers::encodeName(job.song.basicArtist()), img, raw); if (!savedName.isEmpty()) { DBUG << job.song.file << savedName; return savedName; } } } else { // Try to save as cover.jpg in album dir... if (saveInMpdDir && !job.song.isNonMPD() && canSaveTo(job.dir)) { QString coverName=Covers::albumFileName(job.song); savedName=save(mimeType, extension, job.dir+coverName, img, raw); if (!savedName.isEmpty()) { DBUG << job.song.file << savedName; return savedName; } } // Could not save with album, save in cache dir... QString dir = Utils::cacheDir(Covers::constCoverDir+Covers::encodeName(job.song.albumArtist()), true); if (!dir.isEmpty()) { savedName=save(mimeType, extension, dir+Covers::encodeName(job.song.album), img, raw); if (!savedName.isEmpty()) { DBUG << job.song.file << savedName; return savedName; } } } return QString(); } QHash::Iterator CoverDownloader::findJob(const Job &job) { QHash::Iterator it(jobs.begin()); QHash::Iterator end(jobs.end()); bool isComposer=job.song.isComposerImageRequest(); bool isArtist=job.song.isArtistImageRequest(); bool isCover=!isComposer && !isArtist; for (; it!=end; ++it) { if ((*it).song.isComposerImageRequest()) { if (isComposer && (*it).song.composer()==job.song.composer()) { return it; } } else if ((*it).song.isArtistImageRequest()) { if (isArtist && (*it).song.albumArtist()==job.song.albumArtist()) { return it; } } else if (isCover && (*it).song.albumArtist()==job.song.albumArtist() && (*it).song.album==job.song.album) { return it; } } return end; } NetworkAccessManager * CoverDownloader::network() { if (!manager) { manager=new NetworkAccessManager(this); } return manager; } CoverLocator::CoverLocator() : timer(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } void CoverLocator::stop() { thread->stop(); } void CoverLocator::startTimer(int interval) { if (!timer) { timer=thread->createTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(locate()), Qt::QueuedConnection); } timer->start(interval); } void CoverLocator::locate(const Song &s) { queue.append(s); startTimer(0); } // To improve responsiveness of views, we only process a max of X images per even loop iteration. // If more images are asked for, we place these into a list, and get them on the next iteration // of the loop. This way things appear smoother. static const int constMaxCoverUpdatePerIteration=10; void CoverLocator::locate() { QList toDo; for (int i=0; i covers; foreach (const Song &s, toDo) { DBUG << s.file << s.artist << s.albumartist << s.album; Covers::Image img=Covers::locateImage(s); covers.append(LocatedCover(s, img.img, img.fileName)); } if (!covers.isEmpty()) { DBUG << "located" << covers.count(); emit located(covers); } if (!queue.isEmpty()) { startTimer(0); } } CoverLoader::CoverLoader() : timer(0) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } void CoverLoader::stop() { thread->stop(); } void CoverLoader::startTimer(int interval) { if (!timer) { timer=thread->createTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(load()), Qt::QueuedConnection); } timer->start(interval); } void CoverLoader::load(const Song &song) { queue.append(LoadedCover(song)); startTimer(0); } void CoverLoader::load() { QList toDo; for (int i=0; i covers; foreach (const LoadedCover &s, toDo) { DBUG << s.song.artist << s.song.albumId() << s.song.size; int size=s.song.size; if (sizedevicePixelRatio(); cache.setMaxCost(10*1024*1024); } void Covers::readConfig() { saveInMpdDir=Settings::self()->storeCoversInMpdDir(); fetchCovers=Settings::self()->fetchCovers(); } void Covers::stop() { if (downloader) { disconnect(downloader, SIGNAL(artistImage(Song,QImage,QString)), this, SLOT(artistImageDownloaded(Song,QImage,QString))); disconnect(downloader, SIGNAL(composerImage(Song,QImage,QString)), this, SLOT(composerImageDownloaded(Song,QImage,QString))); disconnect(downloader, SIGNAL(cover(Song,QImage,QString)), this, SLOT(coverDownloaded(Song,QImage,QString))); downloader->stop(); downloader=0; } if (locator) { disconnect(locator, SIGNAL(located(QList)), this, SLOT(located(QList))); locator->stop(); locator=0; } if (loader) { disconnect(loader, SIGNAL(loaded(QList)), this, SLOT(loaded(QList))); loader->stop(); loader=0; } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND cleanCdda(); #endif } static inline Song setSizeRequest(Song s, int size) { s.setSpecificSizeRequest(size); return s; } void Covers::clearNameCache() { mutex.lock(); filenames.clear(); mutex.unlock(); } void Covers::clearScaleCache() { cache.clear(); } QPixmap * Covers::getScaledCover(const Song &song, int size) { if (size<4 || song.isUnknownAlbum()) { return 0; } // DBUG_CLASS("Covers") << song.albumArtist() << song.album << song.mbAlbumId << size; QString key=cacheKey(song, size); QPixmap *pix(cache.object(key)); if (!pix) { QImage img=loadScaledCover(song, size); if (!img.isNull()) { pix=new QPixmap(QPixmap::fromImage(img)); } if (pix) { cache.insert(key, pix, pix->width()*pix->height()*(pix->depth()/8)); } else { // Create a dummy image so that we dont keep on stating files that do not exist! pix=new QPixmap(1, 1); cache.insert(key, pix, 1); } cacheSizes.insert(size); } return pix && pix->width()>1 ? pix : 0; } QPixmap * Covers::saveScaledCover(const QImage &img, const Song &song, int size) { if (size<4) { return 0; } if (!isOnlineServiceImage(song)) { QString fileName=getScaledCoverName(song, size, true); bool status=img.save(fileName, constScaledFormat); DBUG_CLASS("Covers") << song.albumArtist() << song.album << song.mbAlbumId() << size << fileName << status; } QPixmap *pix=new QPixmap(QPixmap::fromImage(img)); cache.insert(cacheKey(song, size), pix, pix->width()*pix->height()*(pix->depth()/8)); cacheSizes.insert(size); return pix; } QPixmap * Covers::defaultPix(const Song &song, int size, int origSize) { bool podcast=!song.isArtistImageRequest() && !song.isComposerImageRequest() && song.isFromOnlineService() && OnlineService::isPodcasts(song.onlineService()); QString key=song.isArtistImageRequest() ? QLatin1String("artist-") : song.isComposerImageRequest() ? QLatin1String("composer-") : podcast ? QLatin1String("podcast-") : QLatin1String("album-"); key+=QString::number(size); QPixmap *pix=cache.object(key); if (!pix) { const Icon &icn=song.isArtistImageRequest() || song.isComposerImageRequest() ? Icons::self()->artistIcon : podcast ? Icons::self()->podcastIcon : Icons::self()->albumIcon(size); pix=new QPixmap(icn.pixmap(size, size).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); if (size!=origSize) { pix->setDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of dummy pixmap" << devicePixelRatio; } cache.insert(key, pix, 1); cacheSizes.insert(size); } return pix; } QPixmap * Covers::get(const Song &song, int size, bool urgent) { // DBUG_CLASS("Covers") << song.albumArtist() << song.album << song.mbAlbumId() << song.composer() << song.isArtistImageRequest() << song.isComposerImageRequest() << size << urgent; QString key; QPixmap *pix=0; if (0==size) { size=22; } int origSize=size; if (sizeartistIcon.pixmap(size, size).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else if (Song::SingleTracks==song.type) { pix=new QPixmap(Icons::self()->albumIcon(size).pixmap(size, size).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else if (song.isStandardStream()) { pix=new QPixmap(Icons::self()->streamIcon.pixmap(size, size).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else if (isOnlineServiceImage(song)) { Covers::Image img=serviceImage(song); if (!img.img.isNull()) { pix=new QPixmap(QPixmap::fromImage(img.img.scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation))); } } if (pix) { if (size!=origSize) { pix->setDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of cover" << devicePixelRatio; } cache.insert(key, pix, 1); cacheSizes.insert(size); } } if (!pix) { if (urgent) { QImage cached=loadScaledCover(song, size); if (cached.isNull()) { Image img=findImage(song, false); if (!img.img.isNull()) { pix=saveScaledCover(scale(song, img.img, size), song, size); if (size!=origSize) { pix->setDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of saved scaled cover" << devicePixelRatio; } return pix; } else if (constNoCover==img.fileName) { return defaultPix(song, size, origSize); } } else { pix=new QPixmap(QPixmap::fromImage(cached)); if (size!=origSize) { pix->setDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of loaded scaled cover" << devicePixelRatio; } cache.insert(key, pix); cacheSizes.insert(size); return pix; } } tryToLoad(setSizeRequest(song, origSize)); // Create a dummy image so that we dont keep on locating/loading/downloading files that do not exist! pix=new QPixmap(1, 1); if (size!=origSize) { pix->setDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of dummy cover" << devicePixelRatio; } cache.insert(key, pix, 1); cacheSizes.insert(size); } if (pix && pix->width()>1) { return pix; } } return defaultPix(song, size, origSize); } void Covers::coverDownloaded(const Song &song, const QImage &img, const QString &file) { gotAlbumCover(song, img, file); } void Covers::artistImageDownloaded(const Song &song, const QImage &img, const QString &file) { gotArtistImage(song, img, file); } void Covers::composerImageDownloaded(const Song &song, const QImage &img, const QString &file) { gotComposerImage(song, img, file); } bool Covers::updateCache(const Song &song, const QImage &img, bool dummyEntriesOnly) { // Only remove all scaled entries from disk if the cover has been set by the CoverDialog // This is the only case where dummyEntriesOnly==false // dummyEntriesOnly => entries in cache that have a 'dummy' pixmap if (!dummyEntriesOnly) { clearScaledCache(song); } #ifdef ENABLE_DEVICES_SUPPORT bool emitLoaded=!song.isFromDevice(); #else bool emitLoaded=true; #endif bool updated=false; foreach (int s, cacheSizes) { QString key=cacheKey(song, s); QPixmap *pix(cache.object(key)); if (pix && (!dummyEntriesOnly || pix->width()<2)) { double pixRatio=pix->devicePixelRatio(); cache.remove(key); if (!img.isNull()) { DBUG_CLASS("Covers"); QPixmap *p=saveScaledCover(scale(song, img, s), song, s); if (p) { p->setDevicePixelRatio(pixRatio); DBUG << "Set pixel ratio of updated cached pixmap" << devicePixelRatio; } if (p && emitLoaded) { if (pixRatio>1.00001) { s/=pixRatio; } emit loaded(song, s); updated=true; } } } } return updated; } void Covers::tryToLocate(const Song &song) { if (!locator) { qRegisterMetaType("LocatedCover"); qRegisterMetaType >("QList"); locator=new CoverLocator(); connect(locator, SIGNAL(located(QList)), this, SLOT(located(QList)), Qt::QueuedConnection); connect(this, SIGNAL(locate(Song)), locator, SLOT(locate(Song)), Qt::QueuedConnection); } emit locate(song); } void Covers::tryToDownload(const Song &song) { #ifdef ENABLE_DEVICES_SUPPORT if (song.isFromDevice()) { return; } #endif if (!downloader) { downloader=new CoverDownloader(); connect(this, SIGNAL(download(Song)), downloader, SLOT(download(Song)), Qt::QueuedConnection); connect(downloader, SIGNAL(artistImage(Song,QImage,QString)), this, SLOT(artistImageDownloaded(Song,QImage,QString)), Qt::QueuedConnection); connect(downloader, SIGNAL(cover(Song,QImage,QString)), this, SLOT(coverDownloaded(Song,QImage,QString)), Qt::QueuedConnection); } emit download(song); } void Covers::tryToLoad(const Song &song) { if (!loader) { qRegisterMetaType("LoadedCover"); qRegisterMetaType >("QList"); loader=new CoverLoader(); connect(loader, SIGNAL(loaded(QList)), this, SLOT(loaded(QList)), Qt::QueuedConnection); connect(this, SIGNAL(load(Song)), loader, SLOT(load(Song)), Qt::QueuedConnection); } emit load(song); } Covers::Image Covers::findImage(const Song &song, bool emitResult) { Covers::Image i=locateImage(song); if (!i.img.isNull()) { if (song.isArtistImageRequest()) { gotArtistImage(song, i.img, i.fileName, emitResult); } else if (song.isComposerImageRequest()) { gotComposerImage(song, i.img, i.fileName, emitResult); } else { gotAlbumCover(song, i.img, i.fileName, emitResult); } } return i; } static Covers::Image findCoverInDir(const Song &song, const QString &dirName, const QStringList &coverFileNames, const QString &songFileName=QString()) { foreach (const QString &fileName, coverFileNames) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cover image" << QString(dirName+fileName); return Covers::Image(img, dirName+fileName); } } } if (!songFileName.isEmpty()) { #ifdef TAGLIB_FOUND DBUG_CLASS("Covers") << "Checking file" << songFileName; if (QFile::exists(songFileName)) { QImage img(Tags::readImage(songFileName)); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cover image from tag" << songFileName; // Save image to cache folder - required for MPRIS if (!song.isCdda() && !song.isArtistImageRequest()) { QString dir = Utils::cacheDir(Covers::constCoverDir+Covers::encodeName(song.albumArtist()), true); if (!dir.isEmpty()) { QString fileName=dir+Covers::encodeName(song.album)+".jpg"; if (img.save(fileName)) { return Covers::Image(img, fileName); } } } return Covers::Image(img, Covers::constCoverInTagPrefix+songFileName); } } #endif } QStringList files=QDir(dirName).entryList(QStringList() << QLatin1String("*.jpg") << QLatin1String("*.png"), QDir::Files|QDir::Readable); foreach (const QString &fileName, files) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cover image" << QString(dirName+fileName); return Covers::Image(img, dirName+fileName); } } return Covers::Image(); } Covers::Image Covers::locateImage(const Song &song) { DBUG_CLASS("Covers") << song.file << song.artist << song.albumartist << song.album << song.type; if (song.isFromOnlineService()) { QString id=song.onlineService(); Covers::Image img; if (isOnlineServiceImage(song)) { img=serviceImage(song); if (!img.img.isNull()) { DBUG_CLASS("Covers") << "Got cover online image" << QString(img.fileName) << "for" << id; return img; } } img.fileName=song.extraField(Song::OnlineImageCacheName); if (img.fileName.isEmpty()) { img.fileName=Utils::cacheDir(id.toLower(), true)+Covers::encodeName(song.album.isEmpty() ? song.artist : (song.artist+" - "+song.album))+".jpg"; if (!QFile::exists(img.fileName)) { img.fileName=Utils::changeExtension(img.fileName, ".png"); } } if (!img.fileName.isEmpty()) { img.img=loadImage(img.fileName); if (!img.img.isNull()) { DBUG_CLASS("Covers") << "Got cover online image" << QString(img.fileName) << "for" << id; return img; } } DBUG_CLASS("Covers") << "Failed to locate online image for" << id; return Image(); } #ifdef ENABLE_DEVICES_SUPPORT if (song.isFromDevice()) { Device *dev=DevicesModel::self()->device(song.deviceId()); Image img; if (dev) { img=dev->requestCover(song); } if (img.img.isNull()) { DBUG_CLASS("Covers") << "Failed to locate device image for" << song.deviceId(); } return img; } #endif QString prevFileName=Covers::self()->getFilename(song); if (!prevFileName.isEmpty()) { if (constNoCover==prevFileName) { DBUG_CLASS("Covers") << "No cover"; return Image(QImage(), constNoCover); } #ifdef TAGLIB_FOUND QImage img; if (prevFileName.startsWith(constCoverInTagPrefix)) { img=Tags::readImage(prevFileName.mid(constCoverInTagPrefix.length())); } else { img=loadImage(prevFileName); } #else QImage img(prevFileName); #endif if (!img.isNull()) { DBUG_CLASS("Covers") << "Found previous" << prevFileName; return Image(img, prevFileName); } } QString songFile=song.filePath(); QString dirName; bool haveAbsPath=songFile.startsWith(Utils::constDirSep); if (song.isCantataStream()) { QUrl url(songFile); QUrlQuery u(url); songFile=u.hasQueryItem("file") ? u.queryItemValue("file") : QString(); } QStringList coverFileNames; if (song.isArtistImageRequest()) { QString artistFile=artistFileName(song); QString basicArtist=song.basicArtist(); coverFileNames=QStringList() << basicArtist+".jpg" << basicArtist+".png" << artistFile+".jpg" << artistFile+".png"; } else if (song.isComposerImageRequest()) { QString composerFile=composerFileName(song); coverFileNames=QStringList() << composerFile+".jpg" << composerFile+".png"; } else { QString mpdCover=albumFileName(song); if (!mpdCover.isEmpty()) { for (int e=0; constExtensions[e]; ++e) { coverFileNames << mpdCover+constExtensions[e]; } } foreach (const QString &std, standardNames()) { if (!coverFileNames.contains(std)) { coverFileNames << std; } } for (int e=0; constExtensions[e]; ++e) { coverFileNames+=song.albumArtist()+QLatin1String(" - ")+song.album+constExtensions[e]; } for (int e=0; constExtensions[e]; ++e) { coverFileNames+=song.album+constExtensions[e]; } } if (!songFile.isEmpty() && !songFile.startsWith("http:/") && !song.isCdda() && (haveAbsPath || (!MPDConnection::self()->getDetails().dir.isEmpty() && !MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http://")) ) ) ) { dirName=songFile.endsWith(Utils::constDirSep) ? (haveAbsPath ? QString() : MPDConnection::self()->getDetails().dir)+songFile : Utils::getDir((haveAbsPath ? QString() : MPDConnection::self()->getDetails().dir)+songFile); if (song.isArtistImageRequest() || song.isComposerImageRequest()) { for (int level=0; level<2; ++level) { foreach (const QString &fileName, coverFileNames) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got artist/composer image" << QString(dirName+fileName); return Image(img, dirName+fileName); } } } QDir d(dirName); d.cdUp(); dirName=Utils::fixPath(d.absolutePath()); } // For various artists tracks, or for non-MPD files, see if we have a matching image in MPD. // e.g. artist=Wibble, look for $mpdDir/Wibble/artist.png if (!song.isComposerImageRequest() && (song.isVariousArtists() || song.isNonMPD())) { QString basicArtist=song.basicArtist(); dirName=MPDConnection::self()->getDetails().dirReadable ? MPDConnection::self()->getDetails().dir : QString(); if (!dirName.isEmpty() && !dirName.startsWith(QLatin1String("http:/"))) { dirName+=basicArtist+Utils::constDirSep; foreach (const QString &fileName, coverFileNames) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got artist image" << QString(dirName+fileName); return Image(img, dirName+fileName); } } } } } } else { Covers::Image img=findCoverInDir(song, dirName, coverFileNames, haveAbsPath ? songFile : (MPDConnection::self()->getDetails().dir+songFile)); if (!img.img.isNull()) { return img; } QStringList dirs=QDir(dirName).entryList(QDir::Dirs|QDir::Readable|QDir::NoDotAndDotDot); foreach (const QString &dir, dirs) { img=findCoverInDir(song, dirName+dir+Utils::constDirSep, coverFileNames); if (!img.img.isNull()) { return img; } } } } if (song.isArtistImageRequest() || song.isComposerImageRequest()) { QString artistOrComposer=encodeName(song.isComposerImageRequest() ? song.composer() : song.albumArtist()); if (!artistOrComposer.isEmpty()) { // For non-MPD tracks, see if we actually have a saved MPD cover... if (MPDConnection::self()->getDetails().dirReadable) { QString songDir=artistOrComposer+Utils::constDirSep; if (!song.file.startsWith(songDir)) { QString dirName=MPDConnection::self()->getDetails().dir+songDir; if (QDir(dirName).exists()) { foreach (const QString &fileName, coverFileNames) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got artist/composer image" << QString(dirName+fileName); return Image(img, dirName+fileName); } } } } } } // Check if cover is already cached QString dir(Utils::cacheDir(constCoverDir, false)); for (int e=0; constExtensions[e]; ++e) { DBUG_CLASS("Covers") << "Checking cache file" << QString(dir+artistOrComposer+constExtensions[e]); if (QFile::exists(dir+artistOrComposer+constExtensions[e])) { QImage img=loadImage(dir+artistOrComposer+constExtensions[e]); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cached artist/composer image" << QString(dir+artistOrComposer+constExtensions[e]); return Image(img, dir+artistOrComposer+constExtensions[e]); } } } } } else { QString artist=encodeName(song.albumArtist()); QString album=encodeName(song.album); // For non-MPD tracks, see if we actually have a saved MPD cover... if (MPDConnection::self()->getDetails().dirReadable) { QString songDir=artist+Utils::constDirSep+album+Utils::constDirSep; if (!song.file.startsWith(songDir)) { QString dirName=MPDConnection::self()->getDetails().dir+songDir; if (QDir(dirName).exists()) { foreach (const QString &fileName, coverFileNames) { DBUG_CLASS("Covers") << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img=loadImage(dirName+fileName); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cover image" << QString(dirName+fileName); return Image(img, dirName+fileName); } } } } } } // Check if cover is already cached QString dir(Utils::cacheDir(constCoverDir+artist, false)); for (int e=0; constExtensions[e]; ++e) { DBUG_CLASS("Covers") << "Checking cache file" << QString(dir+album+constExtensions[e]); if (QFile::exists(dir+album+constExtensions[e])) { QImage img=loadImage(dir+album+constExtensions[e]); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cached cover image" << QString(dir+album+constExtensions[e]); return Image(img, dir+album+constExtensions[e]); } } } } DBUG_CLASS("Covers") << "Failed to locate image"; return Image(); } // Dont return song files as cover files! static Covers::Image fix(const Covers::Image &img) { return Covers::Image(img.img, img.validFileName() ? img.fileName : QString()); } bool Covers::Image::validFileName() const { return !fileName.isEmpty() && !fileName.startsWith(constCoverInTagPrefix) && constNoCover!=fileName; } Covers::Image Covers::requestImage(const Song &song, bool urgent) { if (song.isUnknownAlbum()) { return Image(); } DBUG << song.file << song.artist << song.albumartist << song.album << song.composer() << song.isArtistImageRequest() << song.isComposerImageRequest(); if (urgent && song.isFromOnlineService()) { Covers::Image img=serviceImage(song); if (!img.img.isNull()) { return img; } } #ifdef ENABLE_DEVICES_SUPPORT if (song.isFromDevice()) { Device *dev=DevicesModel::self()->device(song.deviceId()); if (dev) { return dev->requestCover(song); } return Image(); } #endif QString key=songKey(song); if (currentImageRequests.contains(key)) { return Image(); } if (!urgent) { currentImageRequests.insert(key); tryToLocate(song); return Image(); } Image img=findImage(song, false); if (img.img.isNull() && Song::OnlineSvrTrack!=song.type && constNoCover!=img.fileName) { DBUG << song.file << song.artist << song.albumartist << song.album << "Need to download"; currentImageRequests.insert(key); tryToDownload(song); } return fix(img); } void Covers::located(const QList &covers) { foreach (const LocatedCover &cvr, covers) { if (!cvr.img.isNull()) { if (cvr.song.isArtistImageRequest()) { gotArtistImage(cvr.song, cvr.img, cvr.fileName); } else if (cvr.song.isComposerImageRequest()) { gotComposerImage(cvr.song, cvr.img, cvr.fileName); } else { gotAlbumCover(cvr.song, cvr.img, cvr.fileName); } } else { // Failed to locate a cover, so try to download one... tryToDownload(cvr.song); } } } void Covers::loaded(const QList &covers) { foreach (const LoadedCover &cvr, covers) { if (!cvr.img.isNull()) { int size=cvr.song.size; int origSize=size; if (sizesetDevicePixelRatio(devicePixelRatio); DBUG << "Set pixel ratio of loaded pixmap" << devicePixelRatio; } cache.insert(cacheKey(cvr.song, size), pix); cacheSizes.insert(size); emit loaded(cvr.song, cvr.song.size); } else { // Failed to load a scaled cover, try locating non-scaled cover... tryToLocate(cvr.song); } } } void Covers::updateCover(const Song &song, const QImage &img, const QString &file) { updateCache(song, img, false); if (!file.isEmpty()) { filenames[songKey(song)]=file; } #ifdef ENABLE_DEVICES_SUPPORT if (!song.isFromDevice()) #endif emit coverUpdated(song, img, file); } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND void Covers::cleanCdda() { QString dir = Utils::cacheDir(Covers::constCddaCoverDir, false); if (!dir.isEmpty()) { QDir d(dir); QStringList entries=d.entryList(QDir::Files|QDir::NoSymLinks|QDir::NoDotAndDotDot); foreach (const QString &f, entries) { if (f.endsWith(".jpg") || f.endsWith(".png")) { QFile::remove(dir+f); } } d.cdUp(); d.rmdir(dir); } } #endif void Covers::gotAlbumCover(const Song &song, const QImage &img, const QString &fileName, bool emitResult) { QString key=albumKey(song); currentImageRequests.remove(key); // if (!img.isNull() && !fileName.isEmpty() && !fileName.startsWith("http:/")) { mutex.lock(); filenames.insert(key, fileName.isEmpty() ? constNoCover : fileName); mutex.unlock(); // } if (emitResult) { bool updatedCover=false; if (!img.isNull()) { updatedCover=updateCache(song, img, true); } if (updatedCover || song.isCdda()/* || !song.isSpecificSizeRequest()*/) { DBUG << "emit cover" << song.file << song.artist << song.albumartist << song.album << song.mbAlbumId() << img.width() << img.height() << fileName; emit cover(song, img, fileName.startsWith(constCoverInTagPrefix) ? QString() : fileName); } } } void Covers::gotArtistImage(const Song &song, const QImage &img, const QString &fileName, bool emitResult) { QString key=artistKey(song); currentImageRequests.remove(key); // if (!img.isNull() && !fileName.isEmpty() && !fileName.startsWith("http:/")) { mutex.lock(); filenames.insert(key, fileName.isEmpty() ? constNoCover : fileName); mutex.unlock(); // } if (emitResult) { if (!img.isNull()) { updateCache(song, img, true); } // if (!song.isSpecificSizeRequest()) { DBUG << "emit artistImage" << song.file << song.artist << song.albumartist << song.album << img.width() << img.height() << fileName; emit artistImage(song, img, fileName.startsWith(constCoverInTagPrefix) ? QString() : fileName); // } } } void Covers::gotComposerImage(const Song &song, const QImage &img, const QString &fileName, bool emitResult) { QString key=composerKey(song); currentImageRequests.remove(key); // if (!img.isNull() && !fileName.isEmpty() && !fileName.startsWith("http:/")) { mutex.lock(); filenames.insert(key, fileName.isEmpty() ? constNoCover : fileName); mutex.unlock(); // } if (emitResult) { if (!img.isNull()) { updateCache(song, img, true); } // if (!song.isSpecificSizeRequest()) { DBUG << "emit composerImage" << song.file << song.artist << song.albumartist << song.album << song.composer() << img.width() << img.height() << fileName; emit composerImage(song, img, fileName.startsWith(constCoverInTagPrefix) ? QString() : fileName); // } } } QString Covers::getFilename(const Song &s) { mutex.lock(); QMap::ConstIterator fileIt=filenames.find(songKey(s)); QString f=fileIt==filenames.end() ? QString() : fileIt.value(); mutex.unlock(); return f; } cantata-2.2.0/gui/covers.h000066400000000000000000000202601316350454000153650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COVERS_H #define COVERS_H #include #include #include #include #include #include #include #include #include "mpd-interface/song.h" #include "config.h" class QString; class Thread; class NetworkJob; class QMutex; class QTimer; class NetworkAccessManager; class CoverDownloader : public QObject { Q_OBJECT public: enum JobType { JobHttpJpg, JobHttpPng, JobLastFm }; struct Job { Job(const Song &s, const QString &d) : song(s), filePath(s.filePath()), dir(d), type(JobLastFm), level(0) { } Song song; QString filePath; QString dir; JobType type; int level; }; CoverDownloader(); ~CoverDownloader() { } void stop(); public Q_SLOTS: void download(const Song &s); Q_SIGNALS: void cover(const Song &song, const QImage &img, const QString &file); void artistImage(const Song &song, const QImage &img, const QString &file); void composerImage(const Song &song, const QImage &img, const QString &file); private: bool downloadViaHttp(Job &job, JobType type); void downloadViaLastFm(Job &job); private Q_SLOTS: void lastFmCallFinished(); void jobFinished(); void onlineJobFinished(); private: void failed(const Job &job); QString saveImg(const Job &job, const QImage &img, const QByteArray &raw); QHash::Iterator findJob(const Job &job); NetworkAccessManager * network(); private: QHash jobs; private: Thread *thread; NetworkAccessManager *manager; }; struct LocatedCover { LocatedCover(const Song &s=Song(), const QImage &i=QImage(), const QString &f=QString()) : song(s), img(i), fileName(f) { } Song song; QImage img; QString fileName; }; class CoverLocator : public QObject { Q_OBJECT public: CoverLocator(); ~CoverLocator() { } void stop(); Q_SIGNALS: void located(const QList &covers); public Q_SLOTS: void locate(const Song &s); void locate(); private: void startTimer(int interval); private: Thread *thread; QTimer *timer; QList queue; }; struct LoadedCover { LoadedCover(const Song &sng=Song(), const QImage &i=QImage()) : song(sng), img(i) { } Song song; QImage img; }; class CoverLoader : public QObject { Q_OBJECT public: CoverLoader(); ~CoverLoader() { } void stop(); Q_SIGNALS: void loaded(const QList &covers); public Q_SLOTS: void load(const Song &song); void load(); private: void startTimer(int interval); private: Thread *thread; QTimer *timer; QList queue; }; class Covers : public QObject { Q_OBJECT public: struct Image { Image(const QImage &i=QImage(), const QString &f=QString()) : img(i) , fileName(f) { } bool validFileName() const; QImage img; QString fileName; }; static void enableDebug(); static bool debugEnabled(); static const QSize constMaxSize; static const QLatin1String constLastFmApiKey; static const QLatin1String constCoverDir; static const QLatin1String constScaledCoverDir; static const QLatin1String constFileName; static const QLatin1String constCddaCoverDir; static const QLatin1String constArtistImage; static const QLatin1String constComposerImage; static const QString constCoverInTagPrefix; static Covers * self(); // static bool isCoverFile(const QString &file); static bool copyImage(const QString &sourceDir, const QString &destDir, const QString &coverFile, const QString &destName, unsigned short maxSize=0); static bool copyCover(const Song &song, const QString &sourceDir, const QString &destDir, const QString &name=QString(), unsigned short maxSize=0); static const QStringList &standardNames(); static QString encodeName(QString name); static QString albumFileName(const Song &song); static QString artistFileName(const Song &song); static QString composerFileName(const Song &song); static QString fixArtist(const QString &artist); static bool isJpg(const QByteArray &data); static bool isPng(const QByteArray &data); static const char * imageFormat(const QByteArray &data); Covers(); void readConfig(); void stop(); void clearNameCache(); void clearScaleCache(); QPixmap * getScaledCover(const Song &song, int size); QPixmap * saveScaledCover(const QImage &img, const Song &song, int size); // Get cover image of specified size. If this is not found 0 will be returned, and the cover // will be downloaded. QPixmap * get(const Song &song, int size, bool urgent=false); // Get QImage and filename associated with Song request. If this is not found, then the cover // will NOT be downloaded. 'emitResult' controls whether 'cover()/artistImage()' is emitted if // a cover is found. Image getImage(const Song &song) { return findImage(song, false); } // Get QImage and filename associated with Song request. If this is not found, then the cover // will be downloaded. If more than 5 covers have been requested in an event-loop iteration, then // the cover requests are placed on a queue. Image requestImage(const Song &song, bool urgent=false); void updateCover(const Song &song, const QImage &img, const QString &file); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND void cleanCdda(); #endif static Image locateImage(const Song &song); Q_SIGNALS: void download(const Song &s); void locate(const Song &s); void load(const Song &song); void loaded(const Song &song, int s); void cover(const Song &song, const QImage &img, const QString &file); void coverUpdated(const Song &song, const QImage &img, const QString &file); void artistImage(const Song &song, const QImage &img, const QString &file); void composerImage(const Song &song, const QImage &img, const QString &file); private Q_SLOTS: void located(const QList &covers); void loaded(const QList &covers); void coverDownloaded(const Song &song, const QImage &img, const QString &file); void artistImageDownloaded(const Song &song, const QImage &img, const QString &file); void composerImageDownloaded(const Song &song, const QImage &img, const QString &file); private: QPixmap * defaultPix(const Song &song, int size, int origSize); void tryToLocate(const Song &song); void tryToDownload(const Song &song); void tryToLoad(const Song &song); Image findImage(const Song &song, bool emitResult); bool updateCache(const Song &song, const QImage &img, bool dummyEntriesOnly); void gotAlbumCover(const Song &song, const QImage &img, const QString &fileName, bool emitResult=true); void gotArtistImage(const Song &song, const QImage &img, const QString &fileName, bool emitResult=true); void gotComposerImage(const Song &song, const QImage &img, const QString &fileName, bool emitResult=true); QString getFilename(const Song &s); private: QSet currentImageRequests; QList queue; QSet cacheSizes; QCache cache; QMap filenames; CoverDownloader *downloader; CoverLocator *locator; CoverLoader *loader; QMutex mutex; }; #endif cantata-2.2.0/gui/currentcover.cpp000066400000000000000000000211231316350454000171370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "currentcover.h" #include "covers.h" #include "config.h" #include "widgets/icons.h" #include "support/utils.h" #include "support/globalstatic.h" #include "online/onlineservice.h" #include #include #if !defined Q_OS_WIN && !defined Q_OS_MAC static void themes(const QString &theme, QStringList &iconThemes) { if (iconThemes.contains(theme)) { return; } QString lower=theme.toLower(); iconThemes << theme; if (lower!=theme) { iconThemes << lower; } QStringList paths=QIcon::themeSearchPaths(); QString key("Inherits="); foreach (const QString &p, paths) { QString index(p+"/"+theme+"/index.theme"); QFile f(index); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { while (!f.atEnd()) { QString line=QString::fromUtf8(f.readLine()).trimmed().simplified(); if (line.startsWith(key)) { QStringList inherited=line.mid(key.length()).split(",", QString::SkipEmptyParts); foreach (const QString &i, inherited) { themes(i, iconThemes); } return; } } } } } void CurrentCover::initIconThemes() { if (iconThemes.isEmpty()) { themes(Icon::currentTheme(), iconThemes); } } QString CurrentCover::findIcon(const QStringList &names) { initIconThemes(); QList sizes=QList() << 256 << 128 << 64 << 48 << 32 << 24 << 22; QStringList paths=QIcon::themeSearchPaths(); QStringList sections=QStringList() << "categories" << "devices"; foreach (const QString &p, paths) {; foreach (const QString &n, names) { foreach (const QString &theme, iconThemes) { QMap files; foreach (int s, sizes) { foreach (const QString §, sections) { QString f(p+"/"+theme+"/"+QString::number(s)+"x"+QString::number(s)+"/"+sect+"/"+n+".png"); if (QFile::exists(f)) { files.insert(s, f); break; } f=QString(p+"/"+theme+"/"+QString::number(s)+"x"+QString::number(s)+"/"+sect+"/"+n+".svg"); if (QFile::exists(f)) { files.insert(s, f); break; } f=QString(p+"/"+theme+"/"+sect+"/"+QString::number(s)+"/"+n+".svg"); if (QFile::exists(f)) { files.insert(s, f); break; } } } if (!files.isEmpty()) { foreach (int s, sizes) { if (files.contains(s)) { return files[s]; } } } } } } return QString(); } #endif GLOBAL_STATIC(CurrentCover, instance) CurrentCover::CurrentCover() : QObject(0) , enabled(false) , valid(false) , timer(0) { img=stdImage(false); coverFileName=noCoverFileName; } CurrentCover::~CurrentCover() { } const QImage & CurrentCover::stdImage(bool stream) { QImage &img=stream ? noStreamCover : noCover; if (img.isNull()) { int iconSize=Icon::stdSize(Utils::scaleForDpi(128)); img = (stream ? Icons::self()->streamIcon : Icons::self()->albumIcon(iconSize)).pixmap(iconSize, iconSize).toImage(); QString &file=stream ? noStreamCoverFileName : noCoverFileName; if (stream && file.isEmpty()) { QString iconFile=QString(CANTATA_SYS_ICONS_DIR+"stream.png"); if (QFile::exists(iconFile)) { file=iconFile; } } #if !defined Q_OS_WIN && !defined Q_OS_MAC if (file.isEmpty()) { file=findIcon(stream ? QStringList() << "applications-internet" : QStringList() << "media-optical" << "media-optical-audio"); } #endif } return img; } void CurrentCover::setEnabled(bool e) { if (enabled==e) { return; } enabled=e; if (enabled) { img=stdImage(false); connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &))); connect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &))); } else { disconnect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &))); disconnect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)), this, SLOT(coverRetrieved(const Song &, const QImage &, const QString &))); current=Song(); } } void CurrentCover::update(const Song &s) { if (!enabled) { return; } if (s.albumArtist()!=current.albumArtist() || s.album!=current.album || s.isStream()!=current.isStream() || s.onlineService()!=current.onlineService()) { current=s; if (timer) { timer->stop(); } if (!s.isUnknownAlbum() && ((!s.albumArtist().isEmpty() && !s.album.isEmpty() && !current.isStandardStream()) || OnlineService::showLogoAsCover(s))) { Covers::Image cImg=Covers::self()->requestImage(s, true); valid=!cImg.img.isNull(); if (valid) { coverFileName=cImg.fileName; img=cImg.img; emit coverFile(cImg.fileName); if (current.isFromOnlineService()) { if (coverFileName.startsWith(CANTATA_SYS_ICONS_DIR)) { emit coverImage(QImage()); } else { emit coverImage(cImg.img); } } else { emit coverImage(cImg.img); } } else { // We need to set the image here, so that TrayItem gets the correct 'noCover' image // ...but if Covers does eventually download a cover, we dont want valid->noCover->valid img=stdImage(false); // Start a timer - in case cover retrieval fails... if (!timer) { timer=new QTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(setDefault())); } timer->start(750); } } else { valid=true; img=stdImage(current.isStandardStream()); coverFileName=current.isStandardStream() ? noStreamCoverFileName : noCoverFileName; emit coverFile(coverFileName); emit coverImage(QImage()); } } } void CurrentCover::coverRetrieved(const Song &s, const QImage &newImage, const QString &file) { if (!s.isArtistImageRequest() && s.albumArtist()==current.albumArtist() && s.album==current.album) { if (timer) { timer->stop(); } valid=!newImage.isNull(); if (valid) { coverFileName=file; img=newImage; emit coverFile(file); emit coverImage(newImage); } else { coverFileName=current.isStandardStream() ? noStreamCoverFileName : noCoverFileName; emit coverFile(coverFileName); emit coverImage(QImage()); } } } void CurrentCover::setDefault() { coverFileName=current.isStandardStream() ? noStreamCoverFileName : noCoverFileName; emit coverFile(coverFileName); emit coverImage(QImage()); } cantata-2.2.0/gui/currentcover.h000066400000000000000000000041451316350454000166110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CURRENT_COVERW_H #define CURRENT_COVERW_H #include #include #include "mpd-interface/song.h" class QPixmap; class QTimer; class CurrentCover : public QObject { Q_OBJECT public: static CurrentCover * self(); CurrentCover(); virtual ~CurrentCover(); void setEnabled(bool e); void update(const Song &s); const Song & song() const { return current; } bool isValid() const { return valid; } const QString & fileName() const { return coverFileName; } const QImage &image() const { return img; } Q_SIGNALS: void coverImage(const QImage &img); void coverFile(const QString &name); private Q_SLOTS: void coverRetrieved(const Song &s, const QImage &img, const QString &file); void setDefault(); private: const QImage & stdImage(bool stream); #if !defined Q_OS_WIN && !defined Q_OS_MAC void initIconThemes(); QString findIcon(const QStringList &names); #endif private: bool enabled; bool valid; Song current; mutable QImage img; QString coverFileName; QImage noStreamCover; QImage noCover; QString noStreamCoverFileName; QString noCoverFileName; QTimer *timer; #if !defined Q_OS_WIN && !defined Q_OS_MAC QStringList iconThemes; #endif }; #endif cantata-2.2.0/gui/customactions.cpp000066400000000000000000000121741316350454000173170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "customactions.h" #include "mainwindow.h" #include "support/globalstatic.h" #include "support/configuration.h" #include #include GLOBAL_STATIC(CustomActions, instance) bool CustomActions::Command::operator<(const Command &o) const { int c=name.localeAwareCompare(o.name); if (c<0) { return true; } if (c==0) { return cmd.localeAwareCompare(o.cmd)<0; } return false; } CustomActions::CustomActions() : Action(tr("Custom Actions"), 0) , mainWindow(0) { QMenu *m=new QMenu(0); setMenu(m); Configuration cfg(metaObject()->className()); int count=cfg.get("count", 0); for (int i=0; iaddAction(cmd.act); commands.append(cmd); connect(cmd.act, SIGNAL(triggered()), this, SLOT(doAction())); } } setVisible(!commands.isEmpty()); } void CustomActions::set(QList cmds) { qSort(cmds); bool diff=cmds.length()!=commands.length(); if (!diff) { for (int i=0; iremoveAction(cmd.act); disconnect(cmd.act, SIGNAL(triggered()), this, SLOT(doAction())); cmd.act->deleteLater(); } commands.clear(); foreach (const Command &cmd, cmds) { Command c(cmd); c.act=new Action(c.name, this); m->addAction(c.act); commands.append(c); connect(c.act, SIGNAL(triggered()), this, SLOT(doAction())); } Configuration cfg; cfg.removeGroup(metaObject()->className()); if (!commands.isEmpty()) { cfg.beginGroup(metaObject()->className()); cfg.set("count", commands.count()); for (int i=0; i(sender()); if (!act) { return; } if (!MPDConnection::self()->getDetails().dirReadable) { return; } QString mpdDir=MPDConnection::self()->getDetails().dir; foreach (const Command &cmd, commands) { if (cmd.act==act) { QList songs=mainWindow->selectedSongs(); if (songs.isEmpty()) { return; } QStringList items; if (cmd.cmd.contains("%d")) { QSet used; foreach (const Song &s, songs) { if (Song::Playlist!=s.type) { QString dir=Utils::getDir(s.file); if (!used.contains(dir)) { used.insert(dir); items.append(mpdDir+dir); } } } } else { foreach (const Song &s, songs) { if (Song::Playlist!=s.type) { items.append(mpdDir+s.file); } } } if (!items.isEmpty()) { QStringList parts=cmd.cmd.split(' '); bool added=false; QString cmd=parts.takeFirst(); QStringList args; foreach (const QString &part, parts) { if (part.startsWith('%')) { args+=items; added=true; } else { args+=part; } } if (!added) { args+=items; } QProcess::startDetached(cmd, args); } return; } } } cantata-2.2.0/gui/customactions.h000066400000000000000000000033671316350454000167700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CUSTOM_ACTIONS_H #define CUSTOM_ACTIONS_H #include "support/action.h" class MainWindow; class CustomActions : public Action { Q_OBJECT public: struct Command { Command(const QString &n=QString(), const QString &c=QString(), Action *a=0) : name(n.trimmed()), cmd(c.trimmed()), act(a) { } bool operator<(const Command &o) const; bool operator==(const Command &o) const { return name==o.name && cmd==o.cmd; } bool operator!=(const Command &o) const { return !(*this==o); } QString name; QString cmd; Action *act; }; static CustomActions * self(); CustomActions(); void set(QList cmds); const QList & commandList() const { return commands; } void setMainWindow(MainWindow *mw) { mainWindow=mw; } private Q_SLOTS: void doAction(); private: MainWindow *mainWindow; QList commands; }; #endif cantata-2.2.0/gui/customactionssettings.cpp000066400000000000000000000144461316350454000211040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "customactionssettings.h" #include "customactions.h" #include "support/messagebox.h" #include "widgets/basicitemdelegate.h" #include "widgets/icons.h" #include "widgets/notelabel.h" #include #include #include #include #include #include CustomActionDialog::CustomActionDialog(QWidget *p) : Dialog(p) { QWidget *widget=new QWidget(this); QFormLayout *layout=new QFormLayout(widget); nameEntry=new LineEdit(widget); commandEntry=new LineEdit(widget); nameEntry->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); commandEntry->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); layout->addRow(new QLabel(tr("Name:"), widget), nameEntry); layout->addRow(new QLabel(tr("Command:"), widget), commandEntry); layout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); NoteLabel *note=new NoteLabel(widget); note->setText(tr("In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command.")); layout->setWidget(2, QFormLayout::SpanningRole, note); layout->setMargin(0); setButtons(Dialog::Ok|Dialog::Cancel); setMainWidget(widget); setMinimumWidth(400); ensurePolished(); adjustSize(); } bool CustomActionDialog::create() { setCaption(tr("Add New Command")); nameEntry->setText(QString()); commandEntry->setText(QString()); return QDialog::Accepted==exec(); } bool CustomActionDialog::edit(const QString &name, const QString &cmd) { setCaption(tr("Edit Command")); nameEntry->setText(name); commandEntry->setText(cmd); return QDialog::Accepted==exec(); } static inline void setResizeMode(QHeaderView *hdr, int idx, QHeaderView::ResizeMode mode) { hdr->setSectionResizeMode(idx, mode); } CustomActionsSettings::CustomActionsSettings(QWidget *parent) : QWidget(parent) , dlg(0) { QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); QLabel *label=new QLabel(tr("To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command " "command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views."), this); label->setWordWrap(true); layout->addWidget(label, 0, 0, 1, 2); tree=new QTreeWidget(this); add=new QPushButton(this); edit=new QPushButton(this); del=new QPushButton(this); layout->addWidget(tree, 1, 0, 4, 1); layout->addWidget(add, 1, 1, 1, 1); layout->addWidget(edit, 2, 1, 1, 1); layout->addWidget(del, 3, 1, 1, 1); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding), 4, 1); add->setText(tr("Add")); edit->setText(tr("Edit")); del->setText(tr("Remove")); edit->setEnabled(false); del->setEnabled(false); tree->setHeaderLabels(QStringList() << tr("Name") << tr("Command")); tree->setAllColumnsShowFocus(true); tree->setSelectionMode(QAbstractItemView::ExtendedSelection); tree->setRootIsDecorated(false); tree->setSortingEnabled(true); setResizeMode(tree->header(), 0, QHeaderView::ResizeToContents); tree->header()->setStretchLastSection(true); tree->setAlternatingRowColors(false); tree->setItemDelegate(new BasicItemDelegate(this)); connect(tree, SIGNAL(itemSelectionChanged()), this, SLOT(controlButtons())); connect(add, SIGNAL(clicked()), SLOT(addCommand())); connect(edit, SIGNAL(clicked()), SLOT(editCommand())); connect(del, SIGNAL(clicked()), SLOT(delCommand())); } CustomActionsSettings::~CustomActionsSettings() { } void CustomActionsSettings::controlButtons() { int selCount=tree->selectedItems().count(); edit->setEnabled(1==selCount); del->setEnabled(selCount>0); } void CustomActionsSettings::load() { foreach (const CustomActions::Command &cmd, CustomActions::self()->commandList()) { new QTreeWidgetItem(tree, QStringList() << cmd.name << cmd.cmd); } } void CustomActionsSettings::save() { QList commands; for (int i=0; itopLevelItemCount(); ++i) { commands.append((CustomActions::Command(tree->topLevelItem(i)->text(0), tree->topLevelItem(i)->text(1)))); } CustomActions::self()->set(commands); } void CustomActionsSettings::addCommand() { if (!dlg) { dlg=new CustomActionDialog(this); } if (dlg->create() && !dlg->nameText().isEmpty() && !dlg->commandText().isEmpty()) { new QTreeWidgetItem(tree, QStringList() << dlg->nameText() << dlg->commandText()); } } void CustomActionsSettings::editCommand() { QList items=tree->selectedItems(); if (1!=items.count()) { return; } if (!dlg) { dlg=new CustomActionDialog(this); } QTreeWidgetItem *item=items.at(0); if (dlg->edit(item->text(0), item->text(1)) && !dlg->nameText().isEmpty() && !dlg->commandText().isEmpty()) { item->setText(0, dlg->nameText()); item->setText(1, dlg->commandText()); } } void CustomActionsSettings::delCommand() { if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Remove the selected commands?"), QString(), GuiItem(tr("Remove")), StdGuiItem::cancel())) { foreach (QTreeWidgetItem *i, tree->selectedItems()) { delete i; } } } cantata-2.2.0/gui/customactionssettings.h000066400000000000000000000035001316350454000205360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CUSTOMACTIONS_SETTINGS_H #define CUSTOMACTIONS_SETTINGS_H #include "support/dialog.h" #include "support/lineedit.h" #include #include class QPushButton; class CustomActionDialog : public Dialog { Q_OBJECT public: CustomActionDialog(QWidget *p); bool create(); bool edit(const QString &name, const QString &cmd); QString nameText() const { return nameEntry->text().trimmed(); } QString commandText() const { return commandEntry->text().trimmed(); } private: LineEdit *nameEntry; LineEdit *commandEntry; }; class CustomActionsSettings : public QWidget { Q_OBJECT public: CustomActionsSettings(QWidget *parent); ~CustomActionsSettings(); void load(); void save(); private Q_SLOTS: void controlButtons(); void addCommand(); void editCommand(); void delCommand(); private: QTreeWidget *tree; QPushButton *add; QPushButton *edit; QPushButton *del; CustomActionDialog *dlg; }; #endif cantata-2.2.0/gui/filesettings.cpp000066400000000000000000000031521316350454000171200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "filesettings.h" #include "settings.h" FileSettings::FileSettings(QWidget *p) : QWidget(p) { setupUi(this); #ifdef Q_OS_MAC expandingSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); #endif } void FileSettings::load() { storeCoversInMpdDir->setChecked(Settings::self()->storeCoversInMpdDir()); storeLyricsInMpdDir->setChecked(Settings::self()->storeLyricsInMpdDir()); storeBackdropsInMpdDir->setChecked(Settings::self()->storeBackdropsInMpdDir()); } void FileSettings::save() { Settings::self()->saveStoreCoversInMpdDir(storeCoversInMpdDir->isChecked()); Settings::self()->saveStoreLyricsInMpdDir(storeLyricsInMpdDir->isChecked()); Settings::self()->saveStoreBackdropsInMpdDir(storeBackdropsInMpdDir->isChecked()); } cantata-2.2.0/gui/filesettings.h000066400000000000000000000021241316350454000165630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FILESETTINGS_H #define FILESETTINGS_H #include "ui_filesettings.h" class FileSettings : public QWidget, private Ui::FileSettings { public: FileSettings(QWidget *p); virtual ~FileSettings() { } void load(); void save(); }; #endif cantata-2.2.0/gui/filesettings.ui000066400000000000000000000052311316350454000167530ustar00rootroot00000000000000 FileSettings 0 0 1413 462 QFormLayout::ExpandingFieldsGrow 0 0 0 0 Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Qt::Vertical 20 0 NoteLabel QLabel
    widgets/notelabel.h
    cantata-2.2.0/gui/folderpage.cpp000066400000000000000000000202151316350454000165270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "folderpage.h" #include "mpd-interface/mpdconnection.h" #include "settings.h" #include "support/messagebox.h" #include "support/action.h" #include "support/utils.h" #include "support/monoicon.h" #include "models/mpdlibrarymodel.h" #include "widgets/menubutton.h" #include "widgets/icons.h" #include "stdactions.h" #include "customactions.h" #include #include FolderPage::FolderPage(QWidget *p) : SinglePageWidget(p) , model(this) { QColor col=Utils::monoIconColor(); browseAction = new Action(MonoIcon::icon(FontAwesome::folderopen, col), tr("Open In File Manager"), this); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(browseAction, SIGNAL(triggered()), this, SLOT(openFileManager())); connect(MPDConnection::self(), SIGNAL(updatingFileList()), view, SLOT(updating())); connect(MPDConnection::self(), SIGNAL(updatedFileList()), view, SLOT(updated())); connect(MPDConnection::self(), SIGNAL(updatingDatabase()), view, SLOT(updating())); connect(MPDConnection::self(), SIGNAL(updatedDatabase()), view, SLOT(updated())); Configuration config(metaObject()->className()); view->setMode(ItemView::Mode_DetailedTree); view->load(config); MenuButton *menu=new MenuButton(this); menu->addActions(createViewActions(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu); view->addAction(StdActions::self()->addToStoredPlaylistAction); view->addAction(CustomActions::self()); #ifdef TAGLIB_FOUND #ifdef ENABLE_DEVICES_SUPPORT view->addAction(StdActions::self()->copyToDeviceAction); #endif view->addAction(StdActions::self()->organiseFilesAction); view->addAction(StdActions::self()->editTagsAction); #ifdef ENABLE_REPLAYGAIN_SUPPORT view->addAction(StdActions::self()->replaygainAction); #endif // TAGLIB_FOUND #endif view->addAction(browseAction); #ifdef ENABLE_DEVICES_SUPPORT view->addSeparator(); view->addAction(StdActions::self()->deleteSongsAction); #endif view->setModel(&model); view->closeSearch(); connect(view, SIGNAL(updateToPlayQueue(QModelIndex,bool)), this, SLOT(updateToPlayQueue(QModelIndex,bool))); } FolderPage::~FolderPage() { Configuration config(metaObject()->className()); view->save(config); } void FolderPage::showEvent(QShowEvent *e) { view->focusView(); SinglePageWidget::showEvent(e); model.load(); } void FolderPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool enable=selected.count()>0; bool trackSelected=false; bool folderSelected=false; foreach (const QModelIndex &idx, selected) { if (static_cast(idx.internalPointer())->isFolder()) { folderSelected=true; } else { trackSelected=true; } if (folderSelected && trackSelected) { enable=false; break; } } StdActions::self()->enableAddToPlayQueue(enable); StdActions::self()->addToStoredPlaylistAction->setEnabled(enable); #ifdef TAGLIB_FOUND StdActions::self()->organiseFilesAction->setEnabled(enable && trackSelected && MPDConnection::self()->getDetails().dirReadable); StdActions::self()->editTagsAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #ifdef ENABLE_REPLAYGAIN_SUPPORT StdActions::self()->replaygainAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #endif #ifdef ENABLE_DEVICES_SUPPORT StdActions::self()->deleteSongsAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); StdActions::self()->copyToDeviceAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #endif #endif // TAGLIB_FOUND browseAction->setEnabled(enable && 1==selected.count() && folderSelected); } void FolderPage::itemDoubleClicked(const QModelIndex &) { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; //doubleclick should only have one selected item } if (!static_cast(selected.at(0).internalPointer())->isFolder()) { addSelectionToPlaylist(); } } void FolderPage::openFileManager() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } BrowseModel::Item *item = static_cast(selected.at(0).internalPointer()); if (item->isFolder()) { QDesktopServices::openUrl(QUrl::fromLocalFile(MPDConnection::self()->getDetails().dir+static_cast(item)->getPath())); } } void FolderPage::updateToPlayQueue(const QModelIndex &idx, bool replace) { BrowseModel::Item *item = static_cast(idx.internalPointer()); if (item->isFolder()) { emit add(QStringList() << MPDConnection::constDirPrefix+static_cast(item)->getPath(), replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0, false); } } QList FolderPage::selectedSongs(bool allowPlaylists) const { return model.songs(view->selectedIndexes(), allowPlaylists); } QStringList FolderPage::selectedFiles(bool allowPlaylists) const { QList songs=selectedSongs(allowPlaylists); QStringList files; foreach (const Song &s, songs) { files.append(s.file); } return files; } void FolderPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { QModelIndexList selected=view->selectedIndexes(); QStringList dirs; QStringList files; foreach (const QModelIndex &idx, selected) { if (static_cast(idx.internalPointer())->isFolder()) { files+=static_cast(idx.internalPointer())->allEntries(false); } else { files.append(static_cast(idx.internalPointer())->getSong().file); } } if (!files.isEmpty()) { if (name.isEmpty()) { emit add(files, action, priorty, decreasePriority); } else { emit addSongsToPlaylist(name, files); } view->clearSelection(); } } #ifdef ENABLE_DEVICES_SUPPORT void FolderPage::addSelectionToDevice(const QString &udi) { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit addToDevice(QString(), udi, songs); view->clearSelection(); } } void FolderPage::deleteSongs() { QList songs=selectedSongs(); if (!songs.isEmpty()) { if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Are you sure you wish to delete the selected songs?\n\nThis cannot be undone."), tr("Delete Songs"), StdGuiItem::del(), StdGuiItem::cancel())) { emit deleteSongs(QString(), songs); } view->clearSelection(); } } #endif cantata-2.2.0/gui/folderpage.h000066400000000000000000000042141316350454000161750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FOLDERPAGE_H #define FOLDERPAGE_H #include "widgets/singlepagewidget.h" #include "models/browsemodel.h" class Action; class FolderPage : public SinglePageWidget { Q_OBJECT public: FolderPage(QWidget *p); virtual ~FolderPage(); void setEnabled(bool e) { model.setEnabled(e); } bool isEnabled() const { return model.isEnabled(); } void load() { model.load(); } void clear() { model.clear(); } QStringList selectedFiles(bool allowPlaylists=false) const; QList selectedSongs(bool allowPlaylists=false) const; #ifdef ENABLE_DEVICES_SUPPORT void addSelectionToDevice(const QString &udi); void deleteSongs(); #endif void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); void showEvent(QShowEvent *e); Q_SIGNALS: void addToDevice(const QString &from, const QString &to, const QList &songs); void deleteSongs(const QString &from, const QList &songs); public Q_SLOTS: void itemDoubleClicked(const QModelIndex &); void openFileManager(); private Q_SLOTS: void updateToPlayQueue(const QModelIndex &idx, bool replace); private: void doSearch() { } void controlActions(); private: Action *browseAction; BrowseModel model; }; #endif cantata-2.2.0/gui/initialsettingswizard.cpp000066400000000000000000000175401316350454000210610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "initialsettingswizard.h" #include "support/messagebox.h" #include "settings.h" #include "support/utils.h" #include "support/icon.h" #include "widgets/icons.h" #include "models/mpdlibrarymodel.h" #ifdef ENABLE_SIMPLE_MPD_SUPPORT #include "mpd-interface/mpduser.h" #endif #include #include #include enum Pages { PAGE_INTRO, PAGE_CONNECTION, PAGE_FILES, PAGE_END }; InitialSettingsWizard::InitialSettingsWizard(QWidget *p) : QWizard(p) { setupUi(this); connect(this, SIGNAL(currentIdChanged(int)), SLOT(pageChanged(int))); connect(this, SIGNAL(setDetails(MPDConnectionDetails)), MPDConnection::self(), SLOT(setDetails(MPDConnectionDetails))); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionStateChanged(bool))); connect(MPDConnection::self(), SIGNAL(error(QString, bool)), SLOT(showError(QString))); connect(MpdLibraryModel::self(), SIGNAL(error(QString)), SLOT(dbError(QString))); connect(connectButton, SIGNAL(clicked(bool)), SLOT(connectToMpd())); connect(basicDir, SIGNAL(textChanged(QString)), SLOT(controlNextButton())); MPDConnection::self()->start(); statusLabel->setText(tr("Not Connected")); MPDConnectionDetails det=Settings::self()->connectionDetails(); host->setText(det.hostname); port->setValue(det.port); password->setText(det.password); dir->setText(det.dir); #if defined Q_OS_WIN || defined Q_OS_MAC bool showGroupWarning=false; #else bool showGroupWarning=0==Utils::getGroupId(); #endif groupWarningLabel->setVisible(showGroupWarning); groupWarningIcon->setVisible(showGroupWarning); int iconSize=Icon::dlgIconSize(); groupWarningIcon->setPixmap(Icon("dialog-warning").pixmap(iconSize, iconSize)); storeCoversInMpdDir->setChecked(Settings::self()->storeCoversInMpdDir()); storeLyricsInMpdDir->setChecked(Settings::self()->storeLyricsInMpdDir()); storeBackdropsInMpdDir->setChecked(Settings::self()->storeBackdropsInMpdDir()); introPage->setBackground(Icons::self()->appIcon); connectionPage->setBackground(Icons::self()->audioFileIcon); filesPage->setBackground(Icons::self()->filesIcon); finishedPage->setBackground(Icon("dialog-ok")); #ifdef ENABLE_SIMPLE_MPD_SUPPORT introStack->setCurrentIndex(MPDUser::self()->isSupported() ? 1 : 0); basic->setChecked(false); advanced->setChecked(true); #else introStack->setCurrentIndex(0); basic->setChecked(false); advanced->setChecked(true); #endif #ifndef Q_OS_WIN QSize sz=size(); // Adjust size for high-DPI setups... bool highDpi=fontMetrics().height()>20; if (highDpi) { foreach (int id, pageIds()) { QWizardPage *p=QWizard::page(id); p->adjustSize(); QSize ps=p->size(); if (ps.width()>sz.width()) { sz.setWidth(ps.width()); } if (ps.height()>sz.height()) { sz.setHeight(ps.height()); } } } if (sz.height()>(sz.width()*(highDpi ? 1.125 : 1.2))) { sz+=QSize(sz.height()*(highDpi ? 0.4 : 0.25), -(sz.height()*(highDpi ? 0.1 : 0.25))); } resize(sz); setMinimumSize(sz); #endif httpNote->setOn(true); } InitialSettingsWizard::~InitialSettingsWizard() { } MPDConnectionDetails InitialSettingsWizard::getDetails() { #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (basic->isChecked()) { MPDUser::self()->setMusicFolder(basicDir->text().trimmed()); return MPDUser::self()->details(true); } #endif MPDConnectionDetails det; det.hostname=host->text().trimmed(); det.port=port->value(); det.password=password->text(); det.dir=dir->text().trimmed(); det.setDirReadable(); return det; } void InitialSettingsWizard::connectToMpd() { emit setDetails(getDetails()); } void InitialSettingsWizard::mpdConnectionStateChanged(bool c) { statusLabel->setText(c ? tr("Connection Established") : tr("Connection Failed")); if (PAGE_CONNECTION==currentId()) { controlNextButton(); } } void InitialSettingsWizard::showError(const QString &message) { MessageBox::error(this, message); } void InitialSettingsWizard::dbError(const QString &message) { MessageBox::error(this, message+QLatin1String("

    ")+tr("Cantata will now terminate")); reject(); } void InitialSettingsWizard::pageChanged(int p) { if (PAGE_CONNECTION==p) { connectionStack->setCurrentIndex(basic->isChecked() ? 1 : 0); if (basic->isChecked() && basicDir->text().isEmpty()) { QString dir=QStandardPaths::writableLocation(QStandardPaths::MusicLocation); if (dir.isEmpty()) { QString dir=QDir::homePath()+"/Music"; dir=dir.replace("//", "/"); } basicDir->setText(dir); } controlNextButton(); return; } if (PAGE_FILES==p) { if (dir->text().trimmed().startsWith(QLatin1String("http:/"))) { storeCoversInMpdDir->setChecked(false); storeLyricsInMpdDir->setChecked(false); storeBackdropsInMpdDir->setChecked(false); httpNote->setVisible(true); } else { storeCoversInMpdDir->setChecked(Settings::self()->storeCoversInMpdDir()); storeLyricsInMpdDir->setChecked(Settings::self()->storeLyricsInMpdDir()); storeBackdropsInMpdDir->setChecked(Settings::self()->storeBackdropsInMpdDir()); httpNote->setVisible(false); } } button(NextButton)->setEnabled(PAGE_END!=p); } void InitialSettingsWizard::controlNextButton() { bool isOk=false; if (basic->isChecked()) { isOk=!basicDir->text().isEmpty(); if (isOk) { QDir d(basicDir->text()); isOk=d.exists() && d.isReadable(); } } else { isOk=MPDConnection::self()->isConnected(); if (isOk) { MPDConnectionDetails det=getDetails(); MPDConnectionDetails mpdDet=MPDConnection::self()->getDetails(); isOk=det.hostname==mpdDet.hostname && (det.isLocal() || det.port==mpdDet.port); } } button(NextButton)->setEnabled(isOk); } void InitialSettingsWizard::accept() { Settings::self()->saveConnectionDetails(getDetails()); Settings::self()->saveStoreCoversInMpdDir(storeCoversInMpdDir->isChecked()); Settings::self()->saveStoreLyricsInMpdDir(storeLyricsInMpdDir->isChecked()); #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (basic->isChecked()) { Settings::self()->saveCurrentConnection(MPDUser::constName); Settings::self()->saveStopOnExit(true); emit setDetails(MPDUser::self()->details()); } else { MPDUser::self()->cleanup(); } #endif Settings::self()->save(); QDialog::accept(); } void InitialSettingsWizard::reject() { // Clear version number - so that wizard is shown next time Cantata is started. Settings::self()->clearVersion(); ThreadCleaner::self()->stopAll(); QTimer::singleShot(0, qApp, SLOT(quit())); QDialog::reject(); } cantata-2.2.0/gui/initialsettingswizard.h000066400000000000000000000032651316350454000205250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INITIALSETTINGSWIZARD_H #define INITIALSETTINGSWIZARD_H #include "ui_initialsettingswizard.h" #include "mpd-interface/mpdconnection.h" #include "config.h" #include class InitialSettingsWizard : public QWizard, public Ui::InitialSettingsWizard { Q_OBJECT public: InitialSettingsWizard(QWidget *p=0); virtual ~InitialSettingsWizard(); MPDConnectionDetails getDetails(); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void setDetails(const MPDConnectionDetails &det); private Q_SLOTS: void connectToMpd(); void mpdConnectionStateChanged(bool c); void showError(const QString &message); void dbError(const QString &message); void pageChanged(int p); void accept(); void reject(); void controlNextButton(); }; #endif cantata-2.2.0/gui/initialsettingswizard.ui000066400000000000000000000725741316350454000207240ustar00rootroot00000000000000 InitialSettingsWizard 0 0 668 607 Cantata First Run 0 0 0 0 0 75 true Welcome to Cantata Qt::Vertical QSizePolicy::Fixed 20 16 <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::NoTextInteraction Qt::Vertical 20 319 75 true <html><head/><body><p>Welcome to Cantata</p></body></html> Qt::NoTextInteraction Qt::Vertical QSizePolicy::Fixed 20 16 <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::NoTextInteraction true Standard multi-user/server setup 0 0 <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true advanced Qt::Vertical QSizePolicy::Fixed 20 16 Basic single user setup 0 0 <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true basic If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Qt::Vertical QSizePolicy::Fixed 20 16 For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::NoTextInteraction Qt::Vertical 20 39 0 0 0 0 0 75 true Connection details Qt::Vertical QSizePolicy::Fixed 20 16 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical QSizePolicy::Fixed 20 8 QFormLayout::ExpandingFieldsGrow Host: host 0 1 65535 6600 Password: password QLineEdit::Password Music folder: dir 75 true true Qt::Horizontal 165 20 Connect The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Qt::Vertical 20 158 75 true Music folder Qt::Vertical QSizePolicy::Fixed 20 16 Please choose the folder containing your music collection. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical QSizePolicy::Fixed 20 13 0 0 Music folder: 75 true true Qt::Vertical 20 104 75 true Covers and Lyrics Qt::Vertical QSizePolicy::Fixed 20 16 <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::NoTextInteraction Qt::Vertical QSizePolicy::Fixed 20 8 Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Qt::Vertical 20 8 75 true Finished! Qt::Vertical QSizePolicy::Fixed 20 16 Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::NoTextInteraction Qt::Vertical QSizePolicy::Fixed 20 32 Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Qt::Vertical QSizePolicy::Fixed 20 32 0 0 <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. true Qt::NoTextInteraction Qt::Vertical 20 8 BuddyLabel QLabel
    support/buddylabel.h
    LineEdit QLineEdit
    support/lineedit.h
    WizardPage QWizardPage
    widgets/wizardpage.h
    1
    PathRequester QLineEdit
    support/pathrequester.h
    1
    NoteLabel QLabel
    widgets/notelabel.h
    cantata-2.2.0/gui/interfacesettings.cpp000066400000000000000000000523471316350454000201530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "interfacesettings.h" #include "settings.h" #include "models/sqllibrarymodel.h" #include "support/utils.h" #include "support/fancytabwidget.h" #include "support/pathrequester.h" #include "widgets/basicitemdelegate.h" #include "widgets/playqueueview.h" #include "widgets/itemview.h" #include "db/librarydb.h" #include #include #include #include #include #ifdef QT_QTDBUS_FOUND #include #include #endif #include #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; static QString viewTypeString(ItemView::Mode mode) { switch (mode) { default: case ItemView::Mode_GroupedTree: return QObject::tr("Grouped Albums"); case ItemView::Mode_Table: return QObject::tr("Table"); } } static void addViewTypes(QComboBox *box, QList modes) { foreach (ItemView::Mode m, modes) { box->addItem(viewTypeString(m), m); } } static QString cueSupportString(MPDParseUtils::CueSupport cs) { switch (cs) { default: case MPDParseUtils::Cue_Parse: return QObject::tr("Parse in Library view, and show in Folders view"); case MPDParseUtils::Cue_ListButDontParse: return QObject::tr("Only show in Folders view"); case MPDParseUtils::Cue_Ignore: return QObject::tr("Do not list"); } } static void addCueSupportTypes(QComboBox *box) { for (int i=0; iaddItem(cueSupportString((MPDParseUtils::CueSupport)i), i); } } static void selectEntry(QComboBox *box, int v) { for (int i=1; icount(); ++i) { if (box->itemData(i).toInt()==v) { box->setCurrentIndex(i); return; } } } static inline int getValue(QComboBox *box) { return box->itemData(box->currentIndex()).toInt(); } static inline QString getStrValue(QComboBox *box) { return box->itemData(box->currentIndex()).toString(); } static const char * constValueProperty="value"; static const char * constSep=","; InterfaceSettings::InterfaceSettings(QWidget *p) : QWidget(p) , loaded(false) { bool mprisSettings=false; bool enableTrayItem=Utils::useSystemTray(); #ifdef Q_OS_MAC // OSX always displays an entry in the taskbar - and the tray seems to confuse things. bool enableNotifications=QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8; #else #ifdef QT_QTDBUS_FOUND // We have dbus, check that org.freedesktop.Notifications exists bool enableNotifications=QDBusConnection::sessionBus().interface()->isServiceRegistered("org.freedesktop.Notifications"); mprisSettings=true; #else // QT_QTDBUS_FOUND bool enableNotifications=true; #endif // QT_QTDBUS_FOUND #endif // Q_MAC_OS setupUi(this); addCueSupportTypes(cueSupport); addViewTypes(playQueueView, QList() << ItemView::Mode_GroupedTree << ItemView::Mode_Table); addView(tr("Play Queue"), QLatin1String("PlayQueuePage")); addView(tr("Library"), QLatin1String("LibraryPage")); addView(tr("Folders"), QLatin1String("FolderPage")); addView(tr("Playlists"), QLatin1String("PlaylistsPage")); addView(tr("Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts"), QLatin1String("OnlineServicesPage")); #ifdef ENABLE_DEVICES_SUPPORT addView(tr("Devices - UMS, MTP (e.g. Android), and AudioCDs"), QLatin1String("DevicesPage")); #else REMOVE(showDeleteAction) #endif addView(tr("Search (via MPD)"), QLatin1String("SearchPage")); addView(tr("Info - Current song information (artist, album, and lyrics)"), QLatin1String("ContextPage")); connect(playQueueView, SIGNAL(currentIndexChanged(int)), SLOT(playQueueViewChanged())); connect(forceSingleClick, SIGNAL(toggled(bool)), SLOT(forceSingleClickChanged())); connect(views, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(viewItemChanged(QListWidgetItem*))); sbStyle->addItem(tr("Large"), FancyTabWidget::Large); sbStyle->addItem(tr("Small"), FancyTabWidget::Small); sbStyle->addItem(tr("Tab-bar"), FancyTabWidget::Tab); sbPosition->addItem(Qt::LeftToRight==layoutDirection() ? tr("Left") : tr("Right"), FancyTabWidget::Side); sbPosition->addItem(tr("Top"), FancyTabWidget::Top); sbPosition->addItem(tr("Bottom"), FancyTabWidget::Bot); connect(sbAutoHide, SIGNAL(toggled(bool)), SLOT(sbAutoHideChanged())); views->setItemDelegate(new BasicItemDelegate(views)); playQueueBackground_none->setProperty(constValueProperty, PlayQueueView::BI_None); playQueueBackground_cover->setProperty(constValueProperty, PlayQueueView::BI_Cover); playQueueBackground_custom->setProperty(constValueProperty, PlayQueueView::BI_Custom); playQueueBackgroundFile->setDirMode(false); playQueueBackgroundFile->setFilter(tr("Images (*.png *.jpg)")); int labelWidth=qMax(fontMetrics().width(QLatin1String("100%")), fontMetrics().width(tr("10px", "pixels"))); playQueueBackgroundOpacityLabel->setFixedWidth(labelWidth); playQueueBackgroundBlurLabel->setFixedWidth(labelWidth); connect(playQueueBackgroundOpacity, SIGNAL(valueChanged(int)), SLOT(setPlayQueueBackgroundOpacityLabel())); connect(playQueueBackgroundBlur, SIGNAL(valueChanged(int)), SLOT(setPlayQueueBackgroundBlurLabel())); connect(playQueueBackground_none, SIGNAL(toggled(bool)), SLOT(enablePlayQueueBackgroundOptions())); connect(playQueueBackground_cover, SIGNAL(toggled(bool)), SLOT(enablePlayQueueBackgroundOptions())); connect(playQueueBackground_custom, SIGNAL(toggled(bool)), SLOT(enablePlayQueueBackgroundOptions())); if (!enableNotifications) { REMOVE(systemTrayPopup) } if (enableTrayItem) { connect(systemTrayCheckBox, SIGNAL(toggled(bool)), minimiseOnClose, SLOT(setEnabled(bool))); connect(systemTrayCheckBox, SIGNAL(toggled(bool)), SLOT(enableStartupState())); connect(minimiseOnClose, SIGNAL(toggled(bool)), SLOT(enableStartupState())); } else { REMOVE(systemTrayCheckBox) REMOVE(minimiseOnClose) REMOVE(startupState) } if (!enableNotifications && !enableTrayItem && !mprisSettings) { tabWidget->removeTab(3); } else if (!enableTrayItem && enableNotifications && !mprisSettings) { tabWidget->setTabText(3, tr("Notifications")); } if (!mprisSettings) { REMOVE(enableMpris) } #if defined Q_OS_WIN || defined Q_OS_MAC || !defined QT_QTDBUS_FOUND if (systemTrayPopup && systemTrayCheckBox) { connect(systemTrayCheckBox, SIGNAL(toggled(bool)), SLOT(systemTrayCheckBoxToggled())); connect(systemTrayPopup, SIGNAL(toggled(bool)), SLOT(systemTrayPopupToggled())); } #endif } void InterfaceSettings::load() { ignorePrefixes->setText(QStringList(Settings::self()->ignorePrefixes().toList()).join(QString(constSep))); composerGenres->setText(QStringList(Settings::self()->composerGenres().toList()).join(QString(constSep))); singleTracksFolders->setText(QStringList(Settings::self()->singleTracksFolders().toList()).join(QString(constSep))); selectEntry(cueSupport, Settings::self()->cueSupport()); #ifdef ENABLE_DEVICES_SUPPORT showDeleteAction->setChecked(Settings::self()->showDeleteAction()); #endif selectEntry(playQueueView, Settings::self()->playQueueView()); playQueueAutoExpand->setChecked(Settings::self()->playQueueAutoExpand()); playQueueStartClosed->setChecked(Settings::self()->playQueueStartClosed()); playQueueScroll->setChecked(Settings::self()->playQueueScroll()); int pqBgnd=Settings::self()->playQueueBackground(); playQueueBackground_none->setChecked(pqBgnd==playQueueBackground_none->property(constValueProperty).toInt()); playQueueBackground_cover->setChecked(pqBgnd==playQueueBackground_cover->property(constValueProperty).toInt()); playQueueBackground_custom->setChecked(pqBgnd==playQueueBackground_custom->property(constValueProperty).toInt()); playQueueBackgroundOpacity->setValue(Settings::self()->playQueueBackgroundOpacity()); playQueueBackgroundBlur->setValue(Settings::self()->playQueueBackgroundBlur()); playQueueBackgroundFile->setText(Utils::convertPathForDisplay(Settings::self()->playQueueBackgroundFile(), false)); playQueueConfirmClear->setChecked(Settings::self()->playQueueConfirmClear()); playQueueSearch->setChecked(Settings::self()->playQueueSearch()); playQueueViewChanged(); forceSingleClick->setChecked(Settings::self()->forceSingleClick()); infoTooltips->setChecked(Settings::self()->infoTooltips()); showStopButton->setChecked(Settings::self()->showStopButton()); showCoverWidget->setChecked(Settings::self()->showCoverWidget()); showRatingWidget->setChecked(Settings::self()->showRatingWidget()); if (systemTrayCheckBox) { systemTrayCheckBox->setChecked(Settings::self()->useSystemTray()); if (minimiseOnClose) { minimiseOnClose->setChecked(Settings::self()->minimiseOnClose()); minimiseOnClose->setEnabled(systemTrayCheckBox->isChecked()); } if (startupState) { switch (Settings::self()->startupState()) { case Settings::SS_ShowMainWindow: startupStateShow->setChecked(true); break; case Settings::SS_HideMainWindow: startupStateHide->setChecked(true); break; case Settings::SS_Previous: startupStateRestore->setChecked(true); break; } enableStartupState(); } } if (systemTrayPopup) { systemTrayPopup->setChecked(Settings::self()->showPopups()); } fetchCovers->setChecked(Settings::self()->fetchCovers()); QStringList hiddenPages=Settings::self()->hiddenPages(); for (int i=0; icount(); ++i) { QListWidgetItem *v=views->item(i); v->setCheckState(hiddenPages.contains(v->data(Qt::UserRole).toString()) ? Qt::Unchecked : Qt::Checked); } int sidebar=Settings::self()->sidebar(); selectEntry(sbStyle, sidebar&FancyTabWidget::Style_Mask); selectEntry(sbPosition, sidebar&FancyTabWidget::Position_Mask); sbIconsOnly->setChecked(sidebar&FancyTabWidget::IconOnly); sbAutoHide->setChecked(Settings::self()->splitterAutoHide()); sbAutoHideChanged(); viewItemChanged(views->item(0)); setPlayQueueBackgroundOpacityLabel(); setPlayQueueBackgroundBlurLabel(); enablePlayQueueBackgroundOptions(); if (enableMpris) { enableMpris->setChecked(Settings::self()->mpris()); } } static QSet toSet(const QString &str) { QStringList parts=str.split(constSep, QString::SkipEmptyParts); QSet set; foreach (QString s, parts) { set.insert(s.trimmed()); } return set; } void InterfaceSettings::save() { Settings::self()->saveIgnorePrefixes(toSet(ignorePrefixes->text())); Settings::self()->saveComposerGenres(toSet(composerGenres->text())); Settings::self()->saveSingleTracksFolders(toSet(singleTracksFolders->text())); Settings::self()->saveCueSupport((MPDParseUtils::CueSupport)(cueSupport->itemData(cueSupport->currentIndex()).toInt())); #ifdef ENABLE_DEVICES_SUPPORT Settings::self()->saveShowDeleteAction(showDeleteAction->isChecked()); #endif Settings::self()->savePlayQueueView(getValue(playQueueView)); Settings::self()->savePlayQueueAutoExpand(playQueueAutoExpand->isChecked()); Settings::self()->savePlayQueueStartClosed(playQueueStartClosed->isChecked()); Settings::self()->savePlayQueueScroll(playQueueScroll->isChecked()); if (playQueueBackground_none->isChecked()) { Settings::self()->savePlayQueueBackground(playQueueBackground_none->property(constValueProperty).toInt()); } else if (playQueueBackground_cover->isChecked()) { Settings::self()->savePlayQueueBackground(playQueueBackground_cover->property(constValueProperty).toInt()); } else if (playQueueBackground_custom->isChecked()) { Settings::self()->savePlayQueueBackground(playQueueBackground_custom->property(constValueProperty).toInt()); } Settings::self()->savePlayQueueBackgroundOpacity(playQueueBackgroundOpacity->value()); Settings::self()->savePlayQueueBackgroundBlur(playQueueBackgroundBlur->value()); Settings::self()->savePlayQueueBackgroundFile(Utils::convertPathFromDisplay(playQueueBackgroundFile->text(), false)); Settings::self()->savePlayQueueConfirmClear(playQueueConfirmClear->isChecked()); Settings::self()->savePlayQueueSearch(playQueueSearch->isChecked()); Settings::self()->saveForceSingleClick(forceSingleClick->isChecked()); Settings::self()->saveInfoTooltips(infoTooltips->isChecked()); Settings::self()->saveShowStopButton(showStopButton->isChecked()); Settings::self()->saveShowCoverWidget(showCoverWidget->isChecked()); Settings::self()->saveShowRatingWidget(showRatingWidget->isChecked()); Settings::self()->saveUseSystemTray(systemTrayCheckBox && systemTrayCheckBox->isChecked()); Settings::self()->saveShowPopups(systemTrayPopup && systemTrayPopup->isChecked()); Settings::self()->saveMinimiseOnClose(minimiseOnClose && minimiseOnClose->isChecked()); if (!startupState || startupStateShow->isChecked()) { Settings::self()->saveStartupState(Settings::SS_ShowMainWindow); } else if (startupStateHide->isChecked()) { Settings::self()->saveStartupState(Settings::SS_HideMainWindow); } else if (startupStateRestore->isChecked()) { Settings::self()->saveStartupState(Settings::SS_Previous); } Settings::self()->saveFetchCovers(fetchCovers->isChecked()); if (loaded && lang) { Settings::self()->saveLang(lang->itemData(lang->currentIndex()).toString()); } if (loaded && styleOption) { Settings::self()->saveStyle(0==styleOption->currentIndex() ? QString() : styleOption->currentText()); } QStringList hiddenPages; for (int i=0; icount(); ++i) { QListWidgetItem *v=views->item(i); if (Qt::Unchecked==v->checkState()) { hiddenPages.append(v->data(Qt::UserRole).toString()); } } Settings::self()->saveHiddenPages(hiddenPages); int sidebar=getValue(sbStyle)|getValue(sbPosition); if (sbIconsOnly->isChecked()) { sidebar|=FancyTabWidget::IconOnly; } Settings::self()->saveSidebar(sidebar); Settings::self()->saveSplitterAutoHide(sbAutoHide->isChecked()); if (enableMpris) { Settings::self()->saveMpris(enableMpris->isChecked()); } } static bool localeAwareCompare(const QString &a, const QString &b) { return a.localeAwareCompare(b) < 0; } static QSet translationCodes(const QString &dir) { QSet codes; QDir d(dir); QStringList installed(d.entryList(QStringList() << "*.qm")); QRegExp langRegExp("^cantata_(.*).qm$"); foreach (const QString &filename, installed) { if (langRegExp.exactMatch(filename)) { codes.insert(langRegExp.cap(1)); } } return codes; } void InterfaceSettings::showEvent(QShowEvent *e) { if (!loaded) { loaded=true; QMap langMap; QSet transCodes; transCodes+=translationCodes(qApp->applicationDirPath()+QLatin1String("/translations")); transCodes+=translationCodes(QDir::currentPath()+QLatin1String("/translations")); #ifndef Q_OS_WIN transCodes+=translationCodes(CANTATA_SYS_TRANS_DIR); #endif foreach (const QString &code, transCodes) { QString langName = QLocale::languageToString(QLocale(code).language()); QString nativeName = QLocale(code).nativeLanguageName(); if (!nativeName.isEmpty()) { langName = nativeName; } langMap[QString("%1 (%2)").arg(langName, code)] = code; } langMap[tr("English (en)")] = "en"; QString current = Settings::self()->lang(); QStringList names = langMap.keys(); qStableSort(names.begin(), names.end(), localeAwareCompare); lang->addItem(tr("System default"), QString()); lang->setCurrentIndex(0); foreach (const QString &name, names) { lang->addItem(name, langMap[name]); if (langMap[name]==current) { lang->setCurrentIndex(lang->count()-1); } } if (lang->count()<3) { REMOVE(lang) REMOVE(langLabel) REMOVE(langNoteLabel) } else { connect(lang, SIGNAL(currentIndexChanged(int)), SLOT(langChanged())); } styleOption->addItem(tr("System default")); styleOption->addItems(QStyleFactory::keys()); if (styleOption->count()<3) { REMOVE(styleOption) REMOVE(styleLabel) REMOVE(styleNoteLabel) } else { QString selected = Settings::self()->style(); styleOption->setCurrentIndex(0); if (!selected.isEmpty()) { for (int i=1; icount(); ++i) { if (styleOption->itemText(i) == selected) { styleOption->setCurrentIndex(i); break; } } } connect(styleOption, SIGNAL(currentIndexChanged(int)), SLOT(styleChanged())); } } QWidget::showEvent(e); } void InterfaceSettings::showPage(const QString &page) { if (QLatin1String("sidebar")==page) { tabWidget->setCurrentIndex(0); } } QSize InterfaceSettings::sizeHint() const { QSize sz=QWidget::sizeHint(); #ifdef Q_OS_MAC sz.setWidth(sz.width()+32); sz.setHeight(qMin(sz.height(), 500)); #endif return sz; } void InterfaceSettings::addView(const QString &v, const QString &prop) { QListWidgetItem *item=new QListWidgetItem(v, views); item->setCheckState(Qt::Unchecked); item->setData(Qt::UserRole, prop); } void InterfaceSettings::playQueueViewChanged() { bool grouped=ItemView::Mode_GroupedTree==getValue(playQueueView); playQueueAutoExpand->setEnabled(grouped); playQueueStartClosed->setEnabled(grouped); } void InterfaceSettings::forceSingleClickChanged() { singleClickLabel->setOn(forceSingleClick->isChecked()!=Settings::self()->forceSingleClick()); } void InterfaceSettings::enableStartupState() { if (systemTrayCheckBox && minimiseOnClose && startupState) { startupState->setEnabled(systemTrayCheckBox->isChecked() && minimiseOnClose->isChecked()); } } void InterfaceSettings::langChanged() { langNoteLabel->setOn(lang->itemData(lang->currentIndex()).toString()!=Settings::self()->lang()); } void InterfaceSettings::styleChanged() { QString st = 0==styleOption->currentIndex() ? QString() : styleOption->currentText(); styleNoteLabel->setOn(st!=Settings::self()->style()); } void InterfaceSettings::viewItemChanged(QListWidgetItem *changedItem) { // If this is the playqueue that has been toggled, then control auto-hide // i.e. can't auto-hide if playqueue is in sidebar if (Qt::Checked==changedItem->checkState() && changedItem==views->item(0)) { sbAutoHide->setChecked(false); } // Ensure we have at least 1 view checked... for (int i=0; icount(); ++i) { QListWidgetItem *v=views->item(i); if (Qt::Checked==v->checkState()) { return; } } views->item(1)->setCheckState(Qt::Checked); } void InterfaceSettings::sbAutoHideChanged() { if (sbAutoHide->isChecked()) { views->item(0)->setCheckState(Qt::Unchecked); } } void InterfaceSettings::setPlayQueueBackgroundOpacityLabel() { playQueueBackgroundOpacityLabel->setText(tr("%1%", "value%").arg(playQueueBackgroundOpacity->value())); } void InterfaceSettings::setPlayQueueBackgroundBlurLabel() { playQueueBackgroundBlurLabel->setText(tr("%1 px", "pixels").arg(playQueueBackgroundBlur->value())); } void InterfaceSettings::enablePlayQueueBackgroundOptions() { playQueueBackgroundOpacity->setEnabled(!playQueueBackground_none->isChecked()); playQueueBackgroundOpacityLabel->setEnabled(playQueueBackgroundOpacity->isEnabled()); playQueueBackgroundBlur->setEnabled(playQueueBackgroundOpacity->isEnabled()); playQueueBackgroundBlurLabel->setEnabled(playQueueBackgroundOpacity->isEnabled()); } void InterfaceSettings::systemTrayCheckBoxToggled() { if (systemTrayCheckBox && systemTrayPopup && !systemTrayCheckBox->isChecked()) { systemTrayPopup->setChecked(false); } } void InterfaceSettings::systemTrayPopupToggled() { if (systemTrayCheckBox && systemTrayPopup && systemTrayPopup->isChecked()) { systemTrayCheckBox->setChecked(true); } } cantata-2.2.0/gui/interfacesettings.h000066400000000000000000000034551316350454000176140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INTERFACESETTINGS_H #define INTERFACESETTINGS_H #include "ui_interfacesettings.h" class QStringList; class InterfaceSettings : public QWidget, private Ui::InterfaceSettings { Q_OBJECT public: InterfaceSettings(QWidget *p); virtual ~InterfaceSettings() { } void load(); void save(); void showEvent(QShowEvent *e); void showPage(const QString &page); QSize sizeHint() const; private: void addView(const QString &v, const QString &prop); private Q_SLOTS: void playQueueViewChanged(); void forceSingleClickChanged(); void enableStartupState(); void langChanged(); void styleChanged(); void viewItemChanged(QListWidgetItem *changedItem); void sbAutoHideChanged(); void setPlayQueueBackgroundOpacityLabel(); void setPlayQueueBackgroundBlurLabel(); void enablePlayQueueBackgroundOptions(); void systemTrayCheckBoxToggled(); void systemTrayPopupToggled(); private: bool loaded; }; #endif cantata-2.2.0/gui/interfacesettings.ui000066400000000000000000000621221316350454000177760ustar00rootroot00000000000000 InterfaceSettings 0 0 1719 435 0 0 0 0 0 false Sidebar Views Use the checkboxes below to configure which views will appear in the sidebar. true If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options QFormLayout::ExpandingFieldsGrow Style: sbStyle Position: sbPosition Only show icons, no text Auto-hide Play Queue QFormLayout::ExpandingFieldsGrow Style: playQueueView Initially collapse albums Automatically expand current album Scroll to current track Prompt before clearing Separate action (and shortcut) for play queue search Qt::Vertical QSizePolicy::Fixed 2 1 0 0 Background Image None Current album cover Custom image: false 0 0 Blur: 0 0 0 0 false 20 1 Qt::Horizontal QSlider::TicksBelow 1 false 10px Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Opacity: 0 0 0 0 false 100 10 Qt::Horizontal QSlider::TicksBelow 10 false 40% Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Toolbar QFormLayout::ExpandingFieldsGrow Show stop button Show cover of current track Show track rating External Enable MPRIS D-BUS interface Show popup messages when changing tracks true Show icon in notification area true Minimize to notification area when closed On Start-up Show main window Hide main window Restore previous state Qt::Vertical 2 2 Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' true Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. true Enter comma separated list of genres... Single Tracks If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' true Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. true Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. Qt::Vertical 16 264 General QFormLayout::ExpandingFieldsGrow Fetch missing covers from Last.fm Show delete action in context menus Enforce single-click activation of items Show song information tooltips Language: lang Style: styleOption Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Changing the style setting will require a re-start of Cantata. Qt::Vertical 16 16 BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    PathRequester QLineEdit
    support/pathrequester.h
    1
    LineEdit QLineEdit
    support/lineedit.h
    1
    playQueueBackground_custom toggled(bool) playQueueBackgroundFile setEnabled(bool) 89 262 199 271
    cantata-2.2.0/gui/librarypage.cpp000066400000000000000000000444211316350454000167250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "librarypage.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdstats.h" #include "covers.h" #include "settings.h" #include "stdactions.h" #include "customactions.h" #include "support/utils.h" #include "support/messagebox.h" #include "support/actioncollection.h" #include "models/mpdlibrarymodel.h" #include "widgets/menubutton.h" #include "widgets/genrecombo.h" LibraryPage::LibraryPage(QWidget *p) : SinglePageWidget(p) { genreCombo=new GenreCombo(this); connect(StdActions::self()->addRandomAlbumToPlayQueueAction, SIGNAL(triggered()), SLOT(addRandomAlbum())); connect(MPDConnection::self(), SIGNAL(updatingLibrary(time_t)), view, SLOT(updating())); connect(MPDConnection::self(), SIGNAL(updatedLibrary()), view, SLOT(updated())); connect(MPDConnection::self(), SIGNAL(updatingDatabase()), view, SLOT(updating())); connect(MPDConnection::self(), SIGNAL(updatedDatabase()), view, SLOT(updated())); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); view->setModel(MpdLibraryModel::self()); connect(MpdLibraryModel::self(), SIGNAL(modelReset()), this, SLOT(modelReset())); // Settings... Configuration config(metaObject()->className()); view->setMode(ItemView::Mode_DetailedTree); MpdLibraryModel::self()->load(config); config.beginGroup(SqlLibraryModel::groupingStr(MpdLibraryModel::self()->topLevel())); view->load(config); showArtistImagesAction=new QAction(tr("Show Artist Images"), this); showArtistImagesAction->setCheckable(true); libraryAlbumSortAction=createMenuGroup(tr("Sort Albums"), QList() << MenuItem(tr("Name"), LibraryDb::AS_AlArYr) << MenuItem(tr("Year"), LibraryDb::AS_YrAlAr), MpdLibraryModel::self()->libraryAlbumSort(), this, SLOT(libraryAlbumSortChanged())); albumAlbumSortAction=createMenuGroup(tr("Sort Albums"), QList() << MenuItem(tr("Album, Artist, Year"), LibraryDb::AS_AlArYr) << MenuItem(tr("Album, Year, Artist"), LibraryDb::AS_AlYrAr) << MenuItem(tr("Artist, Album, Year"), LibraryDb::AS_ArAlYr) << MenuItem(tr("Artist, Year, Album"), LibraryDb::AS_ArYrAl) << MenuItem(tr("Year, Album, Artist"), LibraryDb::AS_YrAlAr) << MenuItem(tr("Year, Artist, Album"), LibraryDb::AS_YrArAl) << MenuItem(tr("Modified Date"), LibraryDb::AS_Modified), MpdLibraryModel::self()->albumAlbumSort(), this, SLOT(albumAlbumSortChanged())); MenuButton *menu=new MenuButton(this); viewAction=createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List << ItemView::Mode_IconTop); menu->addAction(viewAction); menu->addAction(createMenuGroup(tr("Group By"), QList() << MenuItem(tr("Genre"), SqlLibraryModel::T_Genre) << MenuItem(tr("Artist"), SqlLibraryModel::T_Artist) << MenuItem(tr("Album"), SqlLibraryModel::T_Album), MpdLibraryModel::self()->topLevel(), this, SLOT(groupByChanged()))); genreCombo->setVisible(SqlLibraryModel::T_Genre!=MpdLibraryModel::self()->topLevel()); menu->addAction(libraryAlbumSortAction); menu->addAction(albumAlbumSortAction); showArtistImagesAction->setChecked(MpdLibraryModel::self()->useArtistImages()); menu->addAction(showArtistImagesAction); connect(showArtistImagesAction, SIGNAL(toggled(bool)), this, SLOT(showArtistImagesChanged(bool))); albumAlbumSortAction->setVisible(SqlLibraryModel::T_Album==MpdLibraryModel::self()->topLevel()); libraryAlbumSortAction->setVisible(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel()); showArtistImagesAction->setVisible(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel()); genreCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu << genreCombo); connect(genreCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(doSearch())); view->addAction(StdActions::self()->addToStoredPlaylistAction); view->addAction(CustomActions::self()); #ifdef TAGLIB_FOUND #ifdef ENABLE_DEVICES_SUPPORT view->addAction(StdActions::self()->copyToDeviceAction); #endif view->addAction(StdActions::self()->organiseFilesAction); view->addAction(StdActions::self()->editTagsAction); #ifdef ENABLE_REPLAYGAIN_SUPPORT view->addAction(StdActions::self()->replaygainAction); #endif view->addAction(StdActions::self()->setCoverAction); #ifdef ENABLE_DEVICES_SUPPORT view->addSeparator(); view->addAction(StdActions::self()->deleteSongsAction); #endif #endif // TAGLIB_FOUND connect(view, SIGNAL(updateToPlayQueue(QModelIndex,bool)), this, SLOT(updateToPlayQueue(QModelIndex,bool))); view->setOpenAfterSearch(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel()); } LibraryPage::~LibraryPage() { Configuration config(metaObject()->className()); MpdLibraryModel::self()->save(config); config.beginGroup(SqlLibraryModel::groupingStr(MpdLibraryModel::self()->topLevel())); view->save(config); } void LibraryPage::clear() { MpdLibraryModel::self()->clear(); view->goToTop(); } static inline QString nameKey(const QString &artist, const QString &album) { return '{'+artist+"}{"+album+'}'; } QStringList LibraryPage::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } return MpdLibraryModel::self()->filenames(selected, allowPlaylists); } QList LibraryPage::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return MpdLibraryModel::self()->songs(selected, allowPlaylists); } Song LibraryPage::coverRequest() const { QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1==selected.count()) { QList songs=MpdLibraryModel::self()->songs(QModelIndexList() << selected.first(), false); if (!songs.isEmpty()) { Song s=songs.at(0); if (SqlLibraryModel::T_Artist==static_cast(selected.first().internalPointer())->getType() && !s.useComposer()) { s.setArtistImageRequest(); } return s; } } return Song(); } #ifdef ENABLE_DEVICES_SUPPORT void LibraryPage::addSelectionToDevice(const QString &udi) { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit addToDevice(QString(), udi, songs); view->clearSelection(); } } void LibraryPage::deleteSongs() { QList songs=selectedSongs(); if (!songs.isEmpty()) { if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Are you sure you wish to delete the selected songs?\n\nThis cannot be undone."), tr("Delete Songs"), StdGuiItem::del(), StdGuiItem::cancel())) { emit deleteSongs(QString(), songs); } view->clearSelection(); } } #endif void LibraryPage::showSongs(const QList &songs) { // Filter out non-mpd file songs... QList sngs; foreach (const Song &s, songs) { if (!s.file.isEmpty() && !s.file.contains(":/") && !s.file.startsWith('/')) { sngs.append(s); } } if (sngs.isEmpty()) { return; } view->clearSearchText(); bool first=true; foreach (const Song &s, sngs) { QModelIndex idx=MpdLibraryModel::self()->findSongIndex(s); if (idx.isValid()) { if (ItemView::Mode_SimpleTree==view->viewMode() || ItemView::Mode_DetailedTree==view->viewMode() || first) { view->showIndex(idx, first); } if (first) { first=false; } if (ItemView::Mode_SimpleTree!=view->viewMode() && ItemView::Mode_DetailedTree!=view->viewMode()) { return; } } } } void LibraryPage::showArtist(const QString &artist) { view->clearSearchText(); QModelIndex idx=MpdLibraryModel::self()->findArtistIndex(artist); if (idx.isValid()) { view->showIndex(idx, true); if (ItemView::Mode_SimpleTree==view->viewMode() || ItemView::Mode_DetailedTree==view->viewMode()) { view->setExpanded(idx); } } } void LibraryPage::showAlbum(const QString &artist, const QString &album) { view->clearSearchText(); QModelIndex idx=MpdLibraryModel::self()->findAlbumIndex(artist, album); if (idx.isValid()) { view->showIndex(idx, true); if (ItemView::Mode_SimpleTree==view->viewMode() || ItemView::Mode_DetailedTree==view->viewMode()) { view->setExpanded(idx.parent()); view->setExpanded(idx); } } } void LibraryPage::itemDoubleClicked(const QModelIndex &) { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; //doubleclick should only have one selected item } SqlLibraryModel::Item *item = static_cast(selected.at(0).internalPointer()); if (SqlLibraryModel::T_Track==item->getType()) { addSelectionToPlaylist(); } } void LibraryPage::setView(int v) { SinglePageWidget::setView(v); showArtistImagesAction->setVisible(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel() && ItemView::Mode_IconTop!=view->viewMode()); } void LibraryPage::modelReset() { genreCombo->update(MpdLibraryModel::self()->getGenres()); } void LibraryPage::groupByChanged() { QAction *act=qobject_cast(sender()); if (!act) { return; } int mode=act->property(constValProp).toInt(); Configuration config(metaObject()->className()); config.beginGroup(SqlLibraryModel::groupingStr(MpdLibraryModel::self()->topLevel())); view->save(config); MpdLibraryModel::self()->setTopLevel((SqlLibraryModel::Type)mode); albumAlbumSortAction->setVisible(SqlLibraryModel::T_Album==MpdLibraryModel::self()->topLevel()); libraryAlbumSortAction->setVisible(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel()); showArtistImagesAction->setVisible(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel() && ItemView::Mode_IconTop!=view->viewMode()); genreCombo->setVisible(SqlLibraryModel::T_Genre!=MpdLibraryModel::self()->topLevel()); config.endGroup(); config.beginGroup(SqlLibraryModel::groupingStr(MpdLibraryModel::self()->topLevel())); if (!config.hasEntry(ItemView::constViewModeKey)) { view->setMode(SqlLibraryModel::T_Album==mode ? ItemView::Mode_IconTop : ItemView::Mode_DetailedTree); } view->load(config); foreach (QAction *act, viewAction->menu()->actions()) { if (act->property(constValProp).toInt()==view->viewMode()) { act->setChecked(true); break; } } view->setOpenAfterSearch(SqlLibraryModel::T_Album!=MpdLibraryModel::self()->topLevel()); } void LibraryPage::libraryAlbumSortChanged() { QAction *act=qobject_cast(sender()); if (act) { MpdLibraryModel::self()->setLibraryAlbumSort((LibraryDb::AlbumSort)act->property(constValProp).toInt()); } } void LibraryPage::albumAlbumSortChanged() { QAction *act=qobject_cast(sender()); if (act) { MpdLibraryModel::self()->setAlbumAlbumSort((LibraryDb::AlbumSort)act->property(constValProp).toInt()); } } void LibraryPage::showArtistImagesChanged(bool u) { MpdLibraryModel::self()->setUseArtistImages(u); } void LibraryPage::updateToPlayQueue(const QModelIndex &idx, bool replace) { QStringList files=MpdLibraryModel::self()->filenames(QModelIndexList() << idx, true); if (!files.isEmpty()) { emit add(files, replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0, false); } } void LibraryPage::addRandomAlbum() { if (!isVisible()) { return; } QStringList genres; QStringList artists; QList albums; QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... foreach (const QModelIndex &idx, selected) { SqlLibraryModel::Item *item=static_cast(idx.internalPointer()); switch (item->getType()) { case SqlLibraryModel::T_Genre: genres.append(item->getId()); break; case SqlLibraryModel::T_Artist: artists.append(item->getId()); break; case SqlLibraryModel::T_Album: // Can only have albums selected if set to group by albums // ...controlActions ensures this! albums.append(static_cast(item)); break; default: break; } } LibraryDb::Album album; if (!albums.isEmpty()) { // We have albums selected, so choose a random one of these... SqlLibraryModel::AlbumItem *al=albums.at(Utils::random(albums.size())); album.artist=al->getArtistId(); album.id=al->getId(); } else { // If all items selected, then just choose random of all albums switch(MpdLibraryModel::self()->topLevel()) { case SqlLibraryModel::T_Genre: if (genres.size()==MpdLibraryModel::self()->rowCount(QModelIndex())) { genres=QStringList(); } break; case SqlLibraryModel::T_Artist: if (artists.size()==MpdLibraryModel::self()->rowCount(QModelIndex())) { artists=QStringList(); } break; case SqlLibraryModel::T_Album: genres=artists=QStringList(); break; default: break; } album=MpdLibraryModel::self()->getRandomAlbum(genres, artists); } if (album.artist.isEmpty() || album.id.isEmpty()) { return; } QList songs=MpdLibraryModel::self()->getAlbumTracks(album.artist, album.id); if (!songs.isEmpty()) { QStringList files; foreach (const Song &s, songs) { files.append(s.file); } emit add(files, /*replace ? MPDConnection::ReplaceAndplay : */MPDConnection::Append, 0, false); } } void LibraryPage::doSearch() { MpdLibraryModel::self()->search(view->searchText(), genreCombo->isHidden() || genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText()); } void LibraryPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool enable=selected.count()>0; StdActions::self()->enableAddToPlayQueue(enable); StdActions::self()->addToStoredPlaylistAction->setEnabled(enable); #ifdef TAGLIB_FOUND StdActions::self()->organiseFilesAction->setEnabled(enable && MPDConnection::self()->getDetails().dirReadable); StdActions::self()->editTagsAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #ifdef ENABLE_REPLAYGAIN_SUPPORT StdActions::self()->replaygainAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #endif #ifdef ENABLE_DEVICES_SUPPORT StdActions::self()->deleteSongsAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); StdActions::self()->copyToDeviceAction->setEnabled(StdActions::self()->organiseFilesAction->isEnabled()); #endif #endif // TAGLIB_FOUND if (1==selected.count()) { SqlLibraryModel::Item *item=static_cast(selected.at(0).internalPointer()); SqlLibraryModel::Type type=item->getType(); StdActions::self()->setCoverAction->setEnabled((SqlLibraryModel::T_Artist==type/* && !static_cast(item)->isComposer()*/) || SqlLibraryModel::T_Album==type); } else { StdActions::self()->setCoverAction->setEnabled(false); } bool allowRandomAlbum=isVisible() && !selected.isEmpty(); if (allowRandomAlbum) { bool groupingAlbums=SqlLibraryModel::T_Album==MpdLibraryModel::self()->topLevel(); foreach (const QModelIndex &idx, selected) { if (SqlLibraryModel::T_Track==static_cast(idx.internalPointer())->getType() || (!groupingAlbums && SqlLibraryModel::T_Album==static_cast(idx.internalPointer())->getType())) { allowRandomAlbum=false; break; } } } StdActions::self()->addRandomAlbumToPlayQueueAction->setVisible(allowRandomAlbum); } cantata-2.2.0/gui/librarypage.h000066400000000000000000000044671316350454000164000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LIBRARYPAGE_H #define LIBRARYPAGE_H #include "widgets/singlepagewidget.h" class Action; class GenreCombo; class LibraryPage : public SinglePageWidget { Q_OBJECT public: LibraryPage(QWidget *p); virtual ~LibraryPage(); void clear(); QStringList selectedFiles(bool allowPlaylists=false) const; QList selectedSongs(bool allowPlaylists=false) const; Song coverRequest() const; #ifdef ENABLE_DEVICES_SUPPORT void addSelectionToDevice(const QString &udi); void deleteSongs(); #endif void showSongs(const QList &songs); void showArtist(const QString &artist); void showAlbum(const QString &artist, const QString &album); private: void setItemSize(int v); Q_SIGNALS: void addToDevice(const QString &from, const QString &to, const QList &songs); void deleteSongs(const QString &from, const QList &songs); public Q_SLOTS: void itemDoubleClicked(const QModelIndex &); private Q_SLOTS: void modelReset(); void groupByChanged(); void libraryAlbumSortChanged(); void albumAlbumSortChanged(); void showArtistImagesChanged(bool u); void updateToPlayQueue(const QModelIndex &idx, bool replace); void addRandomAlbum(); private: void setView(int v); void doSearch(); void controlActions(); private: GenreCombo *genreCombo; QAction *viewAction; QAction *showArtistImagesAction; QAction *libraryAlbumSortAction; QAction *albumAlbumSortAction; }; #endif cantata-2.2.0/gui/main.cpp000066400000000000000000000214711316350454000153500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "application.h" #include #include #include #include #include #include #include "support/utils.h" #include "config.h" #include "settings.h" #include "initialsettingswizard.h" #include "mainwindow.h" #include "mpd-interface/song.h" #include "support/thread.h" #include "db/librarydb.h" // To enable debug... #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdparseutils.h" #include "covers.h" #include "context/wikipediaengine.h" #include "context/lastfmengine.h" #include "context/metaengine.h" #include "playlists/dynamicplaylists.h" #ifdef ENABLE_DEVICES_SUPPORT #include "models/devicesmodel.h" #endif #include "streams/streamfetcher.h" #include "http/httpserver.h" #include "widgets/songdialog.h" #include "network/networkaccessmanager.h" #include "context/ultimatelyricsprovider.h" #include "tags/taghelperiface.h" #include "context/contextwidget.h" #include "scrobbling/scrobbler.h" #include "gui/mediakeys.h" #ifdef ENABLE_HTTP_STREAM_PLAYBACK #include "mpd-interface/httpstream.h" #endif #include #include #include #include #include static QMutex msgMutex; static bool firstMsg=true; static void cantataQtMsgHandler(QtMsgType, const QMessageLogContext &, const QString &msg) { QMutexLocker locker(&msgMutex); QFile f(Utils::cacheDir(QString(), true)+"cantata.log"); if (f.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text)) { QTextStream stream(&f); if (firstMsg) { stream << "------------START------------" << endl; firstMsg=false; } stream << QDateTime::currentDateTime().toString(Qt::ISODate).replace("T", " ") << " - " << msg << endl; } } static void loadTranslation(const QString &prefix, const QString &path, const QString &overrideLanguage = QString()) { QString language = overrideLanguage.isEmpty() ? QLocale::system().name() : overrideLanguage; QTranslator *t = new QTranslator; if (t->load(prefix+"_"+language, path)) { QCoreApplication::installTranslator(t); } else { delete t; } } static void removeOldFiles(const QString &d, const QStringList &types) { if (!d.isEmpty()) { QDir dir(d); if (dir.exists()) { QFileInfoList files=dir.entryInfoList(types, QDir::Files|QDir::NoDotAndDotDot); foreach (const QFileInfo &file, files) { QFile::remove(file.absoluteFilePath()); } QString dirName=dir.dirName(); if (!dirName.isEmpty()) { dir.cdUp(); dir.rmdir(dirName); } } } } static void removeOldFiles() { // Remove Cantata 1.x XML cache files removeOldFiles(Utils::cacheDir("library"), QStringList() << "*.xml" << "*.xml.gz"); removeOldFiles(Utils::cacheDir("jamendo"), QStringList() << "*.xml.gz"); removeOldFiles(Utils::cacheDir("magnatune"), QStringList() << "*.xml.gz"); } enum Debug { Dbg_Mpd = 0x00000001, Dbg_MpdParse = 0x00000002, Dbg_Covers = 0x00000004, Dbg_Context_Wikipedia = 0x00000008, Dbg_Context_LastFm = 0x00000010, Dbg_Context_Meta = 0x00000020, Dbg_Context_Widget = 0x00000040, // Dbg_Context_Backdrop = 0x00000080, Dbg_Dynamic = 0x00000100, Dbg_StreamFetching = 0x00000200, Dbg_HttpServer = 0x00000400, Dbg_SongDialogs = 0x00000800, Dbg_NetworkAccess = 0x00001000, Dbg_Context_Lyrics = 0x00002000, Dbg_Threads = 0x00004000, Dbg_Tags = 0x00008000, Dbg_Scrobbling = 0x00010000, Dbg_Devices = 0x00020000, Dbg_Sql = 0x00040000, Dbg_HttpStream = 0x00080000, DBG_Other = 0x00100000, // NOTE: MUST UPDATE Dbg_All IF ADD NEW ITEMS!!! Dbg_All = 0x000FFFFF }; static void installDebugMessageHandler() { QString debug=qgetenv("CANTATA_DEBUG"); if (!debug.isEmpty()) { int dbg=0; if (debug.contains(QLatin1String("0x"))) { dbg=debug.startsWith(QLatin1Char('-')) ? (debug.mid(1).toInt(0, 16)*-1) : debug.toInt(0, 16); } else { dbg=debug.toInt(); } bool logToFile=dbg>0; if (dbg<0) { dbg*=-1; } if (dbg&Dbg_Mpd) { MPDConnection::enableDebug(); } if (dbg&Dbg_MpdParse) { MPDParseUtils::enableDebug(); } if (dbg&Dbg_Covers) { Covers::enableDebug(); } if (dbg&Dbg_Context_Wikipedia) { WikipediaEngine::enableDebug(); } if (dbg&Dbg_Context_LastFm) { LastFmEngine::enableDebug(); } if (dbg&Dbg_Context_Meta) { MetaEngine::enableDebug(); } if (dbg&Dbg_Context_Widget) { ContextWidget::enableDebug(); } if (dbg&Dbg_Dynamic) { DynamicPlaylists::enableDebug(); } if (dbg&Dbg_StreamFetching) { StreamFetcher::enableDebug(); } if (dbg&Dbg_HttpServer) { HttpServer::enableDebug(); } if (dbg&Dbg_SongDialogs) { SongDialog::enableDebug(); } if (dbg&Dbg_NetworkAccess) { NetworkAccessManager::enableDebug(); } if (dbg&Dbg_Context_Lyrics) { UltimateLyricsProvider::enableDebug(); } if (dbg&Dbg_Threads) { ThreadCleaner::enableDebug(); } #ifdef ENABLE_TAGLIB if (dbg&Dbg_Tags) { TagHelperIface::enableDebug(); } #endif if (dbg&Dbg_Scrobbling) { Scrobbler::enableDebug(); } #ifdef ENABLE_DEVICES_SUPPORT if (dbg&Dbg_Devices) { DevicesModel::enableDebug(); } #endif if (dbg&Dbg_Sql) { LibraryDb::enableDebug(); } #ifdef ENABLE_HTTP_STREAM_PLAYBACK if (dbg&Dbg_HttpStream) { HttpStream::enableDebug(); } #endif if (dbg&DBG_Other) { MediaKeys::enableDebug(); } if (dbg&Dbg_All && logToFile) { qInstallMessageHandler(cantataQtMsgHandler); } } } int main(int argc, char *argv[]) { QThread::currentThread()->setObjectName("GUI"); QCoreApplication::setApplicationName(PACKAGE_NAME); QCoreApplication::setOrganizationName(ORGANIZATION_NAME); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #if QT_VERSION >= 0x050600 && defined Q_OS_WIN QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif Application app(argc, argv); if (!app.start()) { return 0; } // Set the permissions on the config file on Unix - it can contain passwords // for internet services so it's important that other users can't read it. // On Windows these are stored in the registry instead. #ifdef Q_OS_UNIX QSettings s; // Create the file if it doesn't exist already if (!QFile::exists(s.fileName())) { QFile file(s.fileName()); file.open(QIODevice::WriteOnly); } // Set -rw------- QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner); #endif removeOldFiles(); installDebugMessageHandler(); // Translations QString lang=Settings::self()->lang(); #if defined Q_OS_WIN || defined Q_OS_MAC loadTranslation("qt", CANTATA_SYS_TRANS_DIR, lang); #else loadTranslation("qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath), lang); #endif loadTranslation("cantata", CANTATA_SYS_TRANS_DIR, lang); Application::init(); if (Settings::self()->firstRun()) { InitialSettingsWizard wz; if (QDialog::Rejected==wz.exec()) { return 0; } } MainWindow mw; #if defined Q_OS_WIN || defined Q_OS_MAC app.setActivationWindow(&mw); #endif // !defined Q_OS_MAC app.loadFiles(); return app.exec(); } cantata-2.2.0/gui/mainwindow.cpp000066400000000000000000003114331316350454000166000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "mainwindow.h" #include "support/thread.h" #include "trayitem.h" #include "support/messagebox.h" #include "support/inputdialog.h" #include "models/playlistsmodel.h" #include "covers.h" #include "coverdialog.h" #include "currentcover.h" #include "preferencesdialog.h" #include "mpd-interface/mpdstats.h" #include "mpd-interface/mpdparseutils.h" #include "settings.h" #include "support/utils.h" #include "models/musiclibraryitemartist.h" #include "models/musiclibraryitemalbum.h" #include "models/mpdlibrarymodel.h" #include "librarypage.h" #include "folderpage.h" #include "streams/streamdialog.h" #include "searchpage.h" #include "customactions.h" #include "support/gtkstyle.h" #include "widgets/mirrormenu.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/filejob.h" #include "devices/devicespage.h" #include "models/devicesmodel.h" #include "devices/actiondialog.h" #include "devices/syncdialog.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "devices/audiocddevice.h" #endif #endif #include "online/onlineservicespage.h" #include "http/httpserver.h" #ifdef TAGLIB_FOUND #include "tags/trackorganiser.h" #include "tags/tageditor.h" #include "tags/tags.h" #ifdef ENABLE_REPLAYGAIN_SUPPORT #include "replaygain/rgdialog.h" #endif #endif #include "models/streamsmodel.h" #include "playlists/playlistspage.h" #include "support/fancytabwidget.h" #include "support/monoicon.h" #ifdef QT_QTDBUS_FOUND #include "dbus/mpris.h" #include "cantataadaptor.h" #ifdef Q_OS_LINUX #include "dbus/powermanagement.h" #endif #endif #if !defined Q_OS_WIN && !defined Q_OS_MAC #include "devices/mountpoints.h" #endif #ifdef Q_OS_MAC #include "support/windowmanager.h" #include "support/osxstyle.h" #include "mac/dockmenu.h" #ifdef IOKIT_FOUND #include "mac/powermanagement.h" #endif #endif #include "playlists/dynamicplaylists.h" #include "support/messagewidget.h" #include "widgets/groupedview.h" #include "widgets/actionitemdelegate.h" #include "widgets/icons.h" #include "widgets/volumeslider.h" #include "support/action.h" #include "support/actioncollection.h" #include "stdactions.h" #ifdef ENABLE_HTTP_STREAM_PLAYBACK #include "mpd-interface/httpstream.h" #endif #include #include #include #include #include #ifdef Q_OS_WIN #include "windows/thumbnailtoolbar.h" #endif #include #include #include #include #include "mediakeys.h" #include static int nextKey(int &key) { int k=key; if (Qt::Key_0==key) { key=Qt::Key_A; } else if (Qt::Key_Colon==++key) { key=Qt::Key_0; } return k; } static const char *constRatingKey="rating"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , prevPage(-1) , lastState(MPDState_Inactive) , lastSongId(-1) , autoScrollPlayQueue(true) #ifdef ENABLE_HTTP_STREAM_PLAYBACK , httpStream(new HttpStream(this)) #endif , currentPage(0) #ifdef QT_QTDBUS_FOUND , mpris(0) #endif , statusTimer(0) , playQueueSearchTimer(0) #if !defined Q_OS_WIN && !defined Q_OS_MAC , mpdAccessibilityTimer(0) #endif , contextTimer(0) , contextSwitchTime(0) , connectedState(CS_Init) , stopAfterCurrent(false) #if defined Q_OS_WIN , thumbnailTooolbar(0) #endif { #if defined Q_OS_MAC || defined Q_OS_WIN init(); #else if ("qt5ct"==qgetenv("QT_QPA_PLATFORMTHEME")) { // Work-aroud Qt5Ct colour scheme issues. Qt5Ct applies its palette after Cantata's main window // is shown. Issue 944 // TODO: The real fix would be for Cantata to properly repect colour scheme changes show(); resize(0, 0); QTimer::singleShot(5, this, SLOT(init())); } else { init(); } #endif } void MainWindow::init() { #if !defined Q_OS_MAC && !defined Q_OS_WIN // Work-aroud Qt5Ct incorrectly setting icon theme QIcon::setThemeSearchPaths(QStringList() << CANTATA_SYS_ICONS_DIR << QIcon::themeSearchPaths()); QIcon::setThemeName(QLatin1String("cantata")); #endif QPoint p=pos(); ActionCollection::setMainWidget(this); trayItem=new TrayItem(this); #ifdef QT_QTDBUS_FOUND new CantataAdaptor(this); QDBusConnection::sessionBus().registerObject("/cantata", this); #endif setMinimumHeight(Utils::scaleForDpi(480)); QWidget *widget = new QWidget(this); setupUi(widget); setCentralWidget(widget); messageWidget->hide(); // Need to set these values here, as used in library/device loading... Song::setComposerGenres(Settings::self()->composerGenres()); int hSpace=Utils::layoutSpacing(this); int vSpace=fontMetrics().height()<14 ? hSpace/2 : 0; toolbarLayout->setContentsMargins(hSpace, vSpace, hSpace, vSpace); toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setWindowTitle("Cantata"); #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac(true); QToolBar *topToolBar = addToolBar("ToolBar"); WindowManager *wm=new WindowManager(topToolBar); wm->initialize(WindowManager::WM_DRAG_MENU_AND_TOOLBAR); wm->registerWidgetAndChildren(topToolBar); topToolBar->setObjectName("MainToolBar"); topToolBar->addWidget(toolbar); topToolBar->setMovable(false); topToolBar->setContextMenuPolicy(Qt::PreventContextMenu); topToolBar->ensurePolished(); toolbar=topToolBar; #elif !defined Q_OS_WIN QProxyStyle *proxy=qobject_cast(style()); QStyle *check=proxy && proxy->baseStyle() ? proxy->baseStyle() : style(); if (check->inherits("Kvantum::Style")) { QToolBar *topToolBar = addToolBar("ToolBar"); topToolBar->setObjectName("MainToolBar"); topToolBar->addWidget(toolbar); topToolBar->setMovable(false); topToolBar->setContextMenuPolicy(Qt::PreventContextMenu); topToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); topToolBar->setContentsMargins(0, 0, 0, 0); topToolBar->ensurePolished(); toolbar=topToolBar; } else { toolbar->setFixedHeight(qMax(54, (int)(fontMetrics().height()*3.5)+(toolbarLayout->spacing()*3)+(vSpace*2))); } #endif toolbar->ensurePolished(); toolbar->adjustSize(); coverWidget->setSize(toolbar->height()-(vSpace*2)); nowPlaying->initColors(); nowPlaying->adjustSize(); nowPlaying->setFixedHeight(nowPlaying->height()); volumeSlider->setColor(nowPlaying->textColor()); Icons::self()->initToolbarIcons(nowPlaying->textColor()); Icons::self()->initSidebarIcons(); QColor iconCol=Utils::monoIconColor(); setWindowIcon(Icons::self()->appIcon); prefAction=ActionCollection::get()->createAction("configure", Utils::KDE==Utils::currentDe() ? tr("Configure Cantata...") : tr("Preferences"), Icons::self()->configureIcon); connect(prefAction, SIGNAL(triggered()),this, SLOT(showPreferencesDialog())); quitAction = ActionCollection::get()->createAction("quit", tr("Quit"), MonoIcon::icon(FontAwesome::poweroff, MonoIcon::constRed, MonoIcon::constRed)); connect(quitAction, SIGNAL(triggered()), this, SLOT(quit())); quitAction->setShortcut(QKeySequence::Quit); Action *aboutAction=ActionCollection::get()->createAction("about", tr("About Cantata..."), Icons::self()->appIcon); connect(aboutAction, SIGNAL(triggered()),this, SLOT(showAboutDialog())); #ifdef Q_OS_MAC prefAction->setMenuRole(QAction::PreferencesRole); quitAction->setMenuRole(QAction::QuitRole); aboutAction->setMenuRole(QAction::AboutRole); #endif restoreAction = new Action(tr("Show Window"), this); connect(restoreAction, SIGNAL(triggered()), this, SLOT(restoreWindow())); serverInfoAction=ActionCollection::get()->createAction("mpdinfo", tr("Server information..."), MonoIcon::icon(FontAwesome::server, iconCol)); connect(serverInfoAction, SIGNAL(triggered()),this, SLOT(showServerInfo())); serverInfoAction->setEnabled(Settings::self()->firstRun()); refreshDbAction = ActionCollection::get()->createAction("refresh", tr("Refresh Database"), Icons::self()->refreshIcon); doDbRefreshAction = new Action(refreshDbAction->icon(), tr("Refresh"), this); refreshDbAction->setEnabled(false); connectAction = new Action(Icons::self()->connectIcon, tr("Connect"), this); connectionsAction = new Action(MonoIcon::icon(FontAwesome::server, iconCol), tr("Collection"), this); outputsAction = new Action(MonoIcon::icon(FontAwesome::volumeup, iconCol), tr("Outputs"), this); stopAfterTrackAction = ActionCollection::get()->createAction("stopaftertrack", tr("Stop After Track"), Icons::self()->toolbarStopIcon); QList seeks=QList() << 5 << 30 << 60; QList seekShortcuts=QList() << (int)Qt::ControlModifier << (int)Qt::ShiftModifier << (int)(Qt::ControlModifier|Qt::ShiftModifier); for (int i=0; icreateAction("seekfwd"+QString::number(seek), tr("Seek forward (%1 seconds)").arg(seek)); Action *revAction = ActionCollection::get()->createAction("seekrev"+QString::number(seek), tr("Seek backward (%1 seconds)").arg(seek)); fwdAction->setProperty("offset", seek); revAction->setProperty("offset", -1*seek); connect(fwdAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek())); connect(revAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek())); addAction(fwdAction); addAction(revAction); if (isetShortcut((Qt::RightToLeft==layoutDirection() ? Qt::Key_Left : Qt::Key_Right)+seekShortcuts.at(i)); revAction->setShortcut((Qt::RightToLeft==layoutDirection() ? Qt::Key_Right : Qt::Key_Left)+seekShortcuts.at(i)); } } addPlayQueueToStoredPlaylistAction = new Action(Icons::self()->playlistListIcon, tr("Add To Stored Playlist"), this); #ifdef ENABLE_DEVICES_SUPPORT copyToDeviceAction = new Action(StdActions::self()->copyToDeviceAction->icon(), Utils::strippedText(StdActions::self()->copyToDeviceAction->text()), this); copyToDeviceAction->setMenu(DevicesModel::self()->menu()->duplicate(0)); #endif cropPlayQueueAction = ActionCollection::get()->createAction("cropplaylist", tr("Crop Others")); addStreamToPlayQueueAction = ActionCollection::get()->createAction("addstreamtoplayqueue", tr("Add Stream URL")); QIcon clearIcon = MonoIcon::icon(FontAwesome::times, MonoIcon::constRed, MonoIcon::constRed); clearPlayQueueAction = ActionCollection::get()->createAction("clearplaylist", tr("Clear"), clearIcon); clearPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::Key_K); centerPlayQueueAction = ActionCollection::get()->createAction("centerplaylist", tr("Center On Current Track"), Icons::self()->centrePlayQueueOnTrackIcon); expandInterfaceAction = ActionCollection::get()->createAction("expandinterface", tr("Expanded Interface"), MonoIcon::icon(FontAwesome::expand, iconCol)); expandInterfaceAction->setCheckable(true); songInfoAction = ActionCollection::get()->createAction("showsonginfo", tr("Show Current Song Information"), Icons::self()->infoIcon); songInfoAction->setShortcut(Qt::Key_F12); songInfoAction->setCheckable(true); fullScreenAction = ActionCollection::get()->createAction("fullScreen", tr("Full Screen"), MonoIcon::icon(FontAwesome::arrowsalt, iconCol)); #ifndef Q_OS_MAC fullScreenAction->setShortcut(Qt::Key_F11); #endif randomPlayQueueAction = ActionCollection::get()->createAction("randomplaylist", tr("Random"), Icons::self()->shuffleIcon); repeatPlayQueueAction = ActionCollection::get()->createAction("repeatplaylist", tr("Repeat"), Icons::self()->repeatIcon); singlePlayQueueAction = ActionCollection::get()->createAction("singleplaylist", tr("Single"), Icons::self()->singleIcon, tr("When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled.")); consumePlayQueueAction = ActionCollection::get()->createAction("consumeplaylist", tr("Consume"), Icons::self()->consumeIcon, tr("When consume is activated, a song is removed from the play queue after it has been played.")); searchPlayQueueAction = ActionCollection::get()->createAction("searchplaylist", tr("Find in Play Queue"), Icons::self()->searchIcon); addAction(searchPlayQueueAction); searchPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_F); #ifdef ENABLE_HTTP_STREAM_PLAYBACK streamPlayAction = ActionCollection::get()->createAction("streamplay", tr("Play Stream"), Icons::self()->httpStreamIcon); streamPlayAction->setCheckable(true); streamPlayAction->setChecked(false); streamPlayAction->setVisible(false); streamPlayButton->setDefaultAction(streamPlayAction); #endif streamPlayButton->setVisible(false); locateTrackAction = ActionCollection::get()->createAction("locatetrack", tr("Locate In Library"), Icons::self()->searchIcon); playNextAction = ActionCollection::get()->createAction("playnext", tr("Play next")); #ifdef TAGLIB_FOUND editPlayQueueTagsAction = ActionCollection::get()->createAction("editpqtags", Utils::strippedText(StdActions::self()->editTagsAction->text()), StdActions::self()->editTagsAction->icon()); editPlayQueueTagsAction->setToolTip(tr("Edit Track Information (Play Queue)")); editPlayQueueTagsAction->setProperty(Action::constTtForSettings, true); #endif addAction(expandAllAction = ActionCollection::get()->createAction("expandall", tr("Expand All"))); expandAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Plus); addAction(collapseAllAction = ActionCollection::get()->createAction("collapseall", tr("Collapse All"))); collapseAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Minus); cancelAction = ActionCollection::get()->createAction("cancel", tr("Cancel"), Icons::self()->cancelIcon); cancelAction->setShortcut(Qt::AltModifier+Qt::Key_Escape); connect(cancelAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide())); StdActions::self()->playPauseTrackAction->setEnabled(false); StdActions::self()->nextTrackAction->setEnabled(false); updateNextTrack(-1); StdActions::self()->prevTrackAction->setEnabled(false); enableStopActions(false); volumeSlider->initActions(); connectionsAction->setMenu(new QMenu(this)); connectionsGroup=new QActionGroup(connectionsAction->menu()); outputsAction->setMenu(new QMenu(this)); outputsAction->setVisible(false); addPlayQueueToStoredPlaylistAction->setMenu(PlaylistsModel::self()->menu()->duplicate(0)); playPauseTrackButton->setDefaultAction(StdActions::self()->playPauseTrackAction); stopTrackButton->setDefaultAction(StdActions::self()->stopPlaybackAction); nextTrackButton->setDefaultAction(StdActions::self()->nextTrackAction); prevTrackButton->setDefaultAction(StdActions::self()->prevTrackAction); QMenu *stopMenu=new QMenu(this); stopMenu->addAction(StdActions::self()->stopPlaybackAction); stopMenu->addAction(StdActions::self()->stopAfterCurrentTrackAction); stopTrackButton->setMenu(stopMenu); stopTrackButton->setPopupMode(QToolButton::DelayedPopup); clearPlayQueueAction->setEnabled(false); centerPlayQueueAction->setEnabled(false); StdActions::self()->savePlayQueueAction->setEnabled(false); addStreamToPlayQueueAction->setEnabled(false); clearPlayQueueButton->setDefaultAction(clearPlayQueueAction); savePlayQueueButton->setDefaultAction(StdActions::self()->savePlayQueueAction); centerPlayQueueButton->setDefaultAction(centerPlayQueueAction); randomButton->setDefaultAction(randomPlayQueueAction); repeatButton->setDefaultAction(repeatPlayQueueAction); singleButton->setDefaultAction(singlePlayQueueAction); consumeButton->setDefaultAction(consumePlayQueueAction); QStringList hiddenPages=Settings::self()->hiddenPages(); playQueuePage=new PlayQueuePage(this); contextPage=new ContextPage(this); QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, playQueuePage); layout->setContentsMargins(0, 0, 0, 0); bool playQueueInSidebar=!hiddenPages.contains(playQueuePage->metaObject()->className()); bool contextInSidebar=!hiddenPages.contains(contextPage->metaObject()->className()); layout=new QBoxLayout(QBoxLayout::TopToBottom, contextPage); layout->setContentsMargins(0, 0, 0, 0); // Build sidebar... #define TAB_ACTION(A) A->icon(), A->text(), A->text() int sidebarPageShortcutKey=Qt::Key_1; addAction(showPlayQueueAction = ActionCollection::get()->createAction("showplayqueue", tr("Play Queue"), Icons::self()->playqueueIcon)); showPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_Q); tabWidget->addTab(playQueuePage, TAB_ACTION(showPlayQueueAction), playQueueInSidebar); connect(showPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueue())); libraryPage = new LibraryPage(this); addAction(libraryTabAction = ActionCollection::get()->createAction("showlibrarytab", tr("Library"), Icons::self()->libraryIcon)); libraryTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); tabWidget->addTab(libraryPage, TAB_ACTION(libraryTabAction), !hiddenPages.contains(libraryPage->metaObject()->className())); connect(libraryTabAction, SIGNAL(triggered()), this, SLOT(showLibraryTab())); folderPage = new FolderPage(this); addAction(foldersTabAction = ActionCollection::get()->createAction("showfolderstab", tr("Folders"), Icons::self()->foldersIcon)); foldersTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); tabWidget->addTab(folderPage, TAB_ACTION(foldersTabAction), !hiddenPages.contains(folderPage->metaObject()->className())); connect(foldersTabAction, SIGNAL(triggered()), this, SLOT(showFoldersTab())); folderPage->setEnabled(!hiddenPages.contains(folderPage->metaObject()->className())); playlistsPage = new PlaylistsPage(this); addAction(playlistsTabAction = ActionCollection::get()->createAction("showplayliststab", tr("Playlists"), Icons::self()->playlistsIcon)); playlistsTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); tabWidget->addTab(playlistsPage, TAB_ACTION(playlistsTabAction), !hiddenPages.contains(playlistsPage->metaObject()->className())); connect(playlistsTabAction, SIGNAL(triggered()), this, SLOT(showPlaylistsTab())); connect(DynamicPlaylists::self(), SIGNAL(error(const QString &)), SLOT(showError(const QString &))); connect(DynamicPlaylists::self(), SIGNAL(running(bool)), dynamicLabel, SLOT(setVisible(bool))); connect(DynamicPlaylists::self(), SIGNAL(running(bool)), this, SLOT(controlDynamicButton())); stopDynamicButton->setDefaultAction(DynamicPlaylists::self()->stopAct()); onlinePage = new OnlineServicesPage(this); addAction(onlineTabAction = ActionCollection::get()->createAction("showonlinetab", tr("Internet"), Icons::self()->onlineIcon)); onlineTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); tabWidget->addTab(onlinePage, TAB_ACTION(onlineTabAction), !hiddenPages.contains(onlinePage->metaObject()->className())); onlinePage->setEnabled(!hiddenPages.contains(onlinePage->metaObject()->className())); connect(onlineTabAction, SIGNAL(triggered()), this, SLOT(showOnlineTab())); // connect(onlinePage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(onlinePage, SIGNAL(error(const QString &)), this, SLOT(showError(const QString &))); #ifdef ENABLE_DEVICES_SUPPORT devicesPage = new DevicesPage(this); addAction(devicesTabAction = ActionCollection::get()->createAction("showdevicestab", tr("Devices"), Icons::self()->devicesIcon)); devicesTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); tabWidget->addTab(devicesPage, TAB_ACTION(devicesTabAction), !hiddenPages.contains(devicesPage->metaObject()->className())); DevicesModel::self()->setEnabled(!hiddenPages.contains(devicesPage->metaObject()->className())); connect(devicesTabAction, SIGNAL(triggered()), this, SLOT(showDevicesTab())); #endif searchPage = new SearchPage(this); addAction(searchTabAction = ActionCollection::get()->createAction("showsearchtab", tr("Search"), Icons::self()->searchTabIcon)); searchTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey)); connect(searchTabAction, SIGNAL(triggered()), this, SLOT(showSearchTab())); connect(searchPage, SIGNAL(locate(QList)), this, SLOT(locateTracks(QList))); tabWidget->addTab(searchPage, TAB_ACTION(searchTabAction), !hiddenPages.contains(searchPage->metaObject()->className())); tabWidget->addTab(contextPage, Icons::self()->infoSidebarIcon, tr("Info"), songInfoAction->text(), !hiddenPages.contains(contextPage->metaObject()->className())); tabWidget->setStyle(Settings::self()->sidebar()); if (playQueueInSidebar) { tabToggled(PAGE_PLAYQUEUE); } else { tabWidget->setCurrentIndex(PAGE_LIBRARY); } if (contextInSidebar) { tabToggled(PAGE_CONTEXT); } else { tabWidget->toggleTab(PAGE_CONTEXT, false); } expandInterfaceAction->setChecked(Settings::self()->showPlaylist()); fullScreenAction->setEnabled(expandInterfaceAction->isChecked()); if (fullScreenAction->isEnabled()) { fullScreenAction->setChecked(Settings::self()->showFullScreen()); } randomPlayQueueAction->setCheckable(true); repeatPlayQueueAction->setCheckable(true); singlePlayQueueAction->setCheckable(true); consumePlayQueueAction->setCheckable(true); songInfoButton->setDefaultAction(songInfoAction); fullScreenLabel->setVisible(false); connect(fullScreenLabel, SIGNAL(leftClickedUrl()), fullScreenAction, SIGNAL(triggered())); connect(playQueueSearchWidget, SIGNAL(active(bool)), playQueue, SLOT(searchActive(bool))); if (Configuration(playQueuePage->metaObject()->className()).get(ItemView::constSearchActiveKey, false)) { playQueueSearchWidget->activate(); } else { playQueueSearchWidget->setVisible(false); } QList playbackBtns=QList() << prevTrackButton << stopTrackButton << playPauseTrackButton << nextTrackButton; QList controlBtns=QList() << menuButton << songInfoButton; int playbackIconSizeNonScaled=24==Icons::self()->toolbarPlayIcon.actualSize(QSize(24, 24)).width() ? 24 : 28; int playbackIconSize=Utils::scaleForDpi(playbackIconSizeNonScaled); int playPauseIconSize=Utils::scaleForDpi(32); int controlIconSize=Utils::scaleForDpi(22); int controlButtonSize=Utils::scaleForDpi(32); int playbackButtonSize=28==playbackIconSizeNonScaled ? Utils::scaleForDpi(34) : controlButtonSize; foreach (QToolButton *b, controlBtns) { b->setAutoRaise(true); b->setToolButtonStyle(Qt::ToolButtonIconOnly); b->setFixedSize(QSize(controlButtonSize, controlButtonSize)); b->setIconSize(QSize(controlIconSize, controlIconSize)); } foreach (QToolButton *b, playbackBtns) { b->setAutoRaise(true); b->setToolButtonStyle(Qt::ToolButtonIconOnly); b->setFixedSize(QSize(playbackButtonSize, playbackButtonSize)); b->setIconSize(QSize(playbackIconSize, playbackIconSize)); } playPauseTrackButton->setIconSize(QSize(playPauseIconSize, playPauseIconSize)); playPauseTrackButton->setFixedSize(QSize(playPauseIconSize+6, playPauseIconSize+6)); if (fullScreenAction->isEnabled()) { fullScreenAction->setChecked(Settings::self()->showFullScreen()); } randomPlayQueueAction->setChecked(false); repeatPlayQueueAction->setChecked(false); singlePlayQueueAction->setChecked(false); consumePlayQueueAction->setChecked(false); expandedSize=Settings::self()->mainWindowSize(); collapsedSize=Settings::self()->mainWindowCollapsedSize(); if (Settings::self()->firstRun() || (expandInterfaceAction->isChecked() && expandedSize.isEmpty())) { int width=playPauseTrackButton->width()*25; resize(playPauseTrackButton->width()*25, playPauseTrackButton->height()*18); splitter->setSizes(QList() << width*0.4 << width*0.6); } else { if (expandInterfaceAction->isChecked()) { if (!expandedSize.isEmpty()) { if (expandedSize.width()>1) { resize(expandedSize); expandOrCollapse(false); } else { showMaximized(); expandedSize=size(); } } } else { if (!collapsedSize.isEmpty() && collapsedSize.width()>1) { resize(collapsedSize); expandOrCollapse(false); } } if (!playQueueInSidebar) { QByteArray state=Settings::self()->splitterState(); if (state.isEmpty()) { int width=playPauseTrackButton->width()*25; splitter->setSizes(QList() << width*0.4 << width*0.6); } else { splitter->restoreState(Settings::self()->splitterState()); } } if (fullScreenAction->isChecked()) { fullScreen(); } } #ifdef Q_OS_MAC QMenu *menu=new QMenu(tr("&Music"), this); addMenuAction(menu, refreshDbAction); menu->addSeparator(); addMenuAction(menu, connectionsAction); addMenuAction(menu, outputsAction); #ifdef ENABLE_HTTP_STREAM_PLAYBACK addMenuAction(menu, streamPlayAction); #endif menu->addSeparator(); addMenuAction(menu, quitAction); menuBar()->addMenu(menu); menu=new QMenu(tr("&Edit"), this); addMenuAction(menu, PlayQueueModel::self()->undoAct()); addMenuAction(menu, PlayQueueModel::self()->redoAct()); menu->addSeparator(); addMenuAction(menu, StdActions::self()->searchAction); addMenuAction(menu, searchPlayQueueAction); menu->addSeparator(); addMenuAction(menu, prefAction); menuBar()->addMenu(menu); menu=new QMenu(tr("&View"), this); addMenuAction(menu, expandInterfaceAction); addMenuAction(menu, fullScreenAction); menuBar()->addMenu(menu); menu=new QMenu(tr("&Queue"), this); addMenuAction(menu, clearPlayQueueAction); addMenuAction(menu, StdActions::self()->savePlayQueueAction); addMenuAction(menu, addStreamToPlayQueueAction); menu->addSeparator(); addMenuAction(menu, PlayQueueModel::self()->shuffleAct()); addMenuAction(menu, PlayQueueModel::self()->sortAct()); menuBar()->addMenu(menu); OSXStyle::self()->initWindowMenu(this); menu=new QMenu(tr("&Help"), this); addMenuAction(menu, serverInfoAction); addMenuAction(menu, aboutAction); menuBar()->addMenu(menu); menuButton->hide(); #else QMenu *mainMenu=new QMenu(this); mainMenu->addAction(expandInterfaceAction); mainMenu->addAction(fullScreenAction); mainMenu->addAction(connectionsAction); mainMenu->addAction(outputsAction); #ifdef ENABLE_HTTP_STREAM_PLAYBACK mainMenu->addAction(streamPlayAction); #endif mainMenu->addAction(prefAction); mainMenu->addAction(refreshDbAction); mainMenu->addSeparator(); mainMenu->addAction(StdActions::self()->searchAction); mainMenu->addAction(searchPlayQueueAction); mainMenu->addSeparator(); mainMenu->addAction(serverInfoAction); mainMenu->addAction(aboutAction); mainMenu->addSeparator(); mainMenu->addAction(quitAction); menuButton->setIcon(Icons::self()->toolbarMenuIcon); menuButton->setAlignedMenu(mainMenu); #endif dynamicLabel->setVisible(false); stopDynamicButton->setVisible(false); StdActions::self()->addWithPriorityAction->setVisible(false); StdActions::self()->setPriorityAction->setVisible(false); playQueueProxyModel.setSourceModel(PlayQueueModel::self()); playQueue->setModel(&playQueueProxyModel); playQueue->addAction(playQueue->removeFromAct()); ratingAction=new Action(tr("Set Rating"), this); ratingAction->setMenu(new QMenu(0)); for (int i=0; i<((Song::Rating_Max/Song::Rating_Step)+1); ++i) { QString text; if (0==i) { text=tr("No Rating"); } else { for (int s=0; screateAction(QLatin1String("rating")+QString::number(i), text); action->setProperty(constRatingKey, i*Song::Rating_Step); action->setShortcut(Qt::AltModifier+Qt::Key_0+i); ratingAction->menu()->addAction(action); connect(action, SIGNAL(triggered()), SLOT(setRating())); } playQueue->addAction(ratingAction); playQueue->addAction(StdActions::self()->setPriorityAction); playQueue->addAction(stopAfterTrackAction); playQueue->addAction(locateTrackAction); #ifdef TAGLIB_FOUND playQueue->addAction(editPlayQueueTagsAction); #endif playQueue->addAction(playNextAction); Action *sep=new Action(this); sep->setSeparator(true); playQueue->addAction(sep); playQueue->addAction(PlayQueueModel::self()->removeDuplicatesAct()); playQueue->addAction(clearPlayQueueAction); playQueue->addAction(cropPlayQueueAction); playQueue->addAction(StdActions::self()->savePlayQueueAction); playQueue->addAction(addStreamToPlayQueueAction); playQueue->addAction(addPlayQueueToStoredPlaylistAction); #ifdef ENABLE_DEVICES_SUPPORT playQueue->addAction(copyToDeviceAction); #endif playQueue->addAction(PlayQueueModel::self()->shuffleAct()); playQueue->addAction(PlayQueueModel::self()->sortAct()); playQueue->addAction(PlayQueueModel::self()->undoAct()); playQueue->addAction(PlayQueueModel::self()->redoAct()); playQueue->readConfig(); #ifdef ENABLE_DEVICES_SUPPORT connect(DevicesModel::self(), SIGNAL(addToDevice(const QString &)), this, SLOT(addToDevice(const QString &))); connect(DevicesModel::self(), SIGNAL(error(const QString &)), this, SLOT(showError(const QString &))); connect(libraryPage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(folderPage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(playlistsPage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(devicesPage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(searchPage, SIGNAL(addToDevice(const QString &, const QString &, const QList &)), SLOT(copyToDevice(const QString &, const QString &, const QList &))); connect(StdActions::self()->deleteSongsAction, SIGNAL(triggered()), SLOT(deleteSongs())); connect(devicesPage, SIGNAL(deleteSongs(const QString &, const QList &)), SLOT(deleteSongs(const QString &, const QList &))); connect(libraryPage, SIGNAL(deleteSongs(const QString &, const QList &)), SLOT(deleteSongs(const QString &, const QList &))); connect(folderPage, SIGNAL(deleteSongs(const QString &, const QList &)), SLOT(deleteSongs(const QString &, const QList &))); #endif foreach (QAction *act, StdActions::self()->setPriorityAction->menu()->actions()) { connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority())); } foreach (QAction *act, StdActions::self()->addWithPriorityAction->menu()->actions()) { connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority())); } connect(StdActions::self()->appendToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueueAndPlay())); connect(StdActions::self()->addToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(addToPlayQueueAndPlay())); connect(StdActions::self()->insertAfterCurrentAction, SIGNAL(triggered()), this, SLOT(insertIntoPlayQueue())); connect(MPDConnection::self(), SIGNAL(outputsUpdated(const QList &)), this, SLOT(outputsUpdated(const QList &))); connect(this, SIGNAL(enableOutput(int, bool)), MPDConnection::self(), SLOT(enableOutput(int, bool))); connect(this, SIGNAL(outputs()), MPDConnection::self(), SLOT(outputs())); connect(this, SIGNAL(pause(bool)), MPDConnection::self(), SLOT(setPause(bool))); connect(this, SIGNAL(play()), MPDConnection::self(), SLOT(play())); connect(this, SIGNAL(stop(bool)), MPDConnection::self(), SLOT(stopPlaying(bool))); connect(this, SIGNAL(terminating()), MPDConnection::self(), SLOT(stop())); connect(this, SIGNAL(getStatus()), MPDConnection::self(), SLOT(getStatus())); connect(this, SIGNAL(playListInfo()), MPDConnection::self(), SLOT(playListInfo())); connect(this, SIGNAL(currentSong()), MPDConnection::self(), SLOT(currentSong())); connect(this, SIGNAL(setSeekId(qint32, quint32)), MPDConnection::self(), SLOT(setSeekId(qint32, quint32))); connect(this, SIGNAL(startPlayingSongId(qint32)), MPDConnection::self(), SLOT(startPlayingSongId(qint32))); connect(this, SIGNAL(setDetails(const MPDConnectionDetails &)), MPDConnection::self(), SLOT(setDetails(const MPDConnectionDetails &))); connect(this, SIGNAL(setPriority(const QList &, quint8, bool)), MPDConnection::self(), SLOT(setPriority(const QList &, quint8, bool))); connect(this, SIGNAL(addSongsToPlaylist(const QString &, const QStringList &)), MPDConnection::self(), SLOT(addToPlaylist(const QString &, const QStringList &))); connect(PlayQueueModel::self(), SIGNAL(statsUpdated(int, quint32)), this, SLOT(updatePlayQueueStats(int, quint32))); connect(PlayQueueModel::self(), SIGNAL(fetchingStreams()), playQueue, SLOT(showSpinner())); connect(PlayQueueModel::self(), SIGNAL(streamsFetched()), playQueue, SLOT(hideSpinner())); connect(PlayQueueModel::self(), SIGNAL(updateCurrent(Song)), SLOT(updateCurrentSong(Song))); connect(PlayQueueModel::self(), SIGNAL(streamFetchStatus(QString)), playQueue, SLOT(streamFetchStatus(QString))); connect(playQueue, SIGNAL(cancelStreamFetch()), PlayQueueModel::self(), SLOT(cancelStreamFetch())); connect(playQueue, SIGNAL(itemsSelected(bool)), SLOT(playQueueItemsSelected(bool))); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus())); connect(MPDConnection::self(), SIGNAL(playlistUpdated(const QList &, bool)), this, SLOT(updatePlayQueue(const QList &, bool))); connect(MPDConnection::self(), SIGNAL(currentSongUpdated(Song)), this, SLOT(updateCurrentSong(Song))); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionStateChanged(bool))); connect(MPDConnection::self(), SIGNAL(error(const QString &, bool)), SLOT(showError(const QString &, bool))); connect(MPDConnection::self(), SIGNAL(info(const QString &)), SLOT(showInformation(const QString &))); connect(MPDConnection::self(), SIGNAL(dirChanged()), SLOT(checkMpdDir())); connect(MPDConnection::self(), SIGNAL(connectionNotChanged(QString)), SLOT(mpdConnectionName(QString))); connect(MpdLibraryModel::self(), SIGNAL(error(QString)), SLOT(showError(QString))); connect(refreshDbAction, SIGNAL(triggered()), this, SLOT(refreshDbPromp())); connect(doDbRefreshAction, SIGNAL(triggered()), MpdLibraryModel::self(), SLOT(clearDb())); connect(doDbRefreshAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(update())); connect(doDbRefreshAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide())); connect(connectAction, SIGNAL(triggered()), this, SLOT(connectToMpd())); connect(StdActions::self()->prevTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToPrevious())); connect(StdActions::self()->nextTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToNext())); connect(StdActions::self()->playPauseTrackAction, SIGNAL(triggered()), this, SLOT(playPauseTrack())); connect(StdActions::self()->stopPlaybackAction, SIGNAL(triggered()), this, SLOT(stopPlayback())); connect(StdActions::self()->stopAfterCurrentTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterCurrentTrack())); connect(stopAfterTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterTrack())); connect(this, SIGNAL(setVolume(int)), MPDConnection::self(), SLOT(setVolume(int))); connect(nowPlaying, SIGNAL(sliderReleased()), this, SLOT(setPosition())); connect(PlayQueueModel::self(), SIGNAL(currentSongRating(QString,quint8)), nowPlaying, SLOT(rating(QString,quint8))); connect(randomPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRandom(bool))); connect(repeatPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRepeat(bool))); connect(singlePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setSingle(bool))); connect(consumePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setConsume(bool))); #ifdef ENABLE_HTTP_STREAM_PLAYBACK connect(streamPlayAction, SIGNAL(triggered(bool)), httpStream, SLOT(setEnabled(bool))); connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), SLOT(streamUrl(QString))); #endif connect(playQueueSearchWidget, SIGNAL(returnPressed()), this, SLOT(searchPlayQueue())); connect(playQueueSearchWidget, SIGNAL(textChanged(const QString)), this, SLOT(searchPlayQueue())); connect(playQueueSearchWidget, SIGNAL(active(bool)), this, SLOT(playQueueSearchActivated(bool))); connect(playQueue, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(playQueueItemActivated(const QModelIndex &))); connect(StdActions::self()->removeAction, SIGNAL(triggered()), this, SLOT(removeItems())); connect(StdActions::self()->appendToPlayQueueAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueue())); connect(StdActions::self()->replacePlayQueueAction, SIGNAL(triggered()), this, SLOT(replacePlayQueue())); connect(playQueue->removeFromAct(), SIGNAL(triggered()), this, SLOT(removeFromPlayQueue())); connect(clearPlayQueueAction, SIGNAL(triggered()), playQueueSearchWidget, SLOT(clear())); connect(clearPlayQueueAction, SIGNAL(triggered()), this, SLOT(clearPlayQueue())); connect(centerPlayQueueAction, SIGNAL(triggered()), this, SLOT(centerPlayQueue())); connect(cropPlayQueueAction, SIGNAL(triggered()), this, SLOT(cropPlayQueue())); connect(songInfoAction, SIGNAL(triggered()), this, SLOT(showSongInfo())); connect(expandInterfaceAction, SIGNAL(triggered()), this, SLOT(expandOrCollapse())); connect(fullScreenAction, SIGNAL(triggered()), this, SLOT(fullScreen())); #ifdef TAGLIB_FOUND connect(StdActions::self()->editTagsAction, SIGNAL(triggered()), this, SLOT(editTags())); connect(editPlayQueueTagsAction, SIGNAL(triggered()), this, SLOT(editTags())); connect(StdActions::self()->organiseFilesAction, SIGNAL(triggered()), SLOT(organiseFiles())); #endif connect(context, SIGNAL(findArtist(QString)), this, SLOT(locateArtist(QString))); connect(context, SIGNAL(findAlbum(QString,QString)), this, SLOT(locateAlbum(QString,QString))); connect(context, SIGNAL(playSong(QString)), PlayQueueModel::self(), SLOT(playSong(QString))); connect(locateTrackAction, SIGNAL(triggered()), this, SLOT(locateTrack())); connect(this, SIGNAL(playNext(QList,quint32,quint32)), MPDConnection::self(), SLOT(move(QList,quint32,quint32))); connect(playNextAction, SIGNAL(triggered()), this, SLOT(moveSelectionAfterCurrentSong())); connect(StdActions::self()->searchAction, SIGNAL(triggered()), SLOT(showSearch())); connect(searchPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueueSearch())); connect(playQueue, SIGNAL(focusSearch(QString)), playQueueSearchWidget, SLOT(activate(QString))); connect(expandAllAction, SIGNAL(triggered()), this, SLOT(expandAll())); connect(collapseAllAction, SIGNAL(triggered()), this, SLOT(collapseAll())); connect(addStreamToPlayQueueAction, SIGNAL(triggered()), this, SLOT(addStreamToPlayQueue())); connect(StdActions::self()->setCoverAction, SIGNAL(triggered()), SLOT(setCover())); #ifdef ENABLE_REPLAYGAIN_SUPPORT connect(StdActions::self()->replaygainAction, SIGNAL(triggered()), SLOT(replayGain())); #endif connect(PlaylistsModel::self(), SIGNAL(addToNew()), this, SLOT(addToNewStoredPlaylist())); connect(PlaylistsModel::self(), SIGNAL(addToExisting(const QString &)), this, SLOT(addToExistingStoredPlaylist(const QString &))); // TODO: Why is this here??? // connect(playlistsPage, SIGNAL(add(const QStringList &, bool, quint8)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, bool, quint8))); connect(coverWidget, SIGNAL(clicked()), expandInterfaceAction, SLOT(trigger())); #if !defined Q_OS_WIN && !defined Q_OS_MAC connect(MountPoints::self(), SIGNAL(updated()), SLOT(checkMpdAccessibility())); #endif playQueueItemsSelected(false); playQueue->setFocus(); MPDConnection::self()->start(); connectToMpd(); QString page=Settings::self()->page(); for (int i=0; icount(); ++i) { if (tabWidget->widget(i)->metaObject()->className()==page) { tabWidget->setCurrentIndex(i); break; } } connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int))); connect(tabWidget, SIGNAL(tabToggled(int)), this, SLOT(tabToggled(int))); connect(tabWidget, SIGNAL(styleChanged(int)), this, SLOT(sidebarModeChanged())); readSettings(); updateConnectionsMenu(); ActionCollection::get()->readSettings(); if (testAttribute(Qt::WA_TranslucentBackground)) { // BUG: 146 - Work-around non-showing main window on start-up with transparent QtCurve windows. move(p.isNull() ? QPoint(96, 96) : p); } currentTabChanged(tabWidget->currentIndex()); if (Settings::self()->firstRun() && MPDConnection::self()->isConnected()) { mpdConnectionStateChanged(true); } MediaKeys::self()->start(); #ifdef Q_OS_MAC dockMenu=new DockMenu(this); #endif updateActionToolTips(); CustomActions::self()->setMainWindow(this); if (Utils::Gnome!=Utils::currentDe() && Settings::self()->startHidden()) { hide(); } else { show(); } } MainWindow::~MainWindow() { bool hadCantataStreams=PlayQueueModel::self()->removeCantataStreams(); Settings::self()->saveShowFullScreen(fullScreenAction->isChecked()); if (!fullScreenAction->isChecked()) { if (expandInterfaceAction->isChecked()) { Settings::self()->saveMainWindowSize(isMaximized() ? QSize(1, 1) : size()); } else { Settings::self()->saveMainWindowSize(expandedSize); } Settings::self()->saveMainWindowCollapsedSize(expandInterfaceAction->isChecked() ? collapsedSize : size()); Settings::self()->saveShowPlaylist(expandInterfaceAction->isChecked()); } #ifdef ENABLE_HTTP_STREAM_PLAYBACK Settings::self()->savePlayStream(streamPlayAction->isVisible() && streamPlayAction->isChecked()); #endif if (!fullScreenAction->isChecked()) { if (!tabWidget->isEnabled(PAGE_PLAYQUEUE)) { Settings::self()->saveSplitterState(splitter->saveState()); } } Settings::self()->savePage(tabWidget->currentWidget()->metaObject()->className()); playQueue->saveConfig(); // playlistsPage->saveConfig(); context->saveConfig(); StreamsModel::self()->save(); nowPlaying->saveConfig(); Settings::self()->saveForceSingleClick(TreeView::getForceSingleClick()); if (Utils::Gnome!=Utils::currentDe()) { Settings::StartupState startupState=Settings::self()->startupState(); Settings::self()->saveStartHidden(trayItem->isActive() && Settings::self()->minimiseOnClose() && ((isHidden() && Settings::SS_ShowMainWindow!=startupState) || (Settings::SS_HideMainWindow==startupState))); } Settings::self()->save(); disconnect(MPDConnection::self(), 0, 0, 0); if (Settings::self()->stopOnExit()) { DynamicPlaylists::self()->stop(); } if (Settings::self()->stopOnExit()) { emit terminating(); Utils::sleep(); // Allow time for stop to be sent... } else if (hadCantataStreams) { Utils::sleep(); // Allow time for removal of cantata streams... } #ifdef ENABLE_DEVICES_SUPPORT DevicesModel::self()->stop(); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND Covers::self()->cleanCdda(); #endif #endif MediaKeys::self()->stop(); #ifdef TAGLIB_FOUND Tags::stop(); #endif ThreadCleaner::self()->stopAll(); Configuration(playQueuePage->metaObject()->className()).set(ItemView::constSearchActiveKey, playQueueSearchWidget->isActive()); } QList MainWindow::selectedSongs() const { return currentPage ? currentPage->selectedSongs() : QList(); } QStringList MainWindow::listActions() const { QStringList allActions; for(int i = 0; i < ActionCollection::get()->actions().count(); i++) { Action *action = qobject_cast(ActionCollection::get()->actions().at(i)); if (!action) { continue; } allActions.append(action->objectName()+" - "+Action::settingsText(action)); } return allActions; } void MainWindow::triggerAction(const QString &name) { QAction *act=ActionCollection::get()->action(name); if (act) { act->trigger(); } } #ifdef Q_OS_MAC void MainWindow::addMenuAction(QMenu *menu, QAction *action) { menu->addAction(action); addAction(action); // Bind action to window, so that it works when fullscreen! } #endif void MainWindow::showError(const QString &message, bool showActions) { if (!message.isEmpty()) { if (!isVisible()) { show(); } expand(); } if (QLatin1String("NO_SONGS")==message) { messageWidget->setError(tr("Failed to locate any songs matching the dynamic playlist rules.")); } else { messageWidget->setError(message); } if (showActions) { messageWidget->setActions(QList() << prefAction << connectAction); } else { messageWidget->removeAllActions(); } QApplication::alert(this); } void MainWindow::showInformation(const QString &message) { if (!messageWidget->showingError()) { messageWidget->setInformation(message); messageWidget->removeAllActions(); } } void MainWindow::mpdConnectionStateChanged(bool connected) { serverInfoAction->setEnabled(connected && !MPDConnection::self()->isMopidy()); refreshDbAction->setEnabled(connected); addStreamToPlayQueueAction->setEnabled(connected); if (connected) { //if (!messageWidget->showingError()) { messageWidget->hide(); //} if (CS_Connected!=connectedState) { emit playListInfo(); emit outputs(); MpdLibraryModel::self()->reload(); if (CS_Init!=connectedState) { currentTabChanged(tabWidget->currentIndex()); } connectedState=CS_Connected; StdActions::self()->addWithPriorityAction->setVisible(MPDConnection::self()->canUsePriority()); StdActions::self()->setPriorityAction->setVisible(MPDConnection::self()->canUsePriority()); } } else { libraryPage->clear(); folderPage->clear(); PlaylistsModel::self()->clear(); PlayQueueModel::self()->clear(); searchPage->clear(); connectedState=CS_Disconnected; outputsAction->setVisible(false); MPDStatus dummyStatus; updateStatus(&dummyStatus); } updateWindowTitle(); } void MainWindow::keyPressEvent(QKeyEvent *event) { if ((Qt::Key_Enter==event->key() || Qt::Key_Return==event->key()) && playQueue->hasFocus()) { QModelIndexList selection=playQueue->selectedIndexes(); if (!selection.isEmpty()) { //play the first selected song playQueueItemActivated(selection.first()); } } } #if defined Q_OS_WIN void MainWindow::showEvent(QShowEvent *event) { if (!thumbnailTooolbar) { thumbnailTooolbar=new ThumbnailToolBar(this); } QMainWindow::showEvent(event); } #endif void MainWindow::closeEvent(QCloseEvent *event) { if (trayItem->isActive() && Settings::self()->minimiseOnClose()) { lastPos=pos(); hide(); if (event->spontaneous()) { event->ignore(); } } else if (canClose()) { QMainWindow::closeEvent(event); } } void MainWindow::playQueueItemsSelected(bool s) { int rc=playQueue->model() ? playQueue->model()->rowCount() : 0; bool haveItems=rc>0; bool singleSelection=1==playQueue->selectedIndexes(false).count(); // Dont need sorted selection here... playQueue->removeFromAct()->setEnabled(s && haveItems); StdActions::self()->setPriorityAction->setEnabled(s && haveItems); locateTrackAction->setEnabled(singleSelection); cropPlayQueueAction->setEnabled(playQueue->haveUnSelectedItems() && haveItems); #ifdef TAGLIB_FOUND editPlayQueueTagsAction->setEnabled(s && haveItems && MPDConnection::self()->getDetails().dirReadable); #endif addPlayQueueToStoredPlaylistAction->setEnabled(haveItems); #ifdef ENABLE_DEVICES_SUPPORT copyToDeviceAction->setEnabled(editPlayQueueTagsAction->isEnabled()); #endif stopAfterTrackAction->setEnabled(singleSelection); ratingAction->setEnabled(s && haveItems); } void MainWindow::connectToMpd(const MPDConnectionDetails &details) { if (!MPDConnection::self()->isConnected() || details!=MPDConnection::self()->getDetails()) { libraryPage->clear(); folderPage->clear(); PlaylistsModel::self()->clear(); PlayQueueModel::self()->clear(); searchPage->clear(); if (!MPDConnection::self()->getDetails().isEmpty() && details!=MPDConnection::self()->getDetails()) { DynamicPlaylists::self()->stop(); } showInformation(tr("Connecting to %1").arg(details.description())); outputsAction->setVisible(false); if (CS_Init!=connectedState) { connectedState=CS_Disconnected; } } emit setDetails(details); } void MainWindow::connectToMpd() { messageWidget->hide(); connectToMpd(Settings::self()->connectionDetails()); } void MainWindow::streamUrl(const QString &u) { #ifdef ENABLE_HTTP_STREAM_PLAYBACK streamPlayAction->setVisible(!u.isEmpty()); streamPlayAction->setChecked(streamPlayAction->isVisible() && Settings::self()->playStream()); streamPlayButton->setVisible(!u.isEmpty()); httpStream->setEnabled(streamPlayAction->isChecked()); leftSpacer->setVisible(loveTrack->isVisible() || scrobblingStatus->isVisible() || streamPlayButton->isVisible()); #else Q_UNUSED(u) #endif } void MainWindow::refreshDbPromp() { int btnLayout=style()->styleHint(QStyle::SH_DialogButtonLayout); if (QDialogButtonBox::GnomeLayout==btnLayout || QDialogButtonBox::MacLayout==btnLayout) { messageWidget->setActions(QList() << cancelAction << doDbRefreshAction); } else { messageWidget->setActions(QList() << doDbRefreshAction << cancelAction); } messageWidget->setWarning(tr("Refresh MPD Database?"), false); expand(); } void MainWindow::showAboutDialog() { QMessageBox::about(this, tr("About Cantata"), tr("Cantata %1

    MPD client.

    " "© 2011-2017 Craig Drummond
    Released under the GPLv3").arg(PACKAGE_VERSION_STRING)+ QLatin1String("

    ")+tr("Based upon QtMPC - © 2007-2010 The QtMPC Authors
    ")+ tr("Context view backdrops courtesy of FanArt.tv")+QLatin1String("
    ")+ tr("Context view metadata courtesy of Wikipedia and Last.fm")+ QLatin1String("

    ")+tr("Please consider uploading your own music fan-art to FanArt.tv")+ QLatin1String("
    ")); } bool MainWindow::canClose() { if (onlinePage->isDownloading() && MessageBox::No==MessageBox::warningYesNo(this, tr("A Podcast is currently being downloaded\n\nQuiting now will abort the download."), QString(), GuiItem(tr("Abort download and quit")), GuiItem("Do not quit just yet"))) { return false; } onlinePage->cancelAll(); return true; } void MainWindow::expand() { if (!expandInterfaceAction->isChecked()) { expandInterfaceAction->setChecked(true); expandOrCollapse(); } } bool MainWindow::canShowDialog() { if (PreferencesDialog::instanceCount() || CoverDialog::instanceCount() #ifdef TAGLIB_FOUND || TagEditor::instanceCount() || TrackOrganiser::instanceCount() #endif #ifdef ENABLE_DEVICES_SUPPORT || ActionDialog::instanceCount() || SyncDialog::instanceCount() #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT || RgDialog::instanceCount() #endif ) { MessageBox::error(this, tr("Please close other dialogs first.")); return false; } return true; } void MainWindow::showPreferencesDialog(const QString &page) { if (PreferencesDialog::instanceCount()) { emit showPreferencesPage(page.isEmpty() ? "collection" : page); } if (PreferencesDialog::instanceCount() || !canShowDialog()) { return; } PreferencesDialog *pref=new PreferencesDialog(this); controlConnectionsMenu(false); connect(pref, SIGNAL(settingsSaved()), this, SLOT(updateSettings())); connect(pref, SIGNAL(destroyed()), SLOT(controlConnectionsMenu())); connect(this, SIGNAL(showPreferencesPage(QString)), pref, SLOT(showPage(QString))); if (!page.isEmpty()) { pref->showPage(page); } pref->show(); } void MainWindow::quit() { if (!canClose()) { return; } #ifdef ENABLE_REPLAYGAIN_SUPPORT if (RgDialog::instanceCount()) { return; } #endif #ifdef TAGLIB_FOUND if (TagEditor::instanceCount() || 0!=TrackOrganiser::instanceCount()) { return; } #endif #ifdef ENABLE_DEVICES_SUPPORT if (ActionDialog::instanceCount() || SyncDialog::instanceCount()) { return; } #endif qApp->quit(); } void MainWindow::checkMpdDir() { #ifdef Q_OS_LINUX if (mpdAccessibilityTimer) { mpdAccessibilityTimer->stop(); } MPDConnection::self()->setDirReadable(); #endif #ifdef TAGLIB_FOUND if (StdActions::self()->editTagsAction->isEnabled()) { StdActions::self()->editTagsAction->setEnabled(MPDConnection::self()->getDetails().dirReadable); } #endif if (currentPage) { currentPage->controlActions(); } } void MainWindow::outputsUpdated(const QList &outputs) { const char *constMpdConName="mpd-name"; const char *constMpdEnabledOuptuts="mpd-outputs"; QString lastConn=property(constMpdConName).toString(); QString newConn=MPDConnection::self()->getDetails().name; setProperty(constMpdConName, newConn); outputsAction->setVisible(true); QSet enabledMpd; QSet lastEnabledMpd=QSet::fromList(property(constMpdEnabledOuptuts).toStringList()); QSet mpd; QSet menuItems; QMenu *menu=outputsAction->menu(); foreach (const Output &o, outputs) { if (o.enabled) { enabledMpd.insert(o.name); } mpd.insert(o.name); } foreach (QAction *act, menu->actions()) { menuItems.insert(act->data().toString()); } if (menuItems!=mpd) { menu->clear(); QList out=outputs; qSort(out); int i=Qt::Key_1; foreach (const Output &o, out) { QAction *act=menu->addAction(o.name, this, SLOT(toggleOutput())); act->setData(o.id); act->setCheckable(true); act->setChecked(o.enabled); act->setShortcut(Qt::ControlModifier+Qt::AltModifier+nextKey(i)); } } else { foreach (const Output &o, outputs) { foreach (QAction *act, menu->actions()) { if (Utils::strippedText(act->text())==o.name) { act->setChecked(o.enabled); break; } } } } if (newConn==lastConn && enabledMpd!=lastEnabledMpd && !menuItems.isEmpty()) { QSet switchedOn=enabledMpd-lastEnabledMpd; QSet switchedOff=lastEnabledMpd-enabledMpd; if (!switchedOn.isEmpty() && switchedOff.isEmpty()) { QStringList names=switchedOn.toList(); qSort(names); trayItem->showMessage(tr("Outputs"), tr("Enabled: %1").arg(names.join(QLatin1String(", "))), Icons::self()->speakerIcon.pixmap(64, 64).toImage()); } else if (!switchedOff.isEmpty() && switchedOn.isEmpty()) { QStringList names=switchedOff.toList(); qSort(names); trayItem->showMessage(tr("Outputs"), tr("Disabled: %1").arg(names.join(QLatin1String(", "))), Icons::self()->speakerIcon.pixmap(64, 64).toImage()); } else if (!switchedOn.isEmpty() && !switchedOff.isEmpty()) { QStringList on=switchedOn.toList(); qSort(on); QStringList off=switchedOff.toList(); qSort(off); trayItem->showMessage(tr("Outputs"), tr("Enabled: %1").arg(on.join(QLatin1String(", ")))+QLatin1Char('\n')+ tr("Disabled: %1").arg(off.join(QLatin1String(", "))), Icons::self()->speakerIcon.pixmap(64, 64).toImage()); } } setProperty(constMpdEnabledOuptuts, QStringList() << enabledMpd.toList()); outputsAction->setVisible(outputs.count()>1); trayItem->updateOutputs(); } void MainWindow::updateConnectionsMenu() { QList connections=Settings::self()->allConnections(); if (connections.count()<2) { connectionsAction->setVisible(false); } else { connectionsAction->setVisible(true); QString current=Settings::self()->currentConnection(); QSet cfg; QSet menuItems; QMenu *menu=connectionsAction->menu(); foreach (const MPDConnectionDetails &d, connections) { cfg.insert(d.name); } foreach (QAction *act, menu->actions()) { menuItems.insert(act->data().toString()); act->setChecked(act->data().toString()==current); } if (menuItems!=cfg) { menu->clear(); qSort(connections); int i=Qt::Key_1; foreach (const MPDConnectionDetails &d, connections) { QAction *act=menu->addAction(d.getName(), this, SLOT(changeConnection())); act->setData(d.name); act->setCheckable(true); act->setChecked(d.name==current); act->setActionGroup(connectionsGroup); act->setShortcut(Qt::ControlModifier+nextKey(i)); } } } trayItem->updateConnections(); } void MainWindow::controlConnectionsMenu(bool enable) { if (enable) { updateConnectionsMenu(); } foreach(QAction *act, connectionsAction->menu()->actions()) { act->setEnabled(enable); } } void MainWindow::controlDynamicButton() { stopDynamicButton->setVisible(DynamicPlaylists::self()->isRunning()); PlayQueueModel::self()->enableUndo(!DynamicPlaylists::self()->isRunning()); } void MainWindow::setRating() { Action *act=qobject_cast(sender()); if (act) { PlayQueueModel::self()->setRating(playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes()), act->property(constRatingKey).toUInt()); } } void MainWindow::initMpris() { #ifdef QT_QTDBUS_FOUND if (Settings::self()->mpris()) { if (!mpris) { mpris=new Mpris(this); connect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow())); } } else if (mpris) { disconnect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow())); mpris->deleteLater(); mpris=0; } CurrentCover::self()->setEnabled(mpris || Settings::self()->showPopups() || 0!=Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget()); #endif } void MainWindow::readSettings() { #ifdef QT_QTDBUS_FOUND // It appears as if the KDE MPRIS code does not like the MPRIS interface to be setup before the window is visible. // to work-around this, initMpris in the next event loop iteration. // See #863 QTimer::singleShot(0, this, SLOT(initMpris())); #else CurrentCover::self()->setEnabled(Settings::self()->showPopups() || 0!=Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget()); #endif checkMpdDir(); Song::setIgnorePrefixes(Settings::self()->ignorePrefixes()); Covers::self()->readConfig(); HttpServer::self()->readConfig(); #ifdef ENABLE_DEVICES_SUPPORT StdActions::self()->deleteSongsAction->setVisible(Settings::self()->showDeleteAction()); #endif Song::setComposerGenres(Settings::self()->composerGenres()); trayItem->setup(); autoScrollPlayQueue=Settings::self()->playQueueScroll(); TreeView::setForceSingleClick(Settings::self()->forceSingleClick()); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) PowerManagement::self()->setInhibitSuspend(Settings::self()->inhibitSuspend()); #endif context->readConfig(); tabWidget->setHiddenPages(Settings::self()->hiddenPages()); tabWidget->setStyle(Settings::self()->sidebar()); coverWidget->setEnabled(Settings::self()->showCoverWidget()); stopTrackButton->setVisible(Settings::self()->showStopButton()); #if defined Q_OS_WIN if (thumbnailTooolbar) { thumbnailTooolbar->readSettings(); } #endif searchPlayQueueAction->setEnabled(Settings::self()->playQueueSearch()); searchPlayQueueAction->setVisible(Settings::self()->playQueueSearch()); nowPlaying->readConfig(); setCollapsedSize(); toggleSplitterAutoHide(); if (contextSwitchTime!=Settings::self()->contextSwitchTime()) { contextSwitchTime=Settings::self()->contextSwitchTime(); if (0==contextSwitchTime && contextTimer) { contextTimer->stop(); contextTimer->deleteLater(); contextTimer=0; } } MPDConnection::self()->setVolumeFadeDuration(Settings::self()->stopFadeDuration()); MPDParseUtils::setSingleTracksFolders(Settings::self()->singleTracksFolders()); MPDParseUtils::setCueFileSupport(Settings::self()->cueSupport()); leftSpacer->setVisible(loveTrack->isVisible() || scrobblingStatus->isVisible() || streamPlayButton->isVisible()); } void MainWindow::updateSettings() { connectToMpd(); Settings::self()->save(); readSettings(); bool wasAutoExpand=playQueue->isAutoExpand(); bool wasStartClosed=playQueue->isStartClosed(); bool wasGrouped=playQueue->isGrouped(); playQueue->readConfig(); if (wasGrouped!=playQueue->isGrouped() || (playQueue->isGrouped() && (wasAutoExpand!=playQueue->isAutoExpand() || wasStartClosed!=playQueue->isStartClosed())) ) { QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0)); playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing==MPDStatus::self()->state()); } updateActionToolTips(); } void MainWindow::toggleOutput() { QAction *act=qobject_cast(sender()); if (act) { emit enableOutput(act->data().toInt(), act->isChecked()); } } void MainWindow::changeConnection() { QAction *act=qobject_cast(sender()); if (act) { Settings::self()->saveCurrentConnection(act->data().toString()); connectToMpd(); } } void MainWindow::showServerInfo() { QStringList handlers=MPDConnection::self()->urlHandlers().toList(); QStringList tags=MPDConnection::self()->tags().toList(); qSort(handlers); qSort(tags); long version=MPDConnection::self()->version(); QDateTime dbUpdate; dbUpdate.setTime_t(MPDStats::self()->dbUpdate()); MessageBox::information(this, #ifdef Q_OS_MAC tr("Server Information")+QLatin1String("

    ")+ #endif QLatin1String("

    ")+ tr("" "" "" "" "" "") .arg((version>>16)&0xFF).arg((version>>8)&0xFF).arg(version&0xFF) .arg(Utils::formatDuration(MPDStats::self()->uptime())) .arg(Utils::formatDuration(MPDStats::self()->playtime())) .arg(handlers.join(", ")).arg(tags.join(", "))+ QLatin1String("")+ tr("" "" "" "" "" "") .arg(MPDStats::self()->artists()).arg(MPDStats::self()->albums()).arg(MPDStats::self()->songs()) .arg(Utils::formatDuration(MPDStats::self()->dbPlaytime())).arg(dbUpdate.toString(Qt::SystemLocaleShortDate))+ QLatin1String("
    Server
    Protocol: %1.%2.%3
    Uptime: %4
    Playing: %5
    Handlers: %6
    Tags: %7
    Database
    Artists: %1
    Albums: %2
    Songs: %3
    Duration: %4
    Updated: %5

    "), tr("Server Information")); } void MainWindow::enableStopActions(bool enable) { StdActions::self()->stopAfterCurrentTrackAction->setEnabled(enable); StdActions::self()->stopPlaybackAction->setEnabled(enable); } void MainWindow::stopPlayback() { emit stop(); enableStopActions(false); StdActions::self()->nextTrackAction->setEnabled(false); StdActions::self()->prevTrackAction->setEnabled(false); } void MainWindow::stopAfterCurrentTrack() { PlayQueueModel::self()->clearStopAfterTrack(); emit stop(true); } void MainWindow::stopAfterTrack() { QModelIndexList selected=playQueue->selectedIndexes(false); // Dont need sorted selection here... if (1==selected.count()) { QModelIndex idx=playQueueProxyModel.mapToSource(selected.first()); PlayQueueModel::self()->setStopAfterTrack(PlayQueueModel::self()->getIdByRow(idx.row())); } } void MainWindow::playPauseTrack() { switch (MPDStatus::self()->state()) { case MPDState_Playing: emit pause(true); break; case MPDState_Paused: emit pause(false); break; default: if (PlayQueueModel::self()->rowCount()>0) { if (-1!=PlayQueueModel::self()->currentSong() && -1!=PlayQueueModel::self()->currentSongRow()) { emit startPlayingSongId(PlayQueueModel::self()->currentSong()); } else { emit play(); } } } } void MainWindow::setPosition() { emit setSeekId(MPDStatus::self()->songId(), nowPlaying->value()); } void MainWindow::searchPlayQueue() { if (playQueueSearchWidget->text().isEmpty()) { if (playQueueSearchTimer) { playQueueSearchTimer->stop(); } realSearchPlayQueue(); } else { if (!playQueueSearchTimer) { playQueueSearchTimer=new QTimer(this); playQueueSearchTimer->setSingleShot(true); connect(playQueueSearchTimer, SIGNAL(timeout()), SLOT(realSearchPlayQueue())); } playQueueSearchTimer->start(250); } } void MainWindow::realSearchPlayQueue() { if (playQueueSearchTimer) { playQueueSearchTimer->stop(); } QString filter=playQueueSearchWidget->text().trimmed(); if (filter.length()<2) { filter=QString(); } if (filter!=playQueueProxyModel.filterText()) { playQueue->setFilterActive(!filter.isEmpty()); playQueue->clearSelection(); playQueueProxyModel.update(filter); QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0)); playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing==MPDStatus::self()->state()); scrollPlayQueue(); } } void MainWindow::playQueueSearchActivated(bool a) { if (!a && playQueue->isVisible()) { playQueue->setFocus(); } } void MainWindow::updatePlayQueue(const QList &songs, bool isComplete) { StdActions::self()->playPauseTrackAction->setEnabled(!songs.isEmpty()); StdActions::self()->nextTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && songs.count()>1); StdActions::self()->prevTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && songs.count()>1); StdActions::self()->savePlayQueueAction->setEnabled(!songs.isEmpty()); clearPlayQueueAction->setEnabled(!songs.isEmpty()); int topRow=-1; QModelIndex topIndex=PlayQueueModel::self()->lastCommandWasUnodOrRedo() ? playQueue->indexAt(QPoint(0, 0)) : QModelIndex(); if (topIndex.isValid()) { topRow=playQueueProxyModel.mapToSource(topIndex).row(); } bool wasEmpty=0==PlayQueueModel::self()->rowCount(); bool songChanged=false; PlayQueueModel::self()->update(songs, isComplete); if (songs.isEmpty()) { updateCurrentSong(Song(), wasEmpty); } else if (wasEmpty || current.isStandardStream()) { // Check to see if it has been updated... Song pqSong=PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->currentSongRow()); if (wasEmpty || pqSong.isDifferent(current) ) { updateCurrentSong(pqSong, wasEmpty); songChanged=true; } } QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0)); bool scroll=songChanged && autoScrollPlayQueue && playQueueProxyModel.isEmpty() && (wasEmpty || MPDState_Playing==MPDStatus::self()->state()); playQueue->updateRows(idx.row(), current.key, scroll, wasEmpty); if (!scroll && topRow>0 && topRowrowCount()) { playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(topRow, PlayQueueModel::COL_TITLE)), QAbstractItemView::PositionAtTop); } playQueueItemsSelected(playQueue->haveSelectedItems()); } void MainWindow::updateWindowTitle() { bool multipleConnections=connectionsAction->isVisible(); QString connection=MPDConnection::self()->getDetails().getName(); setWindowTitle(multipleConnections ? tr("Cantata (%1)").arg(connection) : "Cantata"); } void MainWindow::updateCurrentSong(Song song, bool wasEmpty) { if (song.isCdda()) { emit getStatus(); if (song.isUnknownAlbum()) { Song pqSong=PlayQueueModel::self()->getSongById(song .id); if (!pqSong.isEmpty()) { song=pqSong; } } } // if (song.isCantataStream()) { // Song mod=HttpServer::self()->decodeUrl(song.file); // if (!mod.title.isEmpty()) { // mod.id=song.id; // song=mod; // } // } bool diffSong=song.isDifferent(current); current=song; CurrentCover::self()->update(current); #ifdef QT_QTDBUS_FOUND if (mpris) { mpris->updateCurrentSong(current); } #endif if (current.time<5 && MPDStatus::self()->songId()==current.id && MPDStatus::self()->timeTotal()>5) { current.time=MPDStatus::self()->timeTotal(); } nowPlaying->setEnabled(-1!=current.id && !current.isCdda() && (!currentIsStream() || current.time>5)); nowPlaying->update(current); bool isPlaying=MPDState_Playing==MPDStatus::self()->state(); PlayQueueModel::self()->updateCurrentSong(current.id); QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0)); playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && isPlaying, wasEmpty); scrollPlayQueue(wasEmpty); if (diffSong) { context->update(current); trayItem->songChanged(song, isPlaying); } centerPlayQueueAction->setEnabled(!song.isEmpty()); } void MainWindow::scrollPlayQueue(bool wasEmpty) { if (autoScrollPlayQueue && (wasEmpty || MPDState_Playing==MPDStatus::self()->state()) && !playQueue->isGrouped()) { qint32 row=PlayQueueModel::self()->currentSongRow(); if (row>=0) { playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(row, 0)), QAbstractItemView::PositionAtCenter); } } } void MainWindow::updateStatus() { updateStatus(MPDStatus::self()); } void MainWindow::updateStatus(MPDStatus * const status) { if (!status->error().isEmpty()) { showError(tr("MPD reported the following error: %1").arg(status->error())); } if (MPDState_Stopped==status->state() || MPDState_Inactive==status->state()) { nowPlaying->clearTimes(); PlayQueueModel::self()->clearStopAfterTrack(); if (statusTimer) { statusTimer->stop(); statusTimer->setProperty("count", 0); } } else { nowPlaying->setRange(0, 0==status->timeTotal() && 0!=current.time && (current.isCdda() || current.isCantataStream()) ? current.time : status->timeTotal()); nowPlaying->setValue(status->timeElapsed()); if (0==status->timeTotal() && 0==status->timeElapsed()) { if (!statusTimer) { statusTimer=new QTimer(this); statusTimer->setSingleShot(true); connect(statusTimer, SIGNAL(timeout()), SIGNAL(getStatus())); } QVariant id=statusTimer->property("id"); if (!id.isValid() || id.toInt()!=current.id) { statusTimer->setProperty("id", current.id); statusTimer->setProperty("count", 0); statusTimer->start(250); } else if (statusTimer->property("count").toInt()<12) { statusTimer->setProperty("count", statusTimer->property("count").toInt()+1); statusTimer->start(250); } } else if (!nowPlaying->isEnabled()) { nowPlaying->setEnabled(-1!=current.id && !current.isCdda() && (!currentIsStream() || status->timeTotal()>5)); } } randomPlayQueueAction->setChecked(status->random()); repeatPlayQueueAction->setChecked(status->repeat()); singlePlayQueueAction->setChecked(status->single()); consumePlayQueueAction->setChecked(status->consume()); updateNextTrack(status->nextSongId()); if (status->timeElapsed()<172800 && (!currentIsStream() || (status->timeTotal()>0 && status->timeElapsed()<=status->timeTotal()))) { if (status->state() == MPDState_Stopped || status->state() == MPDState_Inactive) { nowPlaying->setRange(0, 0); } else { nowPlaying->setValue(status->timeElapsed()); } } PlayQueueModel::self()->setState(status->state()); StdActions::self()->playPauseTrackAction->setEnabled(status->playlistLength()>0); switch (status->state()) { case MPDState_Playing: StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPauseIcon); enableStopActions(true); StdActions::self()->nextTrackAction->setEnabled(status->playlistLength()>1); StdActions::self()->prevTrackAction->setEnabled(status->playlistLength()>1); nowPlaying->startTimer(); break; case MPDState_Inactive: case MPDState_Stopped: StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon); enableStopActions(false); StdActions::self()->nextTrackAction->setEnabled(false); StdActions::self()->prevTrackAction->setEnabled(false); if (!StdActions::self()->playPauseTrackAction->isEnabled()) { current=Song(); nowPlaying->update(current); CurrentCover::self()->update(current); context->update(current); } current.id=0; trayItem->setToolTip("cantata", tr("Cantata"), QLatin1String("")+tr("Playback stopped")+QLatin1String("")); nowPlaying->stopTimer(); break; case MPDState_Paused: StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon); enableStopActions(0!=status->playlistLength()); StdActions::self()->nextTrackAction->setEnabled(status->playlistLength()>1); StdActions::self()->prevTrackAction->setEnabled(status->playlistLength()>1); nowPlaying->stopTimer(); default: break; } // Check if song has changed or we're playing again after being stopped, and update song info if needed if (MPDState_Inactive!=status->state() && (MPDState_Inactive==lastState || (MPDState_Stopped==lastState && MPDState_Playing==status->state()) || lastSongId != status->songId())) { emit currentSong(); } if (status->state()!=lastState && (MPDState_Playing==status->state() || MPDState_Stopped==status->state())) { startContextTimer(); } // Update status info lastState = status->state(); lastSongId = status->songId(); #if defined Q_OS_WIN if (thumbnailTooolbar) { thumbnailTooolbar->update(status); } #endif #ifdef Q_OS_MAC dockMenu->update(status); #endif } void MainWindow::playQueueItemActivated(const QModelIndex &index) { emit startPlayingSongId(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(index).row())); } void MainWindow::clearPlayQueue() { if (!Settings::self()->playQueueConfirmClear() || MessageBox::Yes==MessageBox::questionYesNo(this, tr("Remove all songs from play queue?"))) { if (dynamicLabel->isVisible()) { DynamicPlaylists::self()->stop(true); } else { PlayQueueModel::self()->removeAll(); } } } void MainWindow::centerPlayQueue() { QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), playQueue->isGrouped() ? 0 : PlayQueueModel::COL_TITLE)); if (idx.isValid()) { if (playQueue->isGrouped()) { playQueue->updateRows(idx.row(), current.key, true, true); } else { playQueue->scrollTo(idx, QAbstractItemView::PositionAtCenter); } } } void MainWindow::appendToPlayQueue(int action, quint8 priority, bool decreasePriority) { playQueueSearchWidget->clear(); if (currentPage) { currentPage->addSelectionToPlaylist(QString(), action, priority, decreasePriority); } } void MainWindow::addWithPriority() { QAction *act=qobject_cast(sender()); if (!act || !MPDConnection::self()->canUsePriority()) { return; } int prio=act->data().toInt(); bool isPlayQueue=playQueue->hasFocus(); bool decreasePriority=false; QModelIndexList pqItems; if (isPlayQueue) { pqItems=playQueue->selectedIndexes(); if (pqItems.isEmpty()) { return; } } if (-1==prio) { InputDialog dlg(tr("Priority"), tr("Enter priority (0..255):"), 150, 0, 255, 5, this); QCheckBox *dec = new QCheckBox(tr("Decrease priority for each subsequent track"), this); dec->setChecked(false); dlg.addExtraWidget(QString(), dec); if (QDialog::Accepted!=dlg.exec()) { return; } prio=dlg.spinBox()->value(); decreasePriority=dec->isChecked(); } if (prio>=0 && prio<=255) { if (isPlayQueue) { QList ids; foreach (const QModelIndex &idx, pqItems) { ids.append(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(idx).row())); } emit setPriority(ids, prio, decreasePriority); } else { appendToPlayQueue(false, prio, decreasePriority); } } } void MainWindow::addToNewStoredPlaylist() { bool pq=playQueue->hasFocus(); for(;;) { QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), QString(), 0, this); if (name==MPDConnection::constStreamsPlayListName) { MessageBox::error(this, tr("'%1' is used to store favorite streams, please choose another name.").arg(name)); continue; } if (PlaylistsModel::self()->exists(name)) { switch(MessageBox::warningYesNoCancel(this, tr("A playlist named '%1' already exists!\n\nAdd to that playlist?").arg(name), tr("Existing Playlist"))) { case MessageBox::Cancel: return; case MessageBox::Yes: break; case MessageBox::No: default: continue; } } if (!name.isEmpty()) { addToExistingStoredPlaylist(name, pq); } break; } } void MainWindow::addToExistingStoredPlaylist(const QString &name, bool pq) { if (pq) { QModelIndexList items = playQueue->selectedIndexes(); QStringList files; if (items.isEmpty()) { files = PlayQueueModel::self()->filenames(); } else { foreach (const QModelIndex &idx, items) { Song s = PlayQueueModel::self()->getSongByRow(playQueueProxyModel.mapToSource(idx).row()); if (!s.file.isEmpty()) { files.append(s.file); } } } if (!files.isEmpty()) { emit addSongsToPlaylist(name, files); } } else if (currentPage) { currentPage->addSelectionToPlaylist(name); } } void MainWindow::addStreamToPlayQueue() { StreamDialog dlg(this, true); if (QDialog::Accepted==dlg.exec()) { QString url=dlg.url(); if (dlg.save()) { StreamsModel::self()->addToFavourites(url, dlg.name()); } PlayQueueModel::self()->addItems(QStringList() << StreamsModel::modifyUrl(url, true, dlg.name()), MPDConnection::Append, 0, false); } } void MainWindow::removeItems() { if (currentPage) { currentPage->removeItems(); } } void MainWindow::checkMpdAccessibility() { #ifdef Q_OS_LINUX if (!mpdAccessibilityTimer) { mpdAccessibilityTimer=new QTimer(this); connect(mpdAccessibilityTimer, SIGNAL(timeout()), SLOT(checkMpdDir())); } mpdAccessibilityTimer->start(500); #endif } void MainWindow::updatePlayQueueStats(int songs, quint32 time) { if (0==songs) { playQueueStatsLabel->setText(QString()); } else if (0==time) { playQueueStatsLabel->setText(tr("%n Track(s)", "", songs)); } else { playQueueStatsLabel->setText(tr("%n Tracks (%1)", "", songs).arg(Utils::formatDuration(time))); } } void MainWindow::showSongInfo() { if (songInfoAction->isCheckable()) { stack->setCurrentWidget(songInfoAction->isChecked() ? (QWidget *)context : (QWidget *)splitter); } else { showTab(PAGE_CONTEXT); } } void MainWindow::fullScreen() { if (expandInterfaceAction->isChecked()) { #ifndef Q_OS_MAC fullScreenLabel->setVisible(!isFullScreen()); #endif expandInterfaceAction->setEnabled(isFullScreen()); if (isFullScreen()) { showNormal(); } else { showFullScreen(); } } else { fullScreenAction->setChecked(false); } } void MainWindow::sidebarModeChanged() { if (expandInterfaceAction->isChecked()) { setMinimumHeight(calcMinHeight()); } } void MainWindow::currentTabChanged(int index) { prevPage=index; controlDynamicButton(); switch(index) { case PAGE_LIBRARY: currentPage=libraryPage; break; case PAGE_FOLDERS: folderPage->load(); currentPage=folderPage; break; case PAGE_PLAYLISTS: currentPage=playlistsPage; break; case PAGE_ONLINE: currentPage=onlinePage; break; #ifdef ENABLE_DEVICES_SUPPORT case PAGE_DEVICES: currentPage=devicesPage; break; #endif case PAGE_SEARCH: currentPage=searchPage; break; default: currentPage=0; break; } if (currentPage) { currentPage->controlActions(); } } void MainWindow::tabToggled(int index) { switch (index) { case PAGE_PLAYQUEUE: if (tabWidget->isEnabled(index)) { splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE)); playQueueWidget->setParent(playQueuePage); playQueuePage->layout()->addWidget(playQueueWidget); playQueueWidget->setVisible(true); } else { playQueuePage->layout()->removeWidget(playQueueWidget); playQueueWidget->setParent(splitter); playQueueWidget->setVisible(true); splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE)); } playQueue->updatePalette(); break; case PAGE_CONTEXT: if (tabWidget->isEnabled(index) && songInfoAction->isCheckable()) { context->setParent(contextPage); contextPage->layout()->addWidget(context); context->setVisible(true); songInfoButton->setVisible(false); songInfoAction->setCheckable(false); } else if (!songInfoAction->isCheckable()) { contextPage->layout()->removeWidget(context); stack->addWidget(context); songInfoButton->setVisible(true); songInfoAction->setCheckable(true); } break; case PAGE_LIBRARY: locateTrackAction->setVisible(tabWidget->isEnabled(index)); break; case PAGE_FOLDERS: folderPage->setEnabled(!folderPage->isEnabled()); break; case PAGE_PLAYLISTS: break; case PAGE_ONLINE: onlinePage->setEnabled(onlinePage->isEnabled()); break; #ifdef ENABLE_DEVICES_SUPPORT case PAGE_DEVICES: DevicesModel::self()->setEnabled(!DevicesModel::self()->isEnabled()); StdActions::self()->copyToDeviceAction->setVisible(DevicesModel::self()->isEnabled()); break; #endif default: break; } sidebarModeChanged(); } void MainWindow::toggleSplitterAutoHide() { bool ah=Settings::self()->splitterAutoHide(); if (splitter->isAutoHideEnabled()!=ah) { splitter->setAutoHideEnabled(ah); splitter->setAutohidable(0, ah); } } void MainWindow::locateTracks(const QList &songs) { if (!songs.isEmpty() && tabWidget->isEnabled(PAGE_LIBRARY)) { showLibraryTab(); libraryPage->showSongs(songs); } } void MainWindow::moveSelectionAfterCurrentSong() { QList selectedIdexes = playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes()); if( !selectedIdexes.empty() ){ QList selectedSongIds; foreach (int row, selectedIdexes){ selectedSongIds.append( (quint32) row); } int currentSongIdx = PlayQueueModel::self()->currentSongRow(); emit playNext(selectedSongIds, currentSongIdx+1, PlayQueueModel::self()->rowCount()); } } void MainWindow::locateArtist(const QString &artist) { if (songInfoAction->isCheckable()) { songInfoAction->setChecked(false); showSongInfo(); } showLibraryTab(); libraryPage->showArtist(artist); } void MainWindow::locateAlbum(const QString &artist, const QString &album) { if (songInfoAction->isCheckable()) { songInfoAction->setChecked(false); showSongInfo(); } showLibraryTab(); libraryPage->showAlbum(artist, album); } void MainWindow::dynamicStatus(const QString &message) { DynamicPlaylists::self()->helperMessage(message); } void MainWindow::setCollection(const QString &collection) { if (!connectionsAction->isVisible()) { return; } foreach (QAction *act, connectionsAction->menu()->actions()) { if (Utils::strippedText(act->text())==collection) { if (!act->isChecked()) { act->trigger(); } break; } } } void MainWindow::mpdConnectionName(const QString &name) { foreach (QAction *act, connectionsAction->menu()->actions()) { if (Utils::strippedText(act->text())==name) { if (!act->isChecked()) { act->setChecked(true); } break; } } } void MainWindow::showPlayQueueSearch() { playQueueSearchWidget->activate(); } void MainWindow::showSearch() { if (!searchPlayQueueAction->isEnabled() && playQueue->hasFocus()) { playQueueSearchWidget->activate(); } else if (context->isVisible()) { context->search(); } else if (currentPage && splitter->sizes().at(0)>0) { if (currentPage==folderPage) { showTab(PAGE_SEARCH); if (PAGE_SEARCH==tabWidget->currentIndex()) { searchPage->setSearchCategory("file"); } } else { currentPage->focusSearch(); } } else if (!searchPlayQueueAction->isEnabled() && (playQueuePage->isVisible() || playQueue->isVisible())) { playQueueSearchWidget->activate(); } } void MainWindow::expandAll() { QWidget *f=QApplication::focusWidget(); if (f && qobject_cast(f) && !qobject_cast(f)) { static_cast(f)->expandAll(QModelIndex(), true); } } void MainWindow::collapseAll() { QWidget *f=QApplication::focusWidget(); if (f && qobject_cast(f) && !qobject_cast(f)) { static_cast(f)->collapseAll(); } } void MainWindow::editTags() { #ifdef TAGLIB_FOUND if (TagEditor::instanceCount() || !canShowDialog()) { return; } QList songs; bool isPlayQueue=playQueue->hasFocus(); if (isPlayQueue) { songs=playQueue->selectedSongs(); } else if (currentPage) { songs=currentPage->selectedSongs(); } if (songs.isEmpty()) { return; } QSet artists, albumArtists, composers, albums, genres; QString udi; #ifdef ENABLE_DEVICES_SUPPORT if (!isPlayQueue && currentPage==devicesPage) { DevicesModel::self()->getDetails(artists, albumArtists, composers, albums, genres); udi=devicesPage->activeFsDeviceUdi(); if (udi.isEmpty()) { return; } } #endif MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres); TagEditor *dlg=new TagEditor(this, songs, artists, albumArtists, composers, albums, genres, udi); dlg->show(); #endif } void MainWindow::organiseFiles() { #ifdef TAGLIB_FOUND if (TrackOrganiser::instanceCount() || !canShowDialog()) { return; } QList songs; if (currentPage) { songs=currentPage->selectedSongs(); } if (!songs.isEmpty()) { QString udi; #ifdef ENABLE_DEVICES_SUPPORT if (currentPage==devicesPage) { udi=devicesPage->activeFsDeviceUdi(); if (udi.isEmpty()) { return; } } #endif TrackOrganiser *dlg=new TrackOrganiser(this); dlg->show(songs, udi); } #endif } void MainWindow::addToDevice(const QString &udi) { #ifdef ENABLE_DEVICES_SUPPORT if (playQueue->hasFocus()) { copyToDevice(QString(), udi, playQueue->selectedSongs()); } else if (currentPage) { currentPage->addSelectionToDevice(udi); } #else Q_UNUSED(udi) #endif } void MainWindow::deleteSongs() { #ifdef ENABLE_DEVICES_SUPPORT if (!StdActions::self()->deleteSongsAction->isVisible()) { return; } if (currentPage) { currentPage->deleteSongs(); } #endif } void MainWindow::copyToDevice(const QString &from, const QString &to, const QList &songs) { #ifdef ENABLE_DEVICES_SUPPORT if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) { return; } ActionDialog *dlg=new ActionDialog(this); dlg->copy(from, to, songs); #else Q_UNUSED(from) Q_UNUSED(to) Q_UNUSED(songs) #endif } void MainWindow::deleteSongs(const QString &from, const QList &songs) { #ifdef ENABLE_DEVICES_SUPPORT if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) { return; } ActionDialog *dlg=new ActionDialog(this); dlg->remove(from, songs); #else Q_UNUSED(from) Q_UNUSED(songs) #endif } void MainWindow::replayGain() { #ifdef ENABLE_REPLAYGAIN_SUPPORT if (RgDialog::instanceCount() || !canShowDialog()) { return; } QList songs; if (currentPage) { songs=currentPage->selectedSongs(); } if (!songs.isEmpty()) { QString udi; #ifdef ENABLE_DEVICES_SUPPORT if (currentPage==devicesPage) { udi=devicesPage->activeFsDeviceUdi(); if (udi.isEmpty()) { return; } } #endif RgDialog *dlg=new RgDialog(this); dlg->show(songs, udi); } #endif } void MainWindow::setCover() { if (CoverDialog::instanceCount() || !canShowDialog()) { return; } Song song; if (currentPage) { song=currentPage->coverRequest(); } if (!song.isEmpty()) { CoverDialog *dlg=new CoverDialog(this); dlg->show(song); } } void MainWindow::updateNextTrack(int nextTrackId) { if (-1!=nextTrackId && MPDState_Stopped==MPDStatus::self()->state()) { nextTrackId=-1; // nextSongId is not accurate if we are stopped. } QString tt=StdActions::self()->nextTrackAction->property("tooltip").toString(); if (-1==nextTrackId && tt.isEmpty()) { StdActions::self()->nextTrackAction->setProperty("tooltip", StdActions::self()->nextTrackAction->toolTip()); } else if (-1==nextTrackId) { StdActions::self()->nextTrackAction->setToolTip(tt); StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId); } else if (nextTrackId!=StdActions::self()->nextTrackAction->property("trackid").toInt()) { Song s=PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->getRowById(nextTrackId)); if (!s.artist.isEmpty() && !s.title.isEmpty()) { tt+=QLatin1String("
    ")+s.artistSong()+QLatin1String(""); } else { nextTrackId=-1; } StdActions::self()->nextTrackAction->setToolTip(tt); StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId); } } void MainWindow::updateActionToolTips() { ActionCollection::get()->updateToolTips(); tabWidget->setToolTip(PAGE_PLAYQUEUE, showPlayQueueAction->toolTip()); tabWidget->setToolTip(PAGE_LIBRARY, libraryTabAction->toolTip()); tabWidget->setToolTip(PAGE_FOLDERS, foldersTabAction->toolTip()); tabWidget->setToolTip(PAGE_PLAYLISTS, playlistsTabAction->toolTip()); tabWidget->setToolTip(PAGE_ONLINE, onlineTabAction->toolTip()); #ifdef ENABLE_DEVICES_SUPPORT tabWidget->setToolTip(PAGE_DEVICES, devicesTabAction->toolTip()); #endif tabWidget->setToolTip(PAGE_SEARCH, searchTabAction->toolTip()); tabWidget->setToolTip(PAGE_CONTEXT, songInfoAction->toolTip()); } void MainWindow::startContextTimer() { if (!contextSwitchTime || current.isStandardStream()) { return; } if (!contextTimer) { contextTimer=new QTimer(this); contextTimer->setSingleShot(true); connect(contextTimer, SIGNAL(timeout()), this, SLOT(toggleContext())); } contextTimer->start(contextSwitchTime); } int MainWindow::calcMinHeight() { return tabWidget->style()&FancyTabWidget::Side && tabWidget->style()&FancyTabWidget::Large ? calcCollapsedSize()+(tabWidget->visibleCount()*tabWidget->tabSize().height()) : Utils::scaleForDpi(256); } int MainWindow::calcCollapsedSize() { return toolbar->height()+(menuBar() && menuBar()->isVisible() ? menuBar()->height() : 0); } void MainWindow::setCollapsedSize() { if (!expandInterfaceAction->isChecked()) { int w=width(); adjustSize(); collapsedSize=QSize(w, calcCollapsedSize()); resize(collapsedSize); setFixedHeight(collapsedSize.height()); } } void MainWindow::expandOrCollapse(bool saveCurrentSize) { if (!expandInterfaceAction->isChecked() && (isFullScreen() || isMaximized() || messageWidget->isVisible())) { expandInterfaceAction->setChecked(true); return; } static bool lastMax=false; bool showing=expandInterfaceAction->isChecked(); QPoint p(isVisible() ? pos() : QPoint()); if (!showing) { setMinimumHeight(0); lastMax=isMaximized(); if (saveCurrentSize) { expandedSize=size(); } } else { if (saveCurrentSize) { collapsedSize=size(); } setMinimumHeight(calcMinHeight()); setMaximumHeight(QWIDGETSIZE_MAX); } int prevWidth=size().width(); stack->setVisible(showing); if (!showing) { setWindowState(windowState()&~Qt::WindowMaximized); } adjustSize(); if (showing) { bool adjustWidth=size().width()!=expandedSize.width(); bool adjustHeight=size().height()!=expandedSize.height(); if (adjustWidth || adjustHeight) { resize(adjustWidth ? expandedSize.width() : size().width(), adjustHeight ? expandedSize.height() : size().height()); } if (lastMax) { showMaximized(); } } else { // Width also sometimes expands, so make sure this is no larger than it was before... collapsedSize=QSize(collapsedSize.isValid() ? collapsedSize.width() : (size().width()>prevWidth ? prevWidth : size().width()), calcCollapsedSize()); resize(collapsedSize); setFixedHeight(size().height()); } if (!p.isNull()) { move(p); } fullScreenAction->setEnabled(showing); songInfoButton->setVisible(songInfoAction->isCheckable() && showing); songInfoAction->setEnabled(showing); } void MainWindow::toggleContext() { if ( songInfoButton->isVisible()) { if ( (MPDState_Playing==MPDStatus::self()->state() && !songInfoAction->isChecked()) || (MPDState_Stopped==MPDStatus::self()->state() && songInfoAction->isChecked()) ) { songInfoAction->trigger(); } } else if (MPDState_Playing==MPDStatus::self()->state() && PAGE_CONTEXT!=tabWidget->currentIndex()) { int pp=prevPage; showTab(PAGE_CONTEXT); prevPage=pp; } else if (MPDState_Stopped==MPDStatus::self()->state() && PAGE_CONTEXT==tabWidget->currentIndex() && -1!=prevPage) { showTab(prevPage); } } void MainWindow::hideWindow() { lastPos=pos(); hide(); } void MainWindow::restoreWindow() { bool wasHidden=isHidden(); Utils::raiseWindow(this); if (wasHidden && !lastPos.isNull()) { move(lastPos); } } cantata-2.2.0/gui/mainwindow.h000066400000000000000000000260101316350454000162370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MAIN_WINDOW_H #define MAIN_WINDOW_H #include #include #include #include #include "ui_mainwindow.h" #include "models/playqueueproxymodel.h" #include "models/playqueuemodel.h" #include "mpd-interface/song.h" #include "mpd-interface/mpdstatus.h" #include "mpd-interface/mpdconnection.h" #include "config.h" class Action; class ActionCollection; class MainWindow; class Page; class LibraryPage; class FolderPage; class PlaylistsPage; class OnlineServicesPage; #ifdef ENABLE_DEVICES_SUPPORT class DevicesPage; #endif class SearchPage; class QAbstractItemView; #ifdef QT_QTDBUS_FOUND class Mpris; #endif class QTimer; class QPropertyAnimation; class QActionGroup; class QDateTime; class QMenu; class TrayItem; class HttpStream; class MPDStatus; struct MPDConnectionDetails; struct Output; #if defined Q_OS_WIN class ThumbnailToolBar; #endif #ifdef Q_OS_MAC class DockMenu; #endif // Dummy classes so that when class name is saved to the config file, we get a more meaningful name than QWidget!!! class PlayQueuePage : public QWidget { Q_OBJECT public: PlayQueuePage(QWidget *p) : QWidget(p) { } }; class ContextPage : public QWidget { Q_OBJECT public: ContextPage(QWidget *p) : QWidget(p) { } }; class MainWindow : public QMainWindow, private Ui::MainWindow { Q_OBJECT Q_PROPERTY( QStringList listActions READ listActions ) public: enum Pages { PAGE_PLAYQUEUE, PAGE_LIBRARY, PAGE_FOLDERS, PAGE_PLAYLISTS, PAGE_ONLINE, #ifdef ENABLE_DEVICES_SUPPORT PAGE_DEVICES, #endif PAGE_SEARCH, PAGE_CONTEXT }; MainWindow(QWidget *parent = 0); ~MainWindow(); int mpdVolume() const { return volumeSlider->value(); } QList selectedSongs() const; QStringList listActions() const; protected: void keyPressEvent(QKeyEvent *event); #if defined Q_OS_WIN void showEvent(QShowEvent *event); #endif void closeEvent(QCloseEvent *event); private: #ifdef Q_OS_MAC void addMenuAction(QMenu *menu, QAction *action); #endif void setupTrayIcon(); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void setDetails(const MPDConnectionDetails &det); void pause(bool p); void play(); void stop(bool afterCurrent=false); void terminating(); void getStatus(); void playListInfo(); void currentSong(); void setSeekId(qint32, quint32); void startPlayingSongId(qint32); void setVolume(int); void outputs(); void enableOutput(int id, bool); void setPriority(const QList &ids, quint8 priority, bool decreasePriority); void addSongsToPlaylist(const QString &name, const QStringList &files); void showPreferencesPage(const QString &page); void playNext(const QList &items, quint32 pos, quint32 size); public Q_SLOTS: void showError(const QString &message, bool showActions=false); void showInformation(const QString &message); void dynamicStatus(const QString &message); void setCollection(const QString &collection); void mpdConnectionName(const QString &name); void hideWindow(); void restoreWindow(); void load(const QStringList &urls) { PlayQueueModel::self()->load(urls); } void showAboutDialog(); void mpdConnectionStateChanged(bool connected); void playQueueItemsSelected(bool s); void showPreferencesDialog(const QString &page=QString()); void quit(); void updateSettings(); void toggleOutput(); void changeConnection(); void connectToMpd(); void connectToMpd(const MPDConnectionDetails &details); void streamUrl(const QString &u); void refreshDbPromp(); void showServerInfo(); void stopPlayback(); void stopAfterCurrentTrack(); void stopAfterTrack(); void playPauseTrack(); void setPosition(); void searchPlayQueue(); void realSearchPlayQueue(); void playQueueSearchActivated(bool a); void updatePlayQueue(const QList &songs, bool isComplete); void updateCurrentSong(Song song, bool wasEmpty=false); void scrollPlayQueue(bool wasEmpty=false); void updateStatus(); void playQueueItemActivated(const QModelIndex &); void clearPlayQueue(); void centerPlayQueue(); void removeFromPlayQueue() { PlayQueueModel::self()->remove(playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes())); } void replacePlayQueue() { appendToPlayQueue(MPDConnection::ReplaceAndplay); } void appendToPlayQueue() { appendToPlayQueue(MPDConnection::Append); } void appendToPlayQueueAndPlay() { appendToPlayQueue(MPDConnection::AppendAndPlay); } void addToPlayQueueAndPlay() { appendToPlayQueue(MPDConnection::AddAndPlay); } void insertIntoPlayQueue() { appendToPlayQueue(MPDConnection::AddAfterCurrent); } void addWithPriority(); void addToNewStoredPlaylist(); void addToExistingStoredPlaylist(const QString &name) { addToExistingStoredPlaylist(name, playQueue->hasFocus()); } void addToExistingStoredPlaylist(const QString &name, bool pq); void addStreamToPlayQueue(); void removeItems(); void checkMpdAccessibility(); void cropPlayQueue() { PlayQueueModel::self()->crop(playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes())); } void updatePlayQueueStats(int songs, quint32 time); void expandOrCollapse(bool saveCurrentSize=true); void showSongInfo(); void fullScreen(); void sidebarModeChanged(); void currentTabChanged(int index); void tabToggled(int index); void showPlayQueue() { showTab(PAGE_PLAYQUEUE); } void showLibraryTab() { showTab(PAGE_LIBRARY); } void showFoldersTab() { showTab(PAGE_FOLDERS); } void showPlaylistsTab() { showTab(PAGE_PLAYLISTS); } void showOnlineTab() { showTab(PAGE_ONLINE); } void showContextTab() { showTab(PAGE_CONTEXT); } void showDevicesTab() { #ifdef ENABLE_DEVICES_SUPPORT showTab(PAGE_DEVICES); #endif } void showSearchTab() { showTab(PAGE_SEARCH); } void toggleSplitterAutoHide(); void locateTracks(const QList &songs); void locateTrack() { locateTracks(playQueue->selectedSongs()); } void moveSelectionAfterCurrentSong(); void locateArtist(const QString &artist); void locateAlbum(const QString &artist, const QString &album); void editTags(); void organiseFiles(); void addToDevice(const QString &udi); void deleteSongs(); void copyToDevice(const QString &from, const QString &to, const QList &songs); void deleteSongs(const QString &from, const QList &songs); void replayGain(); void setCover(); void showPlayQueueSearch(); void showSearch(); void expandAll(); void collapseAll(); void checkMpdDir(); void outputsUpdated(const QList &outputs); void updateConnectionsMenu(); void controlConnectionsMenu(bool enable=true); void controlDynamicButton(); void setRating(); void triggerAction(const QString &name); private: bool canClose(); void expand(); bool canShowDialog(); void enableStopActions(bool enable); void updateStatus(MPDStatus * const status); void readSettings(); void appendToPlayQueue(int action, quint8 priority=0, bool decreasePriority=false); bool currentIsStream() const { return PlayQueueModel::self()->rowCount() && -1!=current.id && current.isStream(); } void updateWindowTitle(); void showTab(int page) { tabWidget->setCurrentIndex(page); } void updateNextTrack(int nextTrackId); void updateActionToolTips(); void startContextTimer(); int calcMinHeight(); int calcCollapsedSize(); void setCollapsedSize(); private Q_SLOTS: void init(); void toggleContext(); void initMpris(); private: int prevPage; MPDState lastState; qint32 lastSongId; PlayQueueProxyModel playQueueProxyModel; bool autoScrollPlayQueue; Action *prefAction; Action *refreshDbAction; Action *doDbRefreshAction; Action *connectAction; Action *connectionsAction; Action *outputsAction; QActionGroup *connectionsGroup; Action *stopAfterTrackAction; Action *addPlayQueueToStoredPlaylistAction; Action *clearPlayQueueAction; Action *centerPlayQueueAction; Action *expandInterfaceAction; Action *cropPlayQueueAction; Action *addStreamToPlayQueueAction; Action *randomPlayQueueAction; Action *repeatPlayQueueAction; Action *singlePlayQueueAction; Action *consumePlayQueueAction; Action *searchPlayQueueAction; #ifdef ENABLE_HTTP_STREAM_PLAYBACK HttpStream *httpStream; Action *streamPlayAction; #endif Action *songInfoAction; Action *fullScreenAction; Action *quitAction; Action *restoreAction; Action *locateTrackAction; Action *playNextAction; #ifdef TAGLIB_FOUND Action *editPlayQueueTagsAction; #endif Action *searchTabAction; Action *expandAllAction; Action *collapseAllAction; Action *serverInfoAction; Action *cancelAction; Action *ratingAction; TrayItem *trayItem; QPoint lastPos; QSize expandedSize; QSize collapsedSize; Song current; Page *currentPage; Action *showPlayQueueAction; QWidget *playQueuePage; Action *libraryTabAction; LibraryPage *libraryPage; Action *foldersTabAction; FolderPage *folderPage; Action *playlistsTabAction; PlaylistsPage *playlistsPage; Action *onlineTabAction; OnlineServicesPage *onlinePage; QWidget *contextPage; #ifdef ENABLE_DEVICES_SUPPORT Action *devicesTabAction; Action *copyToDeviceAction; DevicesPage *devicesPage; #endif SearchPage *searchPage; #ifdef QT_QTDBUS_FOUND Mpris *mpris; #endif QTimer *statusTimer; QTimer *playQueueSearchTimer; #if !defined Q_OS_WIN && !defined Q_OS_MAC QTimer *mpdAccessibilityTimer; #endif QTimer *contextTimer; int contextSwitchTime; enum { CS_Init, CS_Connected, CS_Disconnected } connectedState; bool stopAfterCurrent; #if defined Q_OS_WIN ThumbnailToolBar *thumbnailTooolbar; #endif #ifdef Q_OS_MAC DockMenu *dockMenu; #endif friend class TrayItem; }; #endif cantata-2.2.0/gui/mainwindow.ui000066400000000000000000000255571316350454000164440ustar00rootroot00000000000000 MainWindow 0 0 600 347 0 0 0 0 Qt::Horizontal QSizePolicy::Fixed 4 4 Qt::Horizontal QSizePolicy::Fixed 4 4 0 true 0 0 Qt::Horizontal 0 0 0 0 0 32 32 2 0 0 0 1 0 75 true [Dynamic] Qt::Horizontal 6 2 Exit Full Screen true false true false true false true true SqueezedTextLabel QLabel
    support/squeezedtextlabel.h
    NowPlayingWidget QWidget
    widgets/nowplayingwidget.h
    PlayQueueView QTreeView
    widgets/playqueueview.h
    FancyTabWidget QTabWidget
    support/fancytabwidget.h
    1
    MessageWidget QFrame
    support/messagewidget.h
    1
    AutohidingSplitter QSplitter
    widgets/autohidingsplitter.h
    1
    MenuButton QToolButton
    widgets/menubutton.h
    ToolButton QToolButton
    widgets/toolbutton.h
    ContextWidget QWidget
    context/contextwidget.h
    SearchWidget QLineEdit
    widgets/searchwidget.h
    SpacerWidget QWidget
    widgets/spacerwidget.h
    SizeWidget QWidget
    widgets/sizewidget.h
    SizeGrip QWidget
    widgets/sizegrip.h
    CoverWidget QLabel
    widgets/coverwidget.h
    VolumeSlider QSlider
    widgets/volumeslider.h
    ScrobblingStatus QToolButton
    scrobbling/scrobblingstatus.h
    ScrobblingLove QToolButton
    scrobbling/scrobblinglove.h
    UrlLabel QLabel
    support/urllabel.h
    cantata-2.2.0/gui/mediakeys.cpp000066400000000000000000000070701316350454000163760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mediakeys.h" #ifdef QT_QTDBUS_FOUND #include "dbus/gnomemediakeys.h" #endif #ifdef CANTATA_USE_QXT_MEDIAKEYS #include "qxtmediakeys.h" #endif #include "multimediakeysinterface.h" #include "stdactions.h" #include "support/globalstatic.h" #include static bool debugIsEnabled=false; #define DBUG if (debugIsEnabled) qWarning() << "MediaKeys" << __FUNCTION__ void MediaKeys::enableDebug() { debugIsEnabled=true; } GLOBAL_STATIC(MediaKeys, instance) MediaKeys::MediaKeys() { #ifdef QT_QTDBUS_FOUND gnome=0; #endif #ifdef CANTATA_USE_QXT_MEDIAKEYS qxt=0; #endif } MediaKeys::~MediaKeys() { #ifdef CANTATA_USE_QXT_MEDIAKEYS if (qxt) { delete qxt; } #endif #ifdef QT_QTDBUS_FOUND if (gnome) { delete gnome; } #endif } void MediaKeys::start() { #ifdef QT_QTDBUS_FOUND gnome=new GnomeMediaKeys(0); if (activate(gnome)) { DBUG << "Using Gnome"; return; } DBUG << "Gnome failed"; gnome->deleteLater(); gnome=0; #endif #ifdef CANTATA_USE_QXT_MEDIAKEYS qxt=new QxtMediaKeys(0); if (activate(qxt)) { DBUG << "Using Qxt"; return; } DBUG << "Qxt failed"; qxt->deleteLater(); qxt=0; #endif DBUG << "None"; } void MediaKeys::stop() { #ifdef QT_QTDBUS_FOUND if (gnome) { deactivate(gnome); gnome->deleteLater(); gnome=0; } #endif #ifdef CANTATA_USE_QXT_MEDIAKEYS if (qxt) { deactivate(qxt); qxt->deleteLater(); qxt=0; } #endif } bool MediaKeys::activate(MultiMediaKeysInterface *iface) { if (!iface) { return false; } if (iface->activate()) { QObject::connect(iface, SIGNAL(playPause()), StdActions::self()->playPauseTrackAction, SIGNAL(triggered())); QObject::connect(iface, SIGNAL(stop()), StdActions::self()->stopPlaybackAction, SIGNAL(triggered())); QObject::connect(iface, SIGNAL(next()), StdActions::self()->nextTrackAction, SIGNAL(triggered())); QObject::connect(iface, SIGNAL(previous()), StdActions::self()->prevTrackAction, SIGNAL(triggered())); return true; } return false; } void MediaKeys::deactivate(MultiMediaKeysInterface *iface) { if (!iface) { return; } QObject::disconnect(iface, SIGNAL(playPause()), StdActions::self()->playPauseTrackAction, SIGNAL(triggered())); QObject::disconnect(iface, SIGNAL(stop()), StdActions::self()->stopPlaybackAction, SIGNAL(triggered())); QObject::disconnect(iface, SIGNAL(next()), StdActions::self()->nextTrackAction, SIGNAL(triggered())); QObject::disconnect(iface, SIGNAL(previous()), StdActions::self()->prevTrackAction, SIGNAL(triggered())); iface->deactivate(); } cantata-2.2.0/gui/mediakeys.h000066400000000000000000000027401316350454000160420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MEDIA_KEYS_H #define MEDIA_KEYS_H #include #include "config.h" class GnomeMediaKeys; class QxtMediaKeys; class MultiMediaKeysInterface; #if defined Q_OS_WIN #define CANTATA_USE_QXT_MEDIAKEYS #endif class MediaKeys { public: static void enableDebug(); static MediaKeys * self(); MediaKeys(); ~MediaKeys(); void start(); void stop(); private: bool activate(MultiMediaKeysInterface *iface); void deactivate(MultiMediaKeysInterface *iface); private: #ifdef QT_QTDBUS_FOUND GnomeMediaKeys *gnome; #endif #ifdef CANTATA_USE_QXT_MEDIAKEYS QxtMediaKeys *qxt; #endif }; #endif cantata-2.2.0/gui/multimediakeysinterface.h000066400000000000000000000023711316350454000207760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MULTI_MEDIA_KEYS_INTERFACE_H #define MULTI_MEDIA_KEYS_INTERFACE_H #include class MultiMediaKeysInterface : public QObject { Q_OBJECT public: MultiMediaKeysInterface(QObject *p) : QObject(p) { } ~MultiMediaKeysInterface() { } virtual bool activate()=0; virtual void deactivate()=0; Q_SIGNALS: void playPause(); void stop(); void next(); void previous(); }; #endif cantata-2.2.0/gui/page.h000066400000000000000000000032631316350454000150040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PAGE_H #define PAGE_H #include "mpd-interface/song.h" #include "mpd-interface/mpdconnection.h" class Page { public: Page() { } virtual ~Page() { } virtual Song coverRequest() const { return Song(); } virtual QList selectedSongs(bool allowPlaylists=false) const { Q_UNUSED(allowPlaylists) return QList(); } virtual void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false) { Q_UNUSED(name) Q_UNUSED(action) Q_UNUSED(priorty) Q_UNUSED(decreasePriority) } #ifdef ENABLE_DEVICES_SUPPORT virtual void addSelectionToDevice(const QString &udi) { Q_UNUSED(udi) } virtual void deleteSongs() { } #endif virtual void focusSearch() { } virtual void removeItems() { } virtual void controlActions() { } }; #endif cantata-2.2.0/gui/playbacksettings.cpp000066400000000000000000000171641316350454000177770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playbacksettings.h" #include "settings.h" #include "support/utils.h" #include "mpd-interface/mpdconnection.h" #include "support/icon.h" #include "widgets/basicitemdelegate.h" #include "support/messagebox.h" #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; PlaybackSettings::PlaybackSettings(QWidget *p) : QWidget(p) { setupUi(this); stopFadeDuration->setRange(MPDConnection::MinFade, MPDConnection::MaxFade); stopFadeDuration->setSingleStep(100); outputsView->setItemDelegate(new BasicItemDelegate(outputsView)); replayGain->addItem(tr("None"), QVariant("off")); replayGain->addItem(tr("Track"), QVariant("track")); replayGain->addItem(tr("Album"), QVariant("album")); replayGain->addItem(tr("Auto"), QVariant("auto")); connect(MPDConnection::self(), SIGNAL(replayGain(const QString &)), this, SLOT(replayGainSetting(const QString &))); connect(MPDConnection::self(), SIGNAL(outputsUpdated(const QList &)), this, SLOT(updateOutputs(const QList &))); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), this, SLOT(mpdConnectionStateChanged(bool))); connect(this, SIGNAL(enableOutput(int, bool)), MPDConnection::self(), SLOT(enableOutput(int, bool))); connect(this, SIGNAL(outputs()), MPDConnection::self(), SLOT(outputs())); connect(this, SIGNAL(setReplayGain(const QString &)), MPDConnection::self(), SLOT(setReplayGain(const QString &))); connect(this, SIGNAL(setCrossFade(int)), MPDConnection::self(), SLOT(setCrossFade(int))); connect(this, SIGNAL(getReplayGain()), MPDConnection::self(), SLOT(getReplayGain())); connect(aboutReplayGain, SIGNAL(leftClickedUrl()), SLOT(showAboutReplayGain())); int iconSize=Icon::dlgIconSize(); messageIcon->setFixedSize(iconSize, iconSize); mpdConnectionStateChanged(MPDConnection::self()->isConnected()); #if defined Q_OS_WIN || (defined Q_OS_MAC && !defined IOKIT_FOUND) REMOVE(inhibitSuspend) #endif outputsView->setVisible(outputsView->count()>1); outputsViewLabel->setVisible(outputsView->count()>1); #ifdef Q_OS_MAC expandingSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); #endif } void PlaybackSettings::load() { stopOnExit->setChecked(Settings::self()->stopOnExit()); stopFadeDuration->setValue(Settings::self()->stopFadeDuration()); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) inhibitSuspend->setChecked(Settings::self()->inhibitSuspend()); #endif crossfading->setValue(MPDStatus::self()->crossFade()); if (MPDConnection::self()->isConnected()) { emit getReplayGain(); emit outputs(); } } void PlaybackSettings::save() { Settings::self()->saveStopOnExit(stopOnExit->isChecked()); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) Settings::self()->saveInhibitSuspend(inhibitSuspend->isChecked()); #endif if (MPDConnection::self()->isConnected()) { int crossFade=crossfading->value(); if (crossFade!=MPDStatus::self()->crossFade()) { emit setCrossFade(crossFade); } QString rg=replayGain->itemData(replayGain->currentIndex()).toString(); if (rgSetting!=rg) { rgSetting=rg; emit setReplayGain(rg); } if (outputsView->isVisible()) { for (int i=0; icount(); ++i) { QListWidgetItem *item=outputsView->item(i); bool isEnabled=Qt::Checked==item->checkState(); int id=item->data(Qt::UserRole).toInt(); if (isEnabled && !enabledOutputs.contains(id)) { enabledOutputs.insert(id); emit enableOutput(id, isEnabled); } else if (!isEnabled && enabledOutputs.contains(id)) { enabledOutputs.remove(id); emit enableOutput(id, isEnabled); } } } } } void PlaybackSettings::replayGainSetting(const QString &rg) { rgSetting=rg; replayGain->setCurrentIndex(0); for(int i=0; icount(); ++i) { if (replayGain->itemData(i).toString()==rg){ replayGain->setCurrentIndex(i); break; } } } void PlaybackSettings::updateOutputs(const QList &outputs) { outputsView->clear(); enabledOutputs.clear(); foreach(const Output &output, outputs) { QListWidgetItem *item=new QListWidgetItem(output.name, outputsView); item->setCheckState(output.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole, output.id); if (output.enabled) { enabledOutputs.insert(output.id); } } outputsView->setVisible(outputsView->count()>1); outputsViewLabel->setVisible(outputsView->count()>1); } void PlaybackSettings::mpdConnectionStateChanged(bool c) { bool rgSupported=c && MPDConnection::self()->replaygainSupported(); outputsView->setEnabled(c); outputsViewLabel->setEnabled(c); crossfading->setEnabled(c); crossfadingLabel->setEnabled(c); replayGainLabel->setEnabled(rgSupported); replayGain->setEnabled(rgSupported); messageIcon->setPixmap(Icon(c ? "dialog-information" : "dialog-warning").pixmap(messageIcon->minimumSize())); if (c) { messageLabel->setText(tr("Connected to %1
    The entries below apply to the currently connected MPD collection.
    ") .arg(MPDConnection::self()->getDetails().description())); } else { messageLabel->setText(tr("Not Connected!
    The entries below cannot be modified, as Cantata is not connected to MPD.
    ")); outputsView->clear(); } } void PlaybackSettings::showAboutReplayGain() { MessageBox::information(this, tr("Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer " "audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a " "growing number of players." "

    The following ReplayGain settings may be used:
      " "
    • None - No ReplayGain is applied.
    • " "
    • Track - Volume will be adjusted using the track's ReplayGain tags.
    • " "
    • Album - Volume will be adjusted using the albums's ReplayGain tags.
    • " "
    • Auto - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.
    • " "
    ")); } cantata-2.2.0/gui/playbacksettings.h000066400000000000000000000033361316350454000174400ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYBACKSETTINGS_H #define PLAYBACKSETTINGS_H #include "ui_playbacksettings.h" #include "mpd-interface/output.h" #include #include class PlaybackSettings : public QWidget, private Ui::PlaybackSettings { Q_OBJECT public: PlaybackSettings(QWidget *p); virtual ~PlaybackSettings() { } void load(); void save(); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void getReplayGain(); void setReplayGain(const QString &); void setCrossFade(int secs); void outputs(); void enableOutput(int id, bool); private Q_SLOTS: void replayGainSetting(const QString &rg); void updateOutputs(const QList &outputs); void mpdConnectionStateChanged(bool c); void showAboutReplayGain(); private: QSet enabledOutputs; QString rgSetting; }; #endif cantata-2.2.0/gui/playbacksettings.ui000066400000000000000000000170351316350454000176270ustar00rootroot00000000000000 PlaybackSettings 0 0 1487 495 0 0 0 0 Playback QFormLayout::ExpandingFieldsGrow Fa&deout on stop: stopFadeDuration None ms Stop playback on exit Inhibit suspend whilst playing If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) 0 0 Output <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> true Qt::NoTextInteraction QFormLayout::ExpandingFieldsGrow &Crossfade between tracks: crossfading None s 20 Replay &gain: replayGain 0 0 0 0 Qt::Horizontal QSizePolicy::MinimumExpanding 0 0 About replay gain Use the checkboxes below to control the active outputs. true 0 0 Qt::Vertical 20 0 BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    UrlLabel QWidget
    support/urllabel.h
    cantata-2.2.0/gui/preferencesdialog.cpp000066400000000000000000000122331316350454000201010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "preferencesdialog.h" #include "settings.h" #include "widgets/icons.h" #include "interfacesettings.h" #include "serversettings.h" #include "playbacksettings.h" #include "filesettings.h" #include "context/contextsettings.h" #include "cachesettings.h" #include "customactionssettings.h" #include "mpd-interface/mpdconnection.h" #ifdef ENABLE_PROXY_CONFIG #include "network/proxysettings.h" #endif #include "shortcutssettingspage.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "devices/audiocdsettings.h" #endif #include "shortcutssettingspage.h" #include "scrobbling/scrobblingsettings.h" #include #include static int iCount=0; int PreferencesDialog::instanceCount() { return iCount; } PreferencesDialog::PreferencesDialog(QWidget *parent) : ConfigDialog(parent, "PreferencesDialog") { iCount++; server = new ServerSettings(this); playback = new PlaybackSettings(this); files = new FileSettings(this); interface = new InterfaceSettings(this); context = new ContextSettings(this); cache = new CacheSettings(this); scrobbling = new ScrobblingSettings(this); custom = new CustomActionsSettings(this); server->load(); playback->load(); files->load(); interface->load(); context->load(); scrobbling->load(); custom->load(); addPage(QLatin1String("collection"), server, tr("Collection"), Icon("audio-x-generic"), tr("Collection Settings")); addPage(QLatin1String("playback"), playback, tr("Playback"), Icons::self()->speakerIcon, tr("Playback Settings")); addPage(QLatin1String("files"), files, tr("Downloaded Files"), Icons::self()->filesIcon, tr("Downloaded Files Settings")); addPage(QLatin1String("interface"), interface, tr("Interface"), Icon("preferences-other"), tr("Interface Settings")); addPage(QLatin1String("info"), context, tr("Info"), Icons::self()->contextIcon, tr("Info View Settings")); addPage(QLatin1String("scrobbling"), scrobbling, tr("Scrobbling"), Icons::self()->lastFmIcon, tr("Scrobbling Settings")); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND audiocd = new AudioCdSettings(0); audiocd->load(); addPage(QLatin1String("cd"), audiocd, tr("Audio CD"), Icons::self()->albumIcon(32), tr("Audio CD Settings")); #endif #ifdef ENABLE_PROXY_CONFIG proxy = new ProxySettings(0); proxy->load(); addPage(QLatin1String("proxy"), proxy, tr("Proxy"), Icon("preferences-system-network"), tr("Proxy Settings")); #endif shortcuts = new ShortcutsSettingsPage(0); addPage(QLatin1String("shortcuts"), shortcuts, tr("Shortcuts"), Icon(QStringList() << "preferences-desktop-keyboard" << "keyboard"), tr("Keyboard Shortcut Settings")); shortcuts->load(); addPage(QLatin1String("cache"), cache, tr("Cache"), Icon(QStringList() << "folder-temp" << "folder"), tr("Cached Items")); addPage(QLatin1String("custom"), custom, tr("Custom Actions"), Icon(QStringList() << "fork" << "gtk-execute"), tr("Custom Actions")); #ifdef Q_OS_MAC setCaption(tr("Cantata Preferences")); #else setCaption(tr("Configure")); #endif setAttribute(Qt::WA_DeleteOnClose); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setCurrentPage(QLatin1String("collection")); } PreferencesDialog::~PreferencesDialog() { iCount--; } void PreferencesDialog::showPage(const QString &page) { QStringList parts=page.split(QLatin1Char(':')); if (setCurrentPage(parts.at(0))) { if (parts.count()>1) { QWidget *cur=getPage(parts.at(0)); if (qobject_cast(cur)) { static_cast(cur)->showPage(parts.at(1)); } } } Utils::raiseWindow(this); } void PreferencesDialog::writeSettings() { // *Must* save server settings first, so that MPD settings go to the correct instance! server->save(); playback->save(); files->save(); interface->save(); #ifdef ENABLE_PROXY_CONFIG proxy->save(); #endif shortcuts->save(); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND audiocd->save(); #endif context->save(); scrobbling->save(); custom->save(); Settings::self()->save(); emit settingsSaved(); } void PreferencesDialog::save() { writeSettings(); } void PreferencesDialog::cancel() { server->cancel(); } cantata-2.2.0/gui/preferencesdialog.h000066400000000000000000000042611316350454000175500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PREFERENCES_DIALOG_H #define PREFERENCES_DIALOG_H #include "config.h" #include "support/configdialog.h" class ProxySettings; class ShortcutsSettingsPage; class ServerSettings; class PlaybackSettings; class FileSettings; class InterfaceSettings; class ContextSettings; struct MPDConnectionDetails; class CacheSettings; #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND class AudioCdSettings; #endif class QStringList; #ifdef ENABLE_PROXY_CONFIG class ProxySettings; #endif class ScrobblingSettings; class CustomActionsSettings; class PreferencesDialog : public ConfigDialog { Q_OBJECT public: static int instanceCount(); PreferencesDialog(QWidget *parent); virtual ~PreferencesDialog(); private: void save(); void cancel(); public Q_SLOTS: void showPage(const QString &page); private Q_SLOTS: void writeSettings(); Q_SIGNALS: void settingsSaved(); private: ServerSettings *server; PlaybackSettings *playback; FileSettings *files; InterfaceSettings *interface; ContextSettings *context; #ifdef ENABLE_PROXY_CONFIG ProxySettings *proxy; #endif ShortcutsSettingsPage *shortcuts; CacheSettings *cache; #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND AudioCdSettings *audiocd; #endif ScrobblingSettings *scrobbling; CustomActionsSettings *custom; }; #endif cantata-2.2.0/gui/qxtmediakeys.cpp000066400000000000000000000040071316350454000171300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "qxtmediakeys.h" #include "qxt/qxtglobalshortcut.h" QxtMediaKeys::QxtMediaKeys(QObject *p) : MultiMediaKeysInterface(p) { } bool QxtMediaKeys::activate() { createShortcuts(); return true; // Hmm... How to detect if this failed? } void QxtMediaKeys::deactivate() { clear(); } void QxtMediaKeys::createShortcuts() { if (!shortcuts.isEmpty()) { return; } QxtGlobalShortcut *shortcut = new QxtGlobalShortcut(Qt::Key_MediaPlay, this); connect(shortcut, SIGNAL(activated()), this, SIGNAL(playPause())); shortcuts.append(shortcut); shortcut = new QxtGlobalShortcut(Qt::Key_MediaStop, this); connect(shortcut, SIGNAL(activated()), this, SIGNAL(stop())); shortcuts.append(shortcut); shortcut = new QxtGlobalShortcut(Qt::Key_MediaNext, this); connect(shortcut, SIGNAL(activated()), this, SIGNAL(next())); shortcuts.append(shortcut); shortcut = new QxtGlobalShortcut(Qt::Key_MediaPrevious, this); connect(shortcut, SIGNAL(activated()), this, SIGNAL(previous())); shortcuts.append(shortcut); } void QxtMediaKeys::clear() { if (!shortcuts.isEmpty()) { qDeleteAll(shortcuts); shortcuts.clear(); } } cantata-2.2.0/gui/qxtmediakeys.h000066400000000000000000000024001316350454000165700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef QXT_MEDIA_KEYS_H #define QXT_MEDIA_KEYS_H #include "multimediakeysinterface.h" #include class QxtGlobalShortcut; class QxtMediaKeys : public MultiMediaKeysInterface { public: QxtMediaKeys(QObject *p); virtual ~QxtMediaKeys() { clear(); } bool activate(); void deactivate(); private: void createShortcuts(); void clear(); private: QList shortcuts; }; #endif cantata-2.2.0/gui/searchpage.cpp000066400000000000000000000204011316350454000165160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "searchpage.h" #include "mpd-interface/mpdconnection.h" #include "settings.h" #include "stdactions.h" #include "customactions.h" #include "support/utils.h" #include "support/icon.h" #include "widgets/tableview.h" #include "widgets/menubutton.h" #include "widgets/icons.h" class SearchTableView : public TableView { public: SearchTableView(QWidget *p) : TableView(QLatin1String("search"), p) { setUseSimpleDelegate(); setIndentation(0); } virtual ~SearchTableView() { } }; SearchPage::SearchPage(QWidget *p) : SinglePageWidget(p) , state(-1) , model(this) , proxy(this) { statsLabel=new SqueezedTextLabel(this); locateAction=new Action(Icons::self()->searchIcon, tr("Locate In Library"), this); view->allowTableView(new SearchTableView(view)); connect(&model, SIGNAL(searching()), view, SLOT(showSpinner())); connect(&model, SIGNAL(searched()), view, SLOT(hideSpinner())); connect(&model, SIGNAL(statsUpdated(int, quint32)), this, SLOT(statsUpdated(int, quint32))); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), this, SLOT(setSearchCategories())); connect(locateAction, SIGNAL(triggered()), SLOT(locateSongs())); proxy.setSourceModel(&model); view->setModel(&proxy); view->setPermanentSearch(); setSearchCategories(); view->setSearchCategory(Settings::self()->searchCategory()); statsUpdated(0, 0); view->setMode(ItemView::Mode_List); Configuration config(metaObject()->className()); view->load(config); MenuButton *menu=new MenuButton(this); menu->addActions(createViewActions(QList() << ItemView::Mode_List << ItemView::Mode_Table)); statsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu << statsLabel); view->addAction(StdActions::self()->addToStoredPlaylistAction); view->addAction(CustomActions::self()); #ifdef TAGLIB_FOUND #ifdef ENABLE_DEVICES_SUPPORT view->addAction(StdActions::self()->copyToDeviceAction); #endif #endif // TAGLIB_FOUND view->addAction(locateAction); } SearchPage::~SearchPage() { Configuration config(metaObject()->className()); view->save(config); } void SearchPage::refresh() { model.refresh(); } void SearchPage::clear() { model.clear(); } void SearchPage::setView(int mode) { view->setMode((ItemView::Mode)mode); model.setMultiColumn(ItemView::Mode_Table==mode); } void SearchPage::showEvent(QShowEvent *e) { SinglePageWidget::showEvent(e); view->focusSearch(); } QStringList SearchPage::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } QModelIndexList mapped; foreach (const QModelIndex &idx, selected) { mapped.append(proxy.mapToSource(idx)); } return model.filenames(mapped, allowPlaylists); } QList SearchPage::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } QModelIndexList mapped; foreach (const QModelIndex &idx, selected) { mapped.append(proxy.mapToSource(idx)); } return model.songs(mapped, allowPlaylists); } #ifdef ENABLE_DEVICES_SUPPORT void SearchPage::addSelectionToDevice(const QString &udi) { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit addToDevice(QString(), udi, songs); view->clearSelection(); } } #endif void SearchPage::doSearch() { QString text=view->searchText().trimmed(); if (text.isEmpty()) { model.clear(); } else { model.search(view->searchCategory(), text); } } void SearchPage::itemDoubleClicked(const QModelIndex &) { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; //doubleclick should only have one selected item } addSelectionToPlaylist(); } void SearchPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool enable=selected.count()>0; StdActions::self()->enableAddToPlayQueue(enable); locateAction->setEnabled(enable); } void SearchPage::setSearchCategory(const QString &cat) { view->setSearchCategory(cat); } void SearchPage::setSearchCategories() { int newState=(MPDConnection::self()->composerTagSupported() ? State_ComposerSupported : 0)| (MPDConnection::self()->commentTagSupported() ? State_CommmentSupported : 0)| (MPDConnection::self()->performerTagSupported() ? State_PerformerSupported : 0)| (MPDConnection::self()->modifiedFindSupported() ? State_ModifiedSupported : 0)| (MPDConnection::self()->originalDateTagSupported() ? State_OrigDateSupported : 0); if (state==newState) { // No changes to be made! return; } state=newState; QList categories; categories << SearchWidget::Category(tr("Artist:"), QLatin1String("artist")); if (state&State_ComposerSupported) { categories << SearchWidget::Category(tr("Composer:"), QLatin1String("composer")); } if (state&State_PerformerSupported) { categories << SearchWidget::Category(tr("Performer:"), QLatin1String("performer")); } categories << SearchWidget::Category(tr("Album:"), QLatin1String("album")) << SearchWidget::Category(tr("Title:"), QLatin1String("title")) << SearchWidget::Category(tr("Genre:"), QLatin1String("genre")); if (state&State_CommmentSupported) { categories << SearchWidget::Category(tr("Comment:"), QLatin1String("comment")); } categories << SearchWidget::Category(tr("Date:"), QLatin1String("date"), tr("Find songs be searching the 'Date' tag.

    Usually just entering the year should suffice.")); if (state&State_OrigDateSupported) { categories << SearchWidget::Category(tr("Original Date:"), QLatin1String("originaldate"), tr("Find songs be searching the 'Original Date' tag.

    Usually just entering the year should suffice.")); } if (state&State_ModifiedSupported) { categories << SearchWidget::Category(tr("Modified:"), MPDConnection::constModifiedSince, tr("Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.

    " "Or enter a number of days to find files that were modified in the previous number of days.")); } categories << SearchWidget::Category(tr("File:"), QLatin1String("file")) << SearchWidget::Category(tr("Any:"), QLatin1String("any")); view->setSearchCategories(categories); } void SearchPage::statsUpdated(int songs, quint32 time) { statsLabel->setText(0==songs ? tr("No tracks found.") : tr("%n Tracks (%1)", "", songs).arg(Utils::formatDuration(time))); } void SearchPage::locateSongs() { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit locate(songs); } } cantata-2.2.0/gui/searchpage.h000066400000000000000000000043731316350454000161750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SEARCHPAGE_H #define SEARCHPAGE_H #include "widgets/singlepagewidget.h" #include "models/mpdsearchmodel.h" #include "models/searchproxymodel.h" class Action; class SqueezedTextLabel; class SearchPage : public SinglePageWidget { Q_OBJECT public: SearchPage(QWidget *p); virtual ~SearchPage(); void refresh(); void clear(); void setView(int mode); void showEvent(QShowEvent *e); QStringList selectedFiles(bool allowPlaylists=false) const; QList selectedSongs(bool allowPlaylists=false) const; #ifdef ENABLE_DEVICES_SUPPORT void addSelectionToDevice(const QString &udi); #endif void setSearchCategory(const QString &cat); Q_SIGNALS: void addToDevice(const QString &from, const QString &to, const QList &songs); void locate(const QList &songs); public Q_SLOTS: void itemDoubleClicked(const QModelIndex &); void setSearchCategories(); void statsUpdated(int songs, quint32 time); void locateSongs(); private: void doSearch(); void controlActions(); private: enum State { State_ComposerSupported = 0x01, State_CommmentSupported = 0x02, State_PerformerSupported = 0x04, State_ModifiedSupported = 0x08, State_OrigDateSupported = 0x10 }; int state; MpdSearchModel model; SearchProxyModel proxy; Action *locateAction; SqueezedTextLabel *statsLabel; }; #endif cantata-2.2.0/gui/serversettings.cpp000066400000000000000000000337441316350454000175210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "serversettings.h" #include "settings.h" #include "support/inputdialog.h" #include "support/messagebox.h" #include "widgets/icons.h" #include "db/mpdlibrarydb.h" #ifdef ENABLE_SIMPLE_MPD_SUPPORT #include "mpd-interface/mpduser.h" #endif #include "support/utils.h" #include #include #include #include #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; class CoverNameValidator : public QValidator { public: CoverNameValidator(QObject *parent) : QValidator(parent) { } State validate(QString &input, int &) const { for (int i=0; iappendText(QLatin1String(" ")+tr("This folder will also be used to locate music files " "for tag-editing, replay gain, and transferring to (and from) devices.")+QLatin1String("")); #else musicFolderNoteLabel->appendText(QLatin1String(" ")+tr("This folder will also be used to locate music files " "for tag-editing, replay gain, etc.")+QLatin1String("")); #endif connect(combo, SIGNAL(activated(int)), SLOT(showDetails(int))); connect(removeButton, SIGNAL(clicked(bool)), SLOT(remove())); connect(addButton, SIGNAL(clicked(bool)), SLOT(add())); connect(name, SIGNAL(textChanged(QString)), SLOT(nameChanged())); connect(basicDir, SIGNAL(textChanged(QString)), SLOT(basicDirChanged())); addButton->setIcon(Icons::self()->addIcon); removeButton->setIcon(Icons::self()->minusIcon); addButton->setAutoRaise(true); removeButton->setAutoRaise(true); #if defined Q_OS_WIN hostLabel->setText(tr("Host:")); #endif basicCoverName->setToolTip(coverName->toolTip()); basicCoverNameLabel->setToolTip(coverName->toolTip()); coverNameLabel->setToolTip(coverName->toolTip()); coverName->setValidator(new CoverNameValidator(this)); basicCoverName->setValidator(new CoverNameValidator(this)); #ifdef ENABLE_SIMPLE_MPD_SUPPORT name->setValidator(new CollectionNameValidator(this)); #endif #ifndef ENABLE_HTTP_STREAM_PLAYBACK REMOVE(streamUrlLabel) REMOVE(streamUrl) REMOVE(streamUrlNoteLabel) #endif #ifdef Q_OS_MAC expandingSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); #endif } void ServerSettings::load() { QList all=Settings::self()->allConnections(); QString currentCon=Settings::self()->currentConnection(); qSort(all); combo->clear(); int idx=0; haveBasicCollection=false; foreach (MPDConnectionDetails d, all) { combo->addItem(d.getName(), d.name); if (d.name==currentCon) { prevIndex=idx; } idx++; #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (d.name==MPDUser::constName) { d.dir=MPDUser::self()->details().dir; haveBasicCollection=true; prevBasic=d; } #endif DeviceOptions opts; opts.load(MPDConnectionDetails::configGroupName(d.name), true); collections.append(Collection(d, opts)); } combo->setCurrentIndex(prevIndex); setDetails(collections.at(prevIndex).details); removeButton->setEnabled(combo->count()>1); } void ServerSettings::save() { if (combo->count()<1) { return; } Collection current=collections.at(combo->currentIndex()); current.details=getDetails(); collections.replace(combo->currentIndex(), current); QList existingInConfig=Settings::self()->allConnections(); QList toAdd; foreach (const Collection &c, collections) { bool found=false; for (int i=0; iremoveConnectionDetails(c.name); } foreach (const Collection &c, toAdd) { Settings::self()->saveConnectionDetails(c.details); c.namingOpts.save(MPDConnectionDetails::configGroupName(c.details.name), true); } #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (!haveBasicCollection && MPDUser::self()->isSupported()) { MPDUser::self()->cleanup(); } if (current.details.name==MPDUser::constName) { MPDUser::self()->setDetails(current.details); } #endif Settings::self()->saveCurrentConnection(current.details.name); MpdLibraryDb::removeUnusedDbs(); } void ServerSettings::cancel() { #ifdef ENABLE_SIMPLE_MPD_SUPPORT // If we are canceling any changes, then we need to restore user settings... if (prevBasic.details.name==MPDUser::constName) { MPDUser::self()->setDetails(prevBasic.details); } #endif } void ServerSettings::showDetails(int index) { if (-1!=prevIndex && index!=prevIndex) { MPDConnectionDetails details=getDetails(); if (details.name.isEmpty()) { details.name=generateName(prevIndex); } collections.replace(prevIndex, details); #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (details.name!=MPDUser::constName) #endif { combo->setItemText(prevIndex, details.name); } } setDetails(collections.at(index).details); prevIndex=index; } void ServerSettings::add() { #ifdef ENABLE_SIMPLE_MPD_SUPPORT bool addStandard=true; if (!haveBasicCollection && MPDUser::self()->isSupported()) { static const QChar constBullet(0x2022); switch (MessageBox::questionYesNoCancel(this, QLatin1String("

    ")+ tr("Which type of collection do you wish to connect to?")+QLatin1String("

    ")+ constBullet+QLatin1Char(' ')+tr("Standard - music collection may be shared, is on another machine, is " "already setup, or you wish to enable access from other clients (e.g. " "MPDroid)")+QLatin1String("

    ")+ constBullet+QLatin1Char(' ')+tr("Basic - music collection is not shared with others, and Cantata will " "configure and control the MPD instance. This setup will be exclusive " "to Cantata, and will not be accessible to other MPD clients.")+ QLatin1String("

    ")+ tr("NOTE: %1").arg(tr("If you wish to have an advanced MPD setup (e.g. multiple audio " "outputs, full DSD support, etc) then you must choose 'Standard'")), tr("Add Collection"), GuiItem(tr("Standard")), GuiItem(tr("Basic")))) { case MessageBox::Yes: addStandard=true; break; case MessageBox::No: addStandard=false; break; default: return; } } #endif MPDConnectionDetails details; #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (addStandard) { #endif details.name=generateName(); details.port=6600; details.hostname=QLatin1String("localhost"); details.dir=QLatin1String("/var/lib/mpd/music/"); combo->addItem(details.name); #ifdef ENABLE_SIMPLE_MPD_SUPPORT } else { details=MPDUser::self()->details(true); QString dir=QStandardPaths::writableLocation(QStandardPaths::MusicLocation); if (dir.isEmpty()) { QString dir=QDir::homePath()+"/Music"; dir=dir.replace("//", "/"); } dir=Utils::fixPath(dir); basicDir->setText(dir); MPDUser::self()->setMusicFolder(dir); combo->addItem(MPDUser::translatedName()); } #endif removeButton->setEnabled(combo->count()>1); collections.append(Collection(details)); combo->setCurrentIndex(combo->count()-1); prevIndex=combo->currentIndex(); setDetails(details); } void ServerSettings::remove() { int index=combo->currentIndex(); #ifdef ENABLE_SIMPLE_MPD_SUPPORT QString cName=1==stackedWidget->currentIndex() ? MPDUser::translatedName() : name->text(); #else QString cName=name->text(); #endif if (combo->count()>1 && MessageBox::Yes==MessageBox::questionYesNo(this, tr("Delete '%1'?").arg(cName), tr("Delete"), StdGuiItem::del(), StdGuiItem::cancel())) { bool isLast=index==(combo->count()-1); combo->removeItem(index); combo->setCurrentIndex(isLast ? index-1 : index); prevIndex=combo->currentIndex(); collections.removeAt(index); if (1==stackedWidget->currentIndex()) { haveBasicCollection=false; } setDetails(collections.at(combo->currentIndex()).details); } removeButton->setEnabled(combo->count()>1); } void ServerSettings::nameChanged() { combo->setItemText(combo->currentIndex(), name->text().trimmed()); } void ServerSettings::basicDirChanged() { if (!prevBasic.details.dir.isEmpty()) { QString d=Utils::fixPath(basicDir->text().trimmed()); basicMusicFolderNoteLabel->setOn(d.isEmpty() || d!=prevBasic.details.dir); } } QString ServerSettings::generateName(int ignore) const { QString n; QSet collectionNames; for (int i=0; isetText(Utils::convertPathForDisplay(details.dir)); basicCoverName->setText(details.coverName); stackedWidget->setCurrentIndex(1); } else { #endif name->setText(details.name.isEmpty() ? tr("Default") : details.name); host->setText(details.hostname); port->setValue(details.port); password->setText(details.password); dir->setText(Utils::convertPathForDisplay(details.dir)); coverName->setText(details.coverName); #ifdef ENABLE_HTTP_STREAM_PLAYBACK streamUrl->setText(details.streamUrl); #endif stackedWidget->setCurrentIndex(0); #ifdef ENABLE_SIMPLE_MPD_SUPPORT } #endif } MPDConnectionDetails ServerSettings::getDetails() const { MPDConnectionDetails details; if (0==stackedWidget->currentIndex()) { details.name=name->text().trimmed(); #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (details.name==MPDUser::constName) { details.name=QString(); } #endif details.hostname=host->text().trimmed(); details.port=port->value(); details.password=password->text(); details.dir=Utils::convertPathFromDisplay(dir->text()); details.coverName=coverName->text().trimmed(); #ifdef ENABLE_HTTP_STREAM_PLAYBACK details.streamUrl=streamUrl->text().trimmed(); #endif } #ifdef ENABLE_SIMPLE_MPD_SUPPORT else { details=MPDUser::self()->details(true); details.dir=Utils::convertPathFromDisplay(basicDir->text()); details.coverName=basicCoverName->text().trimmed(); MPDUser::self()->setMusicFolder(details.dir); } #endif details.setDirReadable(); return details; } cantata-2.2.0/gui/serversettings.h000066400000000000000000000036511316350454000171600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SERVERSETTINGS_H #define SERVERSETTINGS_H #include "ui_serversettings.h" #include "mpd-interface/mpdconnection.h" #include "devices/deviceoptions.h" class ServerSettings : public QWidget, private Ui::ServerSettings { Q_OBJECT struct Collection { Collection(const MPDConnectionDetails &d=MPDConnectionDetails(), const DeviceOptions &n=DeviceOptions()) : details(d), namingOpts(n) { } MPDConnectionDetails details; DeviceOptions namingOpts; }; public: ServerSettings(QWidget *p); virtual ~ServerSettings() { } void load(); void save(); void cancel(); private Q_SLOTS: void showDetails(int index); void add(); void remove(); void nameChanged(); void basicDirChanged(); private: void setDetails(const MPDConnectionDetails &details); MPDConnectionDetails getDetails() const; QString generateName(int ignore=-1) const; private: QList collections; Collection prevBasic; bool haveBasicCollection; bool isCurrentConnection; bool allOptions; int prevIndex; }; #endif cantata-2.2.0/gui/serversettings.ui000066400000000000000000000251651316350454000173520ustar00rootroot00000000000000 ServerSettings 0 0 599 472 0 0 0 0 0 0 Collection: 0 QFormLayout::ExpandingFieldsGrow Name: name Host: host 0 1 65535 6600 Password: password QLineEdit::Password Music folder: dir Cover filename: coverName <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> HTTP stream URL: streamUrl The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. Qt::Vertical 20 2 Qt::Vertical QSizePolicy::Fixed 20 13 QFormLayout::ExpandingFieldsGrow Music folder: dir Cover filename: basicCoverName If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> Qt::Vertical 20 0 BuddyLabel QLabel

    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    LineEdit QLineEdit
    support/lineedit.h
    PathRequester QLineEdit
    support/pathrequester.h
    1
    FlatToolButton QToolButton
    support/flattoolbutton.h
    combo cantata-2.2.0/gui/settings.cpp000066400000000000000000000624131316350454000162650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "settings.h" #include "models/sqllibrarymodel.h" #include "support/fancytabwidget.h" #include "widgets/itemview.h" #include "mpd-interface/mpdparseutils.h" #include "support/utils.h" #include "support/globalstatic.h" #include "db/librarydb.h" #include #include #include GLOBAL_STATIC(Settings, instance) struct MpdDefaults { MpdDefaults() : host("localhost") , dir("/var/lib/mpd/music/") , port(6600) { } static QString getVal(const QString &line) { QStringList parts=line.split('\"'); return parts.count()>1 ? parts[1] : QString(); } enum Details { DT_DIR = 0x01, DT_ADDR = 0x02, DT_PORT = 0x04, DT_PASSWD = 0x08, DT_ALL = DT_DIR|DT_ADDR|DT_PORT|DT_PASSWD }; void read() { QFile f("/etc/mpd.conf"); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { int details=0; while (!f.atEnd()) { QString line = QString::fromUtf8(f.readLine()).trimmed(); if (line.startsWith('#')) { continue; } else if (!(details&DT_DIR) && line.startsWith(QLatin1String("music_directory"))) { QString val=getVal(line); if (!val.isEmpty() && QDir(val).exists()) { dir=Utils::fixPath(val); details|=DT_DIR; } } else if (!(details&DT_ADDR) && line.startsWith(QLatin1String("bind_to_address"))) { QString val=getVal(line); if (!val.isEmpty() && val!=QLatin1String("any")) { host=val; details|=DT_ADDR; } } else if (!(details&DT_PORT) && line.startsWith(QLatin1String("port"))) { int val=getVal(line).toInt(); if (val>0) { port=val; details|=DT_PORT; } } else if (!(details&DT_PASSWD) && line.startsWith(QLatin1String("password"))) { QString val=getVal(line); if (!val.isEmpty()) { QStringList parts=val.split('@'); if (!parts.isEmpty()) { passwd=parts[0]; details|=DT_PASSWD; } } } if (details==DT_ALL) { break; } } } } QString host; QString dir; QString passwd; int port; }; static MpdDefaults mpdDefaults; static QString getStartupStateStr(Settings::StartupState s) { switch (s) { case Settings::SS_ShowMainWindow: return QLatin1String("show"); case Settings::SS_HideMainWindow: return QLatin1String("hide"); default: case Settings::SS_Previous: return QLatin1String("prev"); } } static Settings::StartupState getStartupState(const QString &str) { for (int i=0; i<=Settings::SS_Previous; ++i) { if (getStartupStateStr((Settings::StartupState)i)==str) { return (Settings::StartupState)i; } } return Settings::SS_Previous; } Settings::Settings() : state(AP_Configured) , ver(-1) { // Call 'version' so that it initialises 'ver' and 'state' version(); // Only need to read system defaults if we have not previously been configured... if (!cfg.hasGroup(MPDConnectionDetails::configGroupName())) { mpdDefaults.read(); } } Settings::~Settings() { save(); } QString Settings::currentConnection() { return cfg.get("currentConnection", QString()); } MPDConnectionDetails Settings::connectionDetails(const QString &name) { MPDConnectionDetails details; QString n=MPDConnectionDetails::configGroupName(name); details.name=name; if (!cfg.hasGroup(n)) { details.name=QString(); n=MPDConnectionDetails::configGroupName(details.name); } if (cfg.hasGroup(n)) { Configuration grp(n); details.hostname=grp.get("host", name.isEmpty() ? mpdDefaults.host : QString()); details.port=grp.get("port", name.isEmpty() ? mpdDefaults.port : 6600); details.dir=grp.getDirPath("dir", name.isEmpty() ? mpdDefaults.dir : "/var/lib/mpd/music"); details.password=grp.get("passwd", name.isEmpty() ? mpdDefaults.passwd : QString()); details.coverName=grp.get("coverName", QString()); #ifdef ENABLE_HTTP_STREAM_PLAYBACK details.streamUrl=grp.get("streamUrl", QString()); #endif details.replayGain=grp.get("replayGain", QString()); } else { details.hostname=mpdDefaults.host; details.port=mpdDefaults.port; details.dir=mpdDefaults.dir; details.password=mpdDefaults.passwd; details.coverName=QString(); #ifdef ENABLE_HTTP_STREAM_PLAYBACK details.streamUrl=QString(); #endif } details.setDirReadable(); return details; } QList Settings::allConnections() { QStringList groups=cfg.childGroups(); QList connections; foreach (const QString &grp, groups) { if (cfg.hasGroup(grp) && grp.startsWith("Connection")) { connections.append(connectionDetails(grp=="Connection" ? QString() : grp.mid(11))); } } if (connections.isEmpty()) { // If we are empty, add at lease the default connection... connections.append(connectionDetails()); } return connections; } bool Settings::showPlaylist() { return cfg.get("showPlaylist", true); } bool Settings::showFullScreen() { return cfg.get("showFullScreen", false); } QByteArray Settings::headerState(const QString &key) { if (version()=CANTATA_MAKE_VERSION(0, 9, 50); #endif return cfg.get("storeStreamsInMpdDir", def); } bool Settings::storeBackdropsInMpdDir() { return cfg.get("storeBackdropsInMpdDir", false); } int Settings::sidebar() { if (version() Settings::composerGenres() { return cfg.get("composerGenres", Song::composerGenres().toList()).toSet(); } QSet Settings::singleTracksFolders() { return cfg.get("singleTracksFolders", QStringList()).toSet(); } MPDParseUtils::CueSupport Settings::cueSupport() { return MPDParseUtils::toCueSupport(cfg.get("cueSupport", MPDParseUtils::toStr(MPDParseUtils::Cue_Parse))); } QStringList Settings::lyricProviders() { return cfg.get("lyricProviders", QStringList() << "lyrics.wikia.com" << "lyricstime.com" << "lyricsreg.com" << "lyricsmania.com" << "metrolyrics.com" << "azlyrics.com" << "songlyrics.com" << "elyrics.net" << "lyricsdownload.com" << "lyrics.com" << "lyricsbay.com" << "directlyrics.com" << "loudson.gs" << "teksty.org" << "tekstowo.pl (POLISH)" << "vagalume.uol.com.br" << "vagalume.uol.com.br (PORTUGUESE)"); } QStringList Settings::wikipediaLangs() { return cfg.get("wikipediaLangs", QStringList() << "en:en"); } bool Settings::wikipediaIntroOnly() { return cfg.get("wikipediaIntroOnly", true); } int Settings::contextBackdrop() { return getBoolAsInt("contextBackdrop", 0); } int Settings::contextBackdropOpacity() { return cfg.get("contextBackdropOpacity", 15, 0, 100); } int Settings::contextBackdropBlur() { return cfg.get("contextBackdropBlur", 0, 0, 20); } QString Settings::contextBackdropFile() { return cfg.getFilePath("contextBackdropFile", QString()); } bool Settings::contextDarkBackground() { return cfg.get("contextDarkBackground", false); } int Settings::contextZoom() { return cfg.get("contextZoom", 0); } QString Settings::contextSlimPage() { return cfg.get("contextSlimPage", QString()); } QByteArray Settings::contextSplitterState() { return version() Settings::ignorePrefixes() { return cfg.get("ignorePrefixes", Song::ignorePrefixes().toList()).toSet(); } bool Settings::mpris() { return cfg.get("mpris", true); } QString Settings::style() { return cfg.get("style", QString()); } void Settings::removeConnectionDetails(const QString &v) { if (v==currentConnection()) { saveCurrentConnection(QString()); } cfg.removeGroup(MPDConnectionDetails::configGroupName(v)); } void Settings::saveConnectionDetails(const MPDConnectionDetails &v) { if (v.name.isEmpty()) { cfg.removeEntry("connectionHost"); cfg.removeEntry("connectionPasswd"); cfg.removeEntry("connectionPort"); cfg.removeEntry("mpdDir"); } QString n=MPDConnectionDetails::configGroupName(v.name); Configuration grp(n); grp.set("host", v.hostname); grp.set("port", (int)v.port); grp.setDirPath("dir", v.dir); grp.set("passwd", v.password); grp.set("coverName", v.coverName); #ifdef ENABLE_HTTP_STREAM_PLAYBACK grp.set("streamUrl", v.streamUrl); #endif } void Settings::saveCurrentConnection(const QString &v) { cfg.set("currentConnection", v); } void Settings::saveShowFullScreen(bool v) { cfg.set("showFullScreen", v); } void Settings::saveShowPlaylist(bool v) { cfg.set("showPlaylist", v); } void Settings::saveHeaderState(const QString &key, const QByteArray &v) { cfg.set(key+"HeaderState", v); } void Settings::saveSplitterState(const QByteArray &v) { cfg.set("splitterState", v); } void Settings::saveSplitterAutoHide(bool v) { cfg.set("splitterAutoHide", v); } void Settings::saveMainWindowSize(const QSize &v) { cfg.set("mainWindowSize", v); } void Settings::saveMainWindowCollapsedSize(const QSize &v) { if (v.width()>16 && v.height()>16) { cfg.set("mainWindowCollapsedSize", v); } } void Settings::saveUseSystemTray(bool v) { cfg.set("useSystemTray", v); } void Settings::saveMinimiseOnClose(bool v) { cfg.set("minimiseOnClose", v); } void Settings::saveShowPopups(bool v) { cfg.set("showPopups", v); } void Settings::saveStopOnExit(bool v) { cfg.set("stopOnExit", v); } void Settings::saveStoreCoversInMpdDir(bool v) { cfg.set("storeCoversInMpdDir", v); } void Settings::saveStoreLyricsInMpdDir(bool v) { cfg.set("storeLyricsInMpdDir", v); } void Settings::saveStoreBackdropsInMpdDir(bool v) { cfg.set("storeBackdropsInMpdDir", v); } void Settings::saveSidebar(int v) { cfg.set("sidebar", v); } void Settings::saveComposerGenres(const QSet &v) { cfg.set("composerGenres", v.toList()); } void Settings::saveSingleTracksFolders(const QSet &v) { cfg.set("singleTracksFolders", v.toList()); } void Settings::saveCueSupport(MPDParseUtils::CueSupport v) { cfg.set("cueSupport", MPDParseUtils::toStr(v)); } void Settings::saveLyricProviders(const QStringList &v) { cfg.set("lyricProviders", v); } void Settings::saveWikipediaLangs(const QStringList &v) { cfg.set("wikipediaLangs", v); } void Settings::saveWikipediaIntroOnly(bool v) { cfg.set("wikipediaIntroOnly", v); } void Settings::saveContextBackdrop(int v) { cfg.set("contextBackdrop", v); } void Settings::saveContextBackdropOpacity(int v) { cfg.set("contextBackdropOpacity", v); } void Settings::saveContextBackdropBlur(int v) { cfg.set("contextBackdropBlur", v); } void Settings::saveContextBackdropFile(const QString &v) { cfg.setFilePath("contextBackdropFile", v); } void Settings::saveContextDarkBackground(bool v) { cfg.set("contextDarkBackground", v); } void Settings::saveContextZoom(int v) { cfg.set("contextZoom", v); } void Settings::saveContextSlimPage(const QString &v) { cfg.set("contextSlimPage", v); } void Settings::saveContextSplitterState(const QByteArray &v) { cfg.set("contextSplitterState", v); } void Settings::saveContextAlwaysCollapsed(bool v) { cfg.set("contextAlwaysCollapsed", v); } void Settings::saveContextSwitchTime(int v) { cfg.set("contextSwitchTime", v); } void Settings::saveContextAutoScroll(bool v) { cfg.set("contextAutoScroll", v); } void Settings::saveContextTrackView(int v) { cfg.set("contextTrackView", v); } void Settings::savePage(const QString &v) { cfg.set("page", v); } void Settings::saveHiddenPages(const QStringList &v) { cfg.set("hiddenPages", v); } #ifdef ENABLE_DEVICES_SUPPORT void Settings::saveOverwriteSongs(bool v) { cfg.set("overwriteSongs", v); } void Settings::saveShowDeleteAction(bool v) { cfg.set("showDeleteAction", v); } #endif void Settings::saveStopFadeDuration(int v) { cfg.set("stopFadeDuration", v); } void Settings::saveHttpAllocatedPort(int v) { cfg.set("httpAllocatedPort", v); } void Settings::saveHttpInterface(const QString &v) { cfg.set("httpInterface", v); } void Settings::savePlayQueueView(int v) { cfg.set("playQueueView", ItemView::modeStr((ItemView::Mode)v)); } void Settings::savePlayQueueAutoExpand(bool v) { cfg.set("playQueueAutoExpand", v); } void Settings::savePlayQueueStartClosed(bool v) { cfg.set("playQueueStartClosed", v); } void Settings::savePlayQueueScroll(bool v) { cfg.set("playQueueScroll", v); } void Settings::savePlayQueueBackground(int v) { cfg.set("playQueueBackground", v); } void Settings::savePlayQueueBackgroundOpacity(int v) { cfg.set("playQueueBackgroundOpacity", v); } void Settings::savePlayQueueBackgroundBlur(int v) { cfg.set("playQueueBackgroundBlur", v); } void Settings::savePlayQueueBackgroundFile(const QString &v) { cfg.setFilePath("playQueueBackgroundFile", v); } void Settings::savePlayQueueConfirmClear(bool v) { cfg.set("playQueueConfirmClear", v); } void Settings::savePlayQueueSearch(bool v) { cfg.set("playQueueSearch", v); } void Settings::savePlayListsStartClosed(bool v) { cfg.set("playListsStartClosed", v); } #ifdef ENABLE_HTTP_STREAM_PLAYBACK void Settings::savePlayStream(bool v) { cfg.set("playStream", v); } #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND void Settings::saveCdAuto(bool v) { cfg.set("cdAuto", v); } void Settings::saveParanoiaFull(bool v) { cfg.set("paranoiaFull", v); } void Settings::saveParanoiaNeverSkip(bool v) { cfg.set("paranoiaNeverSkip", v); } #endif #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND void Settings::saveUseCddb(bool v) { cfg.set("useCddb", v); } #endif #ifdef CDDB_FOUND void Settings::saveCddbHost(const QString &v) { cfg.set("cddbHost", v); } void Settings::saveCddbPort(int v) { cfg.set("cddbPort", v); } #endif void Settings::saveForceSingleClick(bool v) { cfg.set("forceSingleClick", v); } void Settings::saveStartHidden(bool v) { cfg.set("startHidden", v); } void Settings::saveShowTimeRemaining(bool v) { cfg.set("showTimeRemaining", v); } void Settings::saveHiddenStreamCategories(const QStringList &v) { cfg.set("hiddenStreamCategories", v); } void Settings::saveHiddenOnlineProviders(const QStringList &v) { cfg.set("hiddenOnlineProviders", v); } #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) void Settings::saveInhibitSuspend(bool v) { cfg.set("inhibitSuspend", v); } #endif void Settings::saveRssUpdate(int v) { cfg.set("rssUpdate", v); } void Settings::saveLastRssUpdate(const QDateTime &v) { cfg.set("lastRssUpdate", v); } void Settings::savePodcastDownloadPath(const QString &v) { cfg.set("podcastDownloadPath", v); } void Settings::savePodcastAutoDownloadLimit(int v) { if (cfg.hasEntry("podcastAutoDownload")) { cfg.removeEntry("podcastAutoDownload"); } cfg.set("podcastAutoDownloadLimit", v); } void Settings::saveStartupState(int v) { cfg.set("startupState", getStartupStateStr((StartupState)v)); } void Settings::saveSearchCategory(const QString &v) { cfg.set("searchCategory", v); } void Settings::saveFetchCovers(bool v) { cfg.set("fetchCovers", v); } void Settings::saveLang(const QString &v) { cfg.set("lang", v); } void Settings::saveShowCoverWidget(bool v) { cfg.set("showCoverWidget", v); } void Settings::saveShowStopButton(bool v) { cfg.set("showStopButton", v); } void Settings::saveShowRatingWidget(bool v) { cfg.set("showRatingWidget", v); } void Settings::saveInfoTooltips(bool v) { cfg.set("infoTooltips", v); } void Settings::saveIgnorePrefixes(const QSet &v) { cfg.set("ignorePrefixes", v.toList()); } void Settings::saveMpris(bool v) { cfg.set("mpris", v); } void Settings::saveReplayGain(const QString &conn, const QString &v) { QString n=MPDConnectionDetails::configGroupName(conn); Configuration grp(n); grp.set("replayGain", v); } void Settings::saveStyle(const QString &v) { cfg.set("style", v); } void Settings::save() { if (AP_NotConfigured!=state) { if (version()!=PACKAGE_VERSION || AP_FirstRun==state) { cfg.set("version", PACKAGE_VERSION_STRING); ver=PACKAGE_VERSION; } } cfg.sync(); } void Settings::clearVersion() { cfg.removeEntry("version"); state=AP_NotConfigured; } int Settings::getBoolAsInt(const QString &key, int def) { // Old config, sometimes bool was used - which has now been converted // to an int... QString v=cfg.get(key, QString::number(def)); if (QLatin1String("false")==v) { return 0; } if (QLatin1String("true")==v) { return 1; } return v.toInt(); } cantata-2.2.0/gui/settings.h000066400000000000000000000205411316350454000157260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SETTINGS_H #define SETTINGS_H #include "support/configuration.h" #include "config.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdparseutils.h" class Settings { public: enum StartupState { SS_ShowMainWindow, SS_HideMainWindow, SS_Previous }; static Settings *self(); Settings(); ~Settings(); QString currentConnection(); MPDConnectionDetails connectionDetails(const QString &name=Settings::self()->currentConnection()); QList allConnections(); bool showPlaylist(); bool showFullScreen(); QByteArray headerState(const QString &key); QByteArray splitterState(); bool splitterAutoHide(); QSize mainWindowSize(); QSize mainWindowCollapsedSize(); bool useSystemTray(); bool minimiseOnClose(); bool showPopups(); bool stopOnExit(); bool storeCoversInMpdDir(); bool storeLyricsInMpdDir(); bool storeStreamsInMpdDir(); bool storeBackdropsInMpdDir(); int sidebar(); QSet composerGenres(); QSet singleTracksFolders(); MPDParseUtils::CueSupport cueSupport(); QStringList lyricProviders(); QStringList wikipediaLangs(); bool wikipediaIntroOnly(); int contextBackdrop(); int contextBackdropOpacity(); int contextBackdropBlur(); QString contextBackdropFile(); bool contextDarkBackground(); int contextZoom(); QString contextSlimPage(); QByteArray contextSplitterState(); bool contextAlwaysCollapsed(); int contextSwitchTime(); bool contextAutoScroll(); int contextTrackView(); QString page(); QStringList hiddenPages(); #ifdef ENABLE_DEVICES_SUPPORT bool overwriteSongs(); bool showDeleteAction(); #endif int version(); int stopFadeDuration(); int httpAllocatedPort(); QString httpInterface(); int playQueueView(); bool playQueueAutoExpand(); bool playQueueStartClosed(); bool playQueueScroll(); int playQueueBackground(); int playQueueBackgroundOpacity(); int playQueueBackgroundBlur(); QString playQueueBackgroundFile(); bool playQueueConfirmClear(); bool playQueueSearch(); bool playListsStartClosed(); #ifdef ENABLE_HTTP_STREAM_PLAYBACK bool playStream(); #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND bool cdAuto(); bool paranoiaFull(); bool paranoiaNeverSkip(); #endif #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND bool useCddb(); #endif #ifdef CDDB_FOUND QString cddbHost(); int cddbPort(); #endif bool forceSingleClick(); bool startHidden(); bool showTimeRemaining(); QStringList hiddenStreamCategories(); QStringList hiddenOnlineProviders(); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) bool inhibitSuspend(); #endif int rssUpdate(); QDateTime lastRssUpdate(); QString podcastDownloadPath(); int podcastAutoDownloadLimit(); bool networkAccessEnabled(); int volumeStep(); StartupState startupState(); QString searchCategory(); bool fetchCovers(); QString lang(); bool showCoverWidget(); bool showStopButton(); bool showRatingWidget(); bool infoTooltips(); QSet ignorePrefixes(); bool mpris(); QString style(); void removeConnectionDetails(const QString &v); void saveConnectionDetails(const MPDConnectionDetails &v); void saveCurrentConnection(const QString &v); void saveShowPlaylist(bool v); void saveShowFullScreen(bool v); void saveStopOnExit(bool v); void saveHeaderState(const QString &key, const QByteArray &v); void saveSplitterState(const QByteArray &v); void saveSplitterAutoHide(bool v); void saveMainWindowSize(const QSize &v); void saveMainWindowCollapsedSize(const QSize &v); void saveUseSystemTray(bool v); void saveMinimiseOnClose(bool v); void saveShowPopups(bool v); void saveStoreCoversInMpdDir(bool v); void saveStoreLyricsInMpdDir(bool v); void saveStoreBackdropsInMpdDir(bool v); void saveLibraryArtistImage(bool v); void saveSidebar(int v); void saveComposerGenres(const QSet &v); void saveSingleTracksFolders(const QSet &v); void saveCueSupport(MPDParseUtils::CueSupport v); void saveLyricProviders(const QStringList &v); void saveWikipediaLangs(const QStringList &v); void saveWikipediaIntroOnly(bool v); void saveContextBackdrop(int v); void saveContextBackdropOpacity(int v); void saveContextBackdropBlur(int v); void saveContextBackdropFile(const QString &v); void saveContextDarkBackground(bool v); void saveContextZoom(int v); void saveContextSlimPage(const QString &v); void saveContextSplitterState(const QByteArray &v); void saveContextAlwaysCollapsed(bool v); void saveContextSwitchTime(int v); void saveContextAutoScroll(bool v); void saveContextTrackView(int v); void savePage(const QString &v); void saveHiddenPages(const QStringList &v); #ifdef ENABLE_DEVICES_SUPPORT void saveOverwriteSongs(bool v); void saveShowDeleteAction(bool v); #endif void saveStopFadeDuration(int v); void saveHttpAllocatedPort(int v); void saveHttpInterface(const QString &v); void savePlayQueueView(int v); void savePlayQueueAutoExpand(bool v); void savePlayQueueStartClosed(bool v); void savePlayQueueScroll(bool v); void savePlayQueueBackground(int v); void savePlayQueueBackgroundOpacity(int v); void savePlayQueueBackgroundBlur(int v); void savePlayQueueBackgroundFile(const QString &v); void savePlayQueueConfirmClear(bool v); void savePlayQueueSearch(bool v); void savePlayListsStartClosed(bool v); #ifdef ENABLE_HTTP_STREAM_PLAYBACK void savePlayStream(bool v); #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND void saveCdAuto(bool v); void saveParanoiaFull(bool v); void saveParanoiaNeverSkip(bool v); #endif #if defined CDDB_FOUND && defined MUSICBRAINZ5_FOUND void saveUseCddb(bool v); #endif #ifdef CDDB_FOUND void saveCddbHost(const QString &v); void saveCddbPort(int v); #endif void saveForceSingleClick(bool v); void saveStartHidden(bool v); void saveShowTimeRemaining(bool v); void saveHiddenStreamCategories(const QStringList &v); void saveHiddenOnlineProviders(const QStringList &v); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) void saveInhibitSuspend(bool v); #endif void saveRssUpdate(int v); void saveLastRssUpdate(const QDateTime &v); void savePodcastDownloadPath(const QString &v); void savePodcastAutoDownloadLimit(int v); void saveStartupState(int v); void saveSearchCategory(const QString &v); void saveFetchCovers(bool v); void saveLang(const QString &v); void saveShowCoverWidget(bool v); void saveShowStopButton(bool v); void saveShowRatingWidget(bool v); void saveInfoTooltips(bool v); void saveIgnorePrefixes(const QSet &v); void saveMpris(bool v); void saveReplayGain(const QString &conn, const QString &v); void saveStyle(const QString &v); void save(); void clearVersion(); bool firstRun() const { return AP_Configured!=state; } private: int getBoolAsInt(const QString &key, int def); private: enum AppState { AP_FirstRun, AP_NotConfigured, AP_Configured }; AppState state; int ver; Configuration cfg; }; #endif cantata-2.2.0/gui/shortcutssettingspage.cpp000066400000000000000000000035441316350454000211010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "shortcutssettingspage.h" #include "mediakeys.h" #include "widgets/toolbutton.h" #include "support/actioncollection.h" #include "support/shortcutssettingswidget.h" #include "widgets/basicitemdelegate.h" #include "settings.h" #include "widgets/icons.h" #include "support/buddylabel.h" #include "support/utils.h" #include #include #include #include #include #include ShortcutsSettingsPage::ShortcutsSettingsPage(QWidget *p) : QWidget(p) { QBoxLayout *lay=new QBoxLayout(QBoxLayout::TopToBottom, this); lay->setMargin(0); QHash map; map.insert("Cantata", ActionCollection::get()); shortcuts = new ShortcutsSettingsWidget(map, this); shortcuts->view()->setAlternatingRowColors(false); shortcuts->view()->setItemDelegate(new BasicItemDelegate(shortcuts->view())); lay->addWidget(shortcuts); } void ShortcutsSettingsPage::load() { } void ShortcutsSettingsPage::save() { shortcuts->save(); } cantata-2.2.0/gui/shortcutssettingspage.h000066400000000000000000000022731316350454000205440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SHORTCUT_SETTINGS_PAGE_H #define SHORTCUT_SETTINGS_PAGE_H #include class ShortcutsSettingsWidget; class QComboBox; class ToolButton; class QCheckBox; class ShortcutsSettingsPage : public QWidget { public: ShortcutsSettingsPage(QWidget *p); void load(); void save(); private: ShortcutsSettingsWidget *shortcuts; }; #endif cantata-2.2.0/gui/singleapplication.cpp000066400000000000000000000043551316350454000201330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "singleapplication.h" #include "mainwindow.h" #include "mpd-interface/mpdconnection.h" SingleApplication::SingleApplication(int &argc, char **argv) : QtSingleApplication(argc, argv) { connect(this, SIGNAL(messageReceived(const QString &)), SLOT(message(const QString &))); connect(this, SIGNAL(reconnect()), MPDConnection::self(), SLOT(reconnect())); } bool SingleApplication::start() { if (isRunning()) { QStringList args(arguments()); if (args.count()>1) { args.takeAt(0); sendMessage(args.join("\n")); } else { sendMessage(QString()); } return false; } return true; } void SingleApplication::message(const QString &msg) { if (!msg.isEmpty()) { load(msg.split("\n")); } MainWindow *mw=qobject_cast(activationWindow()); if (mw) { mw->restoreWindow(); } } void SingleApplication::loadFiles() { QStringList args(arguments()); if (args.count()>1) { args.takeAt(0); load(args); } } void SingleApplication::load(const QStringList &files) { if (files.isEmpty()) { return; } QStringList urls; foreach (const QString &f, files) { urls.append(f); } if (!urls.isEmpty()) { MainWindow *mw=qobject_cast(activationWindow()); if (mw) { mw->load(urls); } } } cantata-2.2.0/gui/singleapplication.h000066400000000000000000000024501316350454000175720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SINGLE_APPLICATION_H #define SINGLE_APPLICATION_H #include "qtsingleapplication/qtsingleapplication.h" class SingleApplication : public QtSingleApplication { Q_OBJECT public: SingleApplication(int &argc, char **argv); virtual ~SingleApplication() { } bool start(); void loadFiles(); private: void load(const QStringList &files); private Q_SLOTS: void message(const QString &m); Q_SIGNALS: void reconnect(); }; #endif cantata-2.2.0/gui/stdactions.cpp000066400000000000000000000166241316350454000166030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "stdactions.h" #include "widgets/icons.h" #include "support/action.h" #include "support/actioncollection.h" #include "support/monoicon.h" #include "models/playlistsmodel.h" #ifdef ENABLE_DEVICES_SUPPORT #include "models/devicesmodel.h" #endif #include "support/icon.h" #include "widgets/icons.h" #include "support/utils.h" #include "widgets/mirrormenu.h" #include "support/globalstatic.h" #include GLOBAL_STATIC(StdActions, instance) static void setToolTip(Action *act, const QString &tt) { act->setToolTip(tt); act->setProperty(Action::constTtForSettings, true); } static QMenu * priorityMenu() { Action *prioHighestAction = new Action(QObject::tr("Highest Priority (255)"), 0); Action *prioHighAction = new Action(QObject::tr("High Priority (200)"), 0); Action *prioMediumAction = new Action(QObject::tr("Medium Priority (125)"), 0); Action *prioLowAction = new Action(QObject::tr("Low Priority (50)"), 0); Action *prioDefaultAction = new Action(QObject::tr("Default Priority (0)"), 0); Action *prioCustomAction = new Action(QObject::tr("Custom Priority..."), 0); prioHighestAction->setData(255); prioHighAction->setData(200); prioMediumAction->setData(125); prioLowAction->setData(50); prioDefaultAction->setData(0); prioCustomAction->setData(-1); QMenu *prioMenu=new QMenu(); prioMenu->addAction(prioHighestAction); prioMenu->addAction(prioHighAction); prioMenu->addAction(prioMediumAction); prioMenu->addAction(prioLowAction); prioMenu->addAction(prioDefaultAction); prioMenu->addAction(prioCustomAction); return prioMenu; } StdActions::StdActions() { QColor col=Utils::monoIconColor(); prevTrackAction = ActionCollection::get()->createAction("prevtrack", QObject::tr("Previous Track"), Icons::self()->toolbarPrevIcon); nextTrackAction = ActionCollection::get()->createAction("nexttrack", QObject::tr("Next Track"), Icons::self()->toolbarNextIcon); playPauseTrackAction = ActionCollection::get()->createAction("playpausetrack", QObject::tr("Play/Pause"), Icons::self()->toolbarPlayIcon); stopPlaybackAction = ActionCollection::get()->createAction("stopplayback", QObject::tr("Stop"), Icons::self()->toolbarStopIcon); stopAfterCurrentTrackAction = ActionCollection::get()->createAction("stopaftercurrenttrack", QObject::tr("Stop After Current Track"), Icons::self()->toolbarStopIcon); stopAfterTrackAction = ActionCollection::get()->createAction("stopaftertrack", QObject::tr("Stop After Track"), Icons::self()->toolbarStopIcon); increaseVolumeAction = ActionCollection::get()->createAction("increasevolume", QObject::tr("Increase Volume")); decreaseVolumeAction = ActionCollection::get()->createAction("decreasevolume", QObject::tr("Decrease Volume")); savePlayQueueAction = ActionCollection::get()->createAction("saveplayqueue", QObject::tr("Save As"), Icons::self()->savePlayQueueIcon); appendToPlayQueueAction = ActionCollection::get()->createAction("appendtoplayqueue", QObject::tr("Append"), Icons::self()->appendToPlayQueueIcon); setToolTip(appendToPlayQueueAction, QObject::tr("Append To Play Queue")); appendToPlayQueueAndPlayAction = ActionCollection::get()->createAction("appendtoplayqueueandplay", QObject::tr("Append And Play")); addToPlayQueueAndPlayAction = ActionCollection::get()->createAction("addtoplayqueueandplay", QObject::tr("Add And Play")); setToolTip(appendToPlayQueueAndPlayAction, QObject::tr("Append To Play Queue And Play")); insertAfterCurrentAction = ActionCollection::get()->createAction("insertintoplayqueue", QObject::tr("Insert After Current")); addRandomAlbumToPlayQueueAction = ActionCollection::get()->createAction("addrandomalbumtoplayqueue", QObject::tr("Append Random Album")); replacePlayQueueAction = ActionCollection::get()->createAction("replaceplayqueue", QObject::tr("Play Now (And Replace Play Queue)"), Icons::self()->replacePlayQueueIcon); savePlayQueueAction->setShortcut(Qt::ControlModifier+Qt::Key_S); appendToPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::Key_P); replacePlayQueueAction->setShortcut(Qt::ControlModifier+Qt::Key_R); addWithPriorityAction = new Action(QObject::tr("Add With Priority"), 0); setPriorityAction = new Action(QObject::tr("Set Priority"), 0); setPriorityAction->setMenu(priorityMenu()); addWithPriorityAction->setMenu(priorityMenu()); addToStoredPlaylistAction = new Action(Icons::self()->playlistListIcon, QObject::tr("Add To Playlist"), 0); #ifdef TAGLIB_FOUND organiseFilesAction = ActionCollection::get()->createAction("orgfiles", QObject::tr("Organize Files"), MonoIcon::icon(FontAwesome::folderopeno, col)); editTagsAction = ActionCollection::get()->createAction("edittags", QObject::tr("Edit Track Information"), 0); #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT replaygainAction = ActionCollection::get()->createAction("replaygain", QObject::tr("ReplayGain"), MonoIcon::icon(FontAwesome::barchart, col)); #endif #ifdef ENABLE_DEVICES_SUPPORT copyToDeviceAction = new Action(MonoIcon::icon(FontAwesome::mobile, col), QObject::tr("Copy Songs To Device"), 0); copyToDeviceAction->setMenu(DevicesModel::self()->menu()); deleteSongsAction = new Action(MonoIcon::icon(FontAwesome::trash, MonoIcon::constRed), QObject::tr("Delete Songs"), 0); #endif setCoverAction = new Action(QObject::tr("Set Image"), 0); removeAction = new Action(Icons::self()->removeIcon, QObject::tr("Remove"), 0); searchAction = ActionCollection::get()->createAction("search", QObject::tr("Find"), Icons::self()->searchIcon); searchAction->setShortcut(Qt::ControlModifier+Qt::Key_F); addToStoredPlaylistAction->setMenu(PlaylistsModel::self()->menu()); QMenu *addMenu=new QMenu(); addToPlayQueueMenuAction = new Action(QObject::tr("Add To Play Queue"), 0); addMenu->addAction(appendToPlayQueueAction); addMenu->addAction(appendToPlayQueueAndPlayAction); addMenu->addAction(addToPlayQueueAndPlayAction); addMenu->addAction(addWithPriorityAction); addMenu->addAction(insertAfterCurrentAction); addMenu->addAction(addRandomAlbumToPlayQueueAction); addToPlayQueueMenuAction->setMenu(addMenu); addRandomAlbumToPlayQueueAction->setVisible(false); } void StdActions::enableAddToPlayQueue(bool en) { appendToPlayQueueAction->setEnabled(en); appendToPlayQueueAndPlayAction->setEnabled(en); addToPlayQueueAndPlayAction->setEnabled(en); insertAfterCurrentAction->setEnabled(en); replacePlayQueueAction->setEnabled(en); addToStoredPlaylistAction->setEnabled(en); addToPlayQueueMenuAction->setEnabled(en); } cantata-2.2.0/gui/stdactions.h000066400000000000000000000042461316350454000162450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STDACTIONS_H #define STDACTIONS_H #include "config.h" #include "support/action.h" class StdActions { public: static StdActions *self(); StdActions(); Action *nextTrackAction; Action *prevTrackAction; Action *playPauseTrackAction; Action *stopPlaybackAction; Action *stopAfterCurrentTrackAction; Action *stopAfterTrackAction; Action *increaseVolumeAction; Action *decreaseVolumeAction; Action *savePlayQueueAction; Action *appendToPlayQueueAction; Action *appendToPlayQueueAndPlayAction; Action *addToPlayQueueAndPlayAction; Action *insertAfterCurrentAction; Action *replacePlayQueueAction; Action *addWithPriorityAction; Action *addToStoredPlaylistAction; Action *setPriorityAction; Action *addToPlayQueueMenuAction; Action *addRandomAlbumToPlayQueueAction; #ifdef TAGLIB_FOUND Action *editTagsAction; Action *organiseFilesAction; #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT Action *replaygainAction; #endif #ifdef ENABLE_DEVICES_SUPPORT Action *copyToDeviceAction; Action *deleteSongsAction; #endif Action *setCoverAction; Action *removeAction; Action *searchAction; void enableAddToPlayQueue(bool en); private: StdActions(const StdActions &o); StdActions & operator=(const StdActions &o); }; #endif cantata-2.2.0/gui/trayitem.cpp000066400000000000000000000220531316350454000162570ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "trayitem.h" #include "config.h" #ifdef QT_QTDBUS_FOUND #include "dbus/notify.h" #endif #include "mainwindow.h" #include "settings.h" #include "support/action.h" #include "widgets/icons.h" #include "mpd-interface/song.h" #include "stdactions.h" #include "support/utils.h" #include "currentcover.h" #include #include #ifdef Q_OS_MAC #include "mac/macnotify.h" #endif #ifndef Q_OS_MAC class VolumeSliderEventHandler : public QObject { public: VolumeSliderEventHandler(QObject *p) : QObject(p) { } protected: bool eventFilter(QObject *obj, QEvent *event) { if (QEvent::Wheel==event->type()) { int numDegrees = static_cast(event)->delta() / 8; int numSteps = numDegrees / 15; if (numSteps > 0) { for (int i = 0; i < numSteps; ++i) { StdActions::self()->increaseVolumeAction->trigger(); } } else { for (int i = 0; i > numSteps; --i) { StdActions::self()->decreaseVolumeAction->trigger(); } } return true; } return QObject::eventFilter(obj, event); } }; #endif TrayItem::TrayItem(MainWindow *p) : QObject(p) #ifndef Q_OS_MAC , mw(p) , trayItem(0) , trayItemMenu(0) #ifdef QT_QTDBUS_FOUND , notification(0) #endif , connectionsAction(0) , outputsAction(0) #endif { } void TrayItem::showMessage(const QString &title, const QString &text, const QImage &img) { #ifdef Q_OS_MAC MacNotify::showMessage(title, text, img); #elif defined QT_QTDBUS_FOUND if (!notification) { notification=new Notify(this); } notification->show(title, text, img); #else Q_UNUSED(img) if (trayItem) { trayItem->showMessage(title, text, QSystemTrayIcon::Information, 5000); } #endif } static Action * copyAction(Action *orig) { Action *newAction=new Action(orig->parent()); newAction->setText(Utils::strippedText(orig->text())); newAction->setIcon(orig->icon()); QObject::connect(newAction, SIGNAL(triggered()), orig, SIGNAL(triggered())); QObject::connect(newAction, SIGNAL(triggered(bool)), orig, SIGNAL(triggered(bool))); return newAction; } void TrayItem::setup() { #ifndef Q_OS_MAC if (!Settings::self()->useSystemTray() || !Utils::useSystemTray()) { if (trayItem) { trayItem->setVisible(false); trayItem->deleteLater(); trayItem=0; trayItemMenu->deleteLater(); trayItemMenu=0; } return; } if (trayItem) { return; } #ifndef Q_OS_MAC connectionsAction=new Action(Utils::strippedText(mw->connectionsAction->text()), this); connectionsAction->setVisible(false); outputsAction=new Action(Utils::strippedText(mw->outputsAction->text()), this); outputsAction->setVisible(false); updateConnections(); updateOutputs(); #endif // What systems DONT have a system tray? Also, isSytemTrayAvailable is checked in config dialog, so // useSystemTray should not be set if there is none. // Checking here seems to cause the icon not to appear if Cantata is autostarted in Plasma5 - #759 //if (!QSystemTrayIcon::isSystemTrayAvailable()) { // return; //} trayItem = new QSystemTrayIcon(this); trayItem->installEventFilter(new VolumeSliderEventHandler(this)); trayItemMenu = new QMenu(0); trayItemMenu->addAction(StdActions::self()->prevTrackAction); trayItemMenu->addAction(StdActions::self()->playPauseTrackAction); trayItemMenu->addAction(StdActions::self()->stopPlaybackAction); trayItemMenu->addAction(StdActions::self()->stopAfterCurrentTrackAction); trayItemMenu->addAction(StdActions::self()->nextTrackAction); #ifndef Q_OS_MAC trayItemMenu->addSeparator(); trayItemMenu->addAction(connectionsAction); trayItemMenu->addAction(outputsAction); #endif trayItemMenu->addSeparator(); trayItemMenu->addAction(mw->restoreAction); trayItemMenu->addSeparator(); trayItemMenu->addAction(copyAction(mw->quitAction)); trayItem->setContextMenu(trayItemMenu); QIcon icon=QIcon::fromTheme(QIcon::hasThemeIcon("cantata-panel") ? "cantata-panel" : "cantata"); #if !defined Q_OS_MAC && !defined Q_OS_WIN // Bug: 660 If installed to non-standard folder, QIcon::fromTheme does not seem to find icon. Therefore // add icon files here... if (icon.isNull()) { QStringList sizes=QStringList() << "16" << "22" << "24" << "32" << "48" << "64"; foreach (const QString &s, sizes) { icon.addFile(QLatin1String(ICON_INSTALL_PREFIX "/")+s+QLatin1Char('x')+s+QLatin1String("/apps/cantata.png")); } icon.addFile(QLatin1String(ICON_INSTALL_PREFIX "/scalable/apps/cantata.svg")); } #endif trayItem->setIcon(icon); trayItem->setToolTip(tr("Cantata")); trayItem->show(); connect(trayItem, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayItemClicked(QSystemTrayIcon::ActivationReason))); #endif } void TrayItem::trayItemClicked(QSystemTrayIcon::ActivationReason reason) { #ifdef Q_OS_MAC Q_UNUSED(reason) #else switch (reason) { case QSystemTrayIcon::Trigger: if (mw->isHidden()) { mw->restoreWindow(); } else { mw->hideWindow(); } break; case QSystemTrayIcon::MiddleClick: mw->playPauseTrack(); break; default: break; } #endif } void TrayItem::songChanged(const Song &song, bool isPlaying) { #ifdef Q_OS_MAC if (Settings::self()->showPopups()) { bool useable=song.isStandardStream() ? !song.title.isEmpty() && !song.name().isEmpty() : !song.title.isEmpty() && !song.artist.isEmpty() && !song.album.isEmpty(); if (useable) { QString text=song.describe(false); if (song.time>0) { text+=QString(" – ")+Utils::formatTime(song.time); } MacNotify::showMessage(tr("Now playing"), text, CurrentCover::self()->image()); } } #else if (Settings::self()->showPopups() || trayItem) { bool useable=song.isStandardStream() ? !song.title.isEmpty() && !song.name().isEmpty() : !song.title.isEmpty() && !song.artist.isEmpty() && !song.album.isEmpty(); if (useable) { QString text=song.describe(false); if (song.time>0) { text+=QString(" – ")+Utils::formatTime(song.time); } if (trayItem) { #if defined Q_OS_WIN || defined Q_OS_MAC || !defined QT_QTDBUS_FOUND trayItem->setToolTip(tr("Cantata")+"\n\n"+text); // The pure Qt implementation needs both the tray icon and the setting checked. if (Settings::self()->showPopups() && isPlaying) { trayItem->showMessage(tr("Now playing"), text, QSystemTrayIcon::Information, 5000); } #else trayItem->setToolTip(tr("Cantata")+"\n\n"+text); #endif } #ifdef QT_QTDBUS_FOUND if (Settings::self()->showPopups() && isPlaying) { if (!notification) { notification=new Notify(this); } notification->show(tr("Now playing"), text, CurrentCover::self()->image()); } #endif } else if (trayItem) { trayItem->setToolTip(tr("Cantata")); } } #endif } #ifndef Q_OS_MAC static void copyMenu(Action *from, Action *to) { if (!to) { return; } to->setVisible(from->isVisible()); if (to->isVisible()) { if (!to->menu()) { to->setMenu(new QMenu(0)); } QMenu *m=to->menu(); m->clear(); foreach (QAction *act, from->menu()->actions()) { m->addAction(act); } } } #endif void TrayItem::updateConnections() { #ifndef Q_OS_MAC copyMenu(mw->connectionsAction, connectionsAction); #endif } void TrayItem::updateOutputs() { #ifndef Q_OS_MAC copyMenu(mw->outputsAction, outputsAction); #endif } cantata-2.2.0/gui/trayitem.h000066400000000000000000000044741316350454000157330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TRAYITEM_H #define TRAYITEM_H #include #include #include "support/icon.h" class QMenu; #include "config.h" #ifdef QT_QTDBUS_FOUND class Notify; #endif class MainWindow; class QImage; struct Song; class Action; class TrayItem : public QObject { Q_OBJECT public: TrayItem(MainWindow *p); virtual ~TrayItem() { } void showMessage(const QString &title, const QString &text, const QImage &img); void setup(); #ifdef Q_OS_MAC bool isActive() const { return false; } void setIcon(const QIcon &) { } void setToolTip(const QString &, const QString &, const QString &) { } #else bool isActive() const { return 0!=trayItem; } void setIcon(const QIcon &icon) { if (trayItem) { trayItem->setIcon(icon); } } void setToolTip(const QString &iconName, const QString &title, const QString &subTitle) { if (trayItem) { Q_UNUSED(iconName) Q_UNUSED(subTitle) trayItem->setToolTip(title); } } #endif void songChanged(const Song &song, bool isPlaying); void updateConnections(); void updateOutputs(); private Q_SLOTS: void trayItemClicked(QSystemTrayIcon::ActivationReason reason); private: #ifndef Q_OS_MAC MainWindow *mw; QSystemTrayIcon *trayItem; QMenu *trayItemMenu; #ifdef QT_QTDBUS_FOUND Notify *notification; #endif Action *connectionsAction; Action *outputsAction; #endif }; #endif cantata-2.2.0/http/000077500000000000000000000000001316350454000141065ustar00rootroot00000000000000cantata-2.2.0/http/httpserver.cpp000066400000000000000000000224021316350454000170200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "httpserver.h" #include "httpsocket.h" #ifdef TAGLIB_FOUND #include "tags/tags.h" #endif #include "gui/settings.h" #include "support/thread.h" #include "support/globalstatic.h" #include "mpd-interface/mpdconnection.h" #include #include #include #include #include static bool debugIsEnabled=false; #define DBUG if (debugIsEnabled) qWarning() << "HttpServer" << __FUNCTION__ #ifdef Q_OS_WIN static inline QString fixWindowsPath(const QString &f) { return f.length()>3 && f.startsWith(QLatin1Char('/')) && QLatin1Char(':')==f.at(2) ? f.mid(1) : f; } #endif void HttpServer::enableDebug() { debugIsEnabled=true; } bool HttpServer::debugEnabled() { return debugIsEnabled; } GLOBAL_STATIC(HttpServer, instance) #ifdef ENABLE_HTTP_SERVER HttpServer::HttpServer() : QObject(0) , thread(0) , socket(0) , closeTimer(0) { connect(MPDConnection::self(), SIGNAL(cantataStreams(QList,bool)), this, SLOT(cantataStreams(QList,bool))); connect(MPDConnection::self(), SIGNAL(cantataStreams(QStringList)), this, SLOT(cantataStreams(QStringList))); connect(MPDConnection::self(), SIGNAL(removedIds(QSet)), this, SLOT(removedIds(QSet))); connect(MPDConnection::self(), SIGNAL(ifaceIp(QString)), this, SLOT(ifaceIp(QString))); } bool HttpServer::start() { if (closeTimer) { DBUG << "stop close timer"; closeTimer->stop(); } if (socket) { DBUG << "already open"; return true; } DBUG << "open new socket"; quint16 prevPort=Settings::self()->httpAllocatedPort(); bool newThread=0==thread; if (newThread) { thread=new Thread("HttpServer"); } socket=new HttpSocket(Settings::self()->httpInterface(), prevPort); socket->mpdAddress(MPDConnection::self()->ipAddress()); connect(this, SIGNAL(terminateSocket()), socket, SLOT(terminate()), Qt::QueuedConnection); if (socket->serverPort()!=prevPort) { Settings::self()->saveHttpAllocatedPort(socket->serverPort()); } socket->moveToThread(thread); bool started=socket->isListening(); if (newThread) { thread->start(); } return started; } void HttpServer::stop() { if (socket) { DBUG; emit terminateSocket(); socket=0; } } void HttpServer::readConfig() { QString iface=Settings::self()->httpInterface(); if (socket && socket->isListening() && iface==socket->configuredInterface()) { return; } bool wasStarted=0!=socket; stop(); if (wasStarted) { start(); } } static inline QString serverUrl(const QString &ip, quint16 port) { return QLatin1String("http://")+ip+QLatin1Char(':')+QString::number(port); } QString HttpServer::address() const { return socket ? serverUrl(currentIfaceIp, socket->serverPort()) : QLatin1String("http://127.0.0.1:*"); } bool HttpServer::isOurs(const QString &url) const { if (0==socket || !url.startsWith(QLatin1String("http://"))) { return false; } foreach (const QString &ip, ipAddresses) { if (url.startsWith(serverUrl(ip, socket->serverPort()))) { return true; } } return false; } QByteArray HttpServer::encodeUrl(const Song &s) { DBUG << "song" << s.file << isAlive(); if (!start()) { return QByteArray(); } QUrl url; QUrlQuery query; url.setScheme("http"); url.setHost(currentIfaceIp); url.setPort(socket->serverPort()); url.setPath(s.file); if (!s.album.isEmpty()) { query.addQueryItem("album", s.album); } if (!s.artist.isEmpty()) { query.addQueryItem("artist", s.artist); } if (!s.albumartist.isEmpty()) { query.addQueryItem("albumartist", s.albumartist); } if (!s.composer().isEmpty()) { query.addQueryItem("composer", s.composer()); } if (!s.title.isEmpty()) { query.addQueryItem("title", s.title); } if (!s.genres[0].isEmpty()) { query.addQueryItem("genre", s.firstGenre()); } if (s.disc) { query.addQueryItem("disc", QString::number(s.disc)); } if (s.year) { query.addQueryItem("year", QString::number(s.year)); } if (s.time) { query.addQueryItem("time", QString::number(s.time)); } if (s.track) { query.addQueryItem("track", QString::number(s.track)); } if (s.isFromOnlineService()) { query.addQueryItem("onlineservice", s.onlineService()); } query.addQueryItem("id", QString::number(s.id)); query.addQueryItem("cantata", "song"); url.setQuery(query); DBUG << "encoded as" << url.toString(); return url.toEncoded(); } QByteArray HttpServer::encodeUrl(const QString &file) { Song s; #ifdef Q_OS_WIN QString f=fixWindowsPath(file); DBUG << "file" << f << "orig" << file; // For some reason, drag'n' drop of \\share\path\file.mp3 is changed to share/path/file.mp3! if (!f.startsWith(QLatin1String("//")) && !QFile::exists(f)) { QString share=f.startsWith(QLatin1Char('/')) ? (QLatin1Char('/')+f) : (QLatin1String("//")+f); if (QFile::exists(share)) { f=share; DBUG << "converted to share-path" << f; } } #ifdef TAGLIB_FOUND s=Tags::read(f); #endif s.file=f; #else DBUG << "file" << file; #ifdef TAGLIB_FOUND s=Tags::read(file); #endif s.file=file; #endif return encodeUrl(s); } Song HttpServer::decodeUrl(const QString &url) const { return decodeUrl(QUrl(url)); } Song HttpServer::decodeUrl(const QUrl &url) const { Song s; QUrlQuery q(url); if (q.hasQueryItem("cantata") && q.queryItemValue("cantata")=="song") { if (q.hasQueryItem("album")) { s.album=q.queryItemValue("album"); } if (q.hasQueryItem("artist")) { s.artist=q.queryItemValue("artist"); } if (q.hasQueryItem("albumartist")) { s.albumartist=q.queryItemValue("albumartist"); } if (q.hasQueryItem("composer")) { s.setComposer(q.queryItemValue("composer")); } if (q.hasQueryItem("title")) { s.title=q.queryItemValue("title"); } if (q.hasQueryItem("genre")) { s.addGenre(q.queryItemValue("genre")); } if (q.hasQueryItem("disc")) { s.disc=q.queryItemValue("disc").toInt(); } if (q.hasQueryItem("year")) { s.year=q.queryItemValue("year").toInt(); } if (q.hasQueryItem("time")) { s.time=q.queryItemValue("time").toInt(); } if (q.hasQueryItem("track")) { s.track=q.queryItemValue("track").toInt(); } if (q.hasQueryItem("id")) { s.id=q.queryItemValue("id").toInt(); } if (q.hasQueryItem("onlineservice")) { s.setIsFromOnlineService(q.queryItemValue("onlineservice")); } s.file=url.path(); s.type=Song::CantataStream; #ifdef Q_OS_WIN s.file=fixWindowsPath(s.file); #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (s.file.startsWith(Song::constCddaProtocol)) { s.type=Song::Cdda; } #endif DBUG << s.file << s.albumArtist() << s.album << s.title; } return s; } void HttpServer::startCloseTimer() { if (!closeTimer) { closeTimer=new QTimer(this); closeTimer->setSingleShot(true); connect(closeTimer, SIGNAL(timeout()), this, SLOT(stop())); } DBUG; closeTimer->start(1000); } void HttpServer::cantataStreams(const QStringList &files) { DBUG << files; foreach (const QString &f, files) { Song s=HttpServer::self()->decodeUrl(f); if (s.isCantataStream() || s.isCdda()) { start(); break; } } } void HttpServer::cantataStreams(const QList &songs, bool isUpdate) { DBUG << isUpdate << songs.count(); if (!isUpdate) { streamIds.clear(); } foreach (const Song &s, songs) { streamIds.insert(s.id); } if (streamIds.isEmpty()) { startCloseTimer(); } else { start(); } } void HttpServer::removedIds(const QSet &ids) { streamIds+=ids; if (streamIds.isEmpty()) { startCloseTimer(); } } void HttpServer::ifaceIp(const QString &ip) { DBUG << "MPD interface ip" << ip; if (ip.isEmpty()) { return; } currentIfaceIp=ip; ipAddresses.insert(ip); } #endif cantata-2.2.0/http/httpserver.h000066400000000000000000000053011316350454000164640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _HTTP_SERVER_H #define _HTTP_SERVER_H #include #include #include #include "mpd-interface/song.h" #include "config.h" class HttpSocket; class Thread; class QUrl; class QTimer; class HttpServer : public QObject { #if defined ENABLE_HTTP_SERVER Q_OBJECT #endif public: static void enableDebug(); static bool debugEnabled(); static HttpServer * self(); virtual ~HttpServer() { } #ifdef ENABLE_HTTP_SERVER HttpServer(); bool isAlive() const { return true; } // Started on-demamnd! void readConfig(); QString address() const; bool isOurs(const QString &url) const; QByteArray encodeUrl(const Song &s); QByteArray encodeUrl(const QString &file); Song decodeUrl(const QUrl &url) const; Song decodeUrl(const QString &file) const; #else HttpServer() { } bool isAlive() const { return false; } // Not used! void readConfig() { } QString address() const { return QString(); } bool isOurs(const QString &) const { return false; } QByteArray encodeUrl(const Song &) { return QByteArray(); } QByteArray encodeUrl(const QString &) { return QByteArray(); } Song decodeUrl(const QUrl &) const { return Song(); } Song decodeUrl(const QString &) const { return Song(); } #endif private: #ifdef ENABLE_HTTP_SERVER bool start(); private Q_SLOTS: void stop(); void startCloseTimer(); void cantataStreams(const QStringList &files); void cantataStreams(const QList &songs, bool isUpdate); void removedIds(const QSet &ids); void ifaceIp(const QString &ip); Q_SIGNALS: void terminateSocket(); private: Thread *thread; HttpSocket *socket; QSet streamIds; // Currently playing MPD stream IDs QTimer *closeTimer; QString currentIfaceIp; QSet ipAddresses; #endif }; #endif cantata-2.2.0/http/httpsocket.cpp000066400000000000000000000422001316350454000170000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "httpsocket.h" #include "httpserver.h" #include "gui/settings.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "devices/cdparanoia.h" #include "devices/extractjob.h" #endif #include #include #include #include #include #include #include #include #include #define DBUG if (HttpServer::debugEnabled()) qWarning() << "HttpSocket" << __FUNCTION__ static const quint64 constMaxBuffer = 32768; static QString detectMimeType(const QString &file) { QString suffix = QFileInfo(file).suffix().toLower(); if (suffix == QLatin1String("mp3")) { return QLatin1String("audio/mpeg"); } if (suffix == QLatin1String("ogg")) { return QLatin1String("audio/ogg"); } if (suffix == QLatin1String("flac")) { return QLatin1String("audio/x-flac"); } if (suffix == QLatin1String("wma")) { return QLatin1String("audio/x-ms-wma"); } if (suffix == QLatin1String("m4a") || suffix == QLatin1String("m4b") || suffix == QLatin1String("m4p") || suffix == QLatin1String("mp4")) { return QLatin1String("audio/mp4"); } if (suffix == QLatin1String("wav")) { return QLatin1String("audio/x-wav"); } if (suffix == QLatin1String("wv") || suffix == QLatin1String("wvp")) { return QLatin1String("audio/x-wavpack"); } if (suffix == QLatin1String("ape")) { return QLatin1String("audio/x-monkeys-audio"); // "audio/x-ape"; } if (suffix == QLatin1String("spx")) { return QLatin1String("audio/x-speex"); } if (suffix == QLatin1String("tta")) { return QLatin1String("audio/x-tta"); } if (suffix == QLatin1String("aiff") || suffix == QLatin1String("aif") || suffix == QLatin1String("aifc")) { return QLatin1String("audio/x-aiff"); } if (suffix == QLatin1String("mpc") || suffix == QLatin1String("mpp") || suffix == QLatin1String("mp+")) { return QLatin1String("audio/x-musepack"); } if (suffix == QLatin1String("dff")) { return QLatin1String("application/x-dff"); } if (suffix == QLatin1String("dsf")) { return QLatin1String("application/x-dsf"); } if (suffix == QLatin1String("opus")) { return QLatin1String("audio/opus"); } return QString(); } static void writeMimeType(const QString &mimeType, QTcpSocket *socket, qint32 from, qint32 size, bool allowSeek) { if (!mimeType.isEmpty()) { QTextStream os(socket); os.setAutoDetectUnicode(true); if (allowSeek) { if (0==from) { os << "HTTP/1.0 200 OK" << "\r\nAccept-Ranges: bytes" << "\r\nContent-Length: " << QString::number(size) << "\r\nContent-Type: " << mimeType << "\r\n\r\n"; } else { os << "HTTP/1.0 200 OK" << "\r\nAccept-Ranges: bytes" << "\r\nContent-Range: bytes " << QString::number(from) << "-" << QString::number(size-1) << "/" << QString::number(size) << "\r\nContent-Length: " << QString::number(size-from) << "\r\nContent-Type: " << mimeType << "\r\n\r\n"; } DBUG << mimeType << QString::number(size) << "Can seek"; } else { os << "HTTP/1.0 200 OK" << "\r\nContent-Length: " << QString::number(size) << "\r\nContent-Type: " << mimeType << "\r\n\r\n"; DBUG << mimeType << QString::number(size); } } } static int getSep(const QByteArray &a, int pos) { for (int i=pos+1; i split(const QByteArray &a) { QList rv; int lastPos=-1; for (;;) { int pos=getSep(a, lastPos); if (pos==(lastPos+1)) { lastPos++; } else if (pos>-1) { lastPos++; rv.append(a.mid(lastPos, pos-lastPos)); lastPos=pos; } else { lastPos++; rv.append(a.mid(lastPos)); break; } } return rv; } static bool isFromMpd(const QStringList ¶ms) { foreach (const QString &str, params) { if (str.startsWith("User-Agent:") && str.contains("Music Player Daemon")) { return true; } } return false; } static void getRange(const QStringList ¶ms, qint32 &from, qint32 &to) { foreach (const QString &str, params) { if (str.startsWith("Range:")) { int start=str.indexOf("bytes="); if (start>0) { QStringList range=str.mid(start+6).split("-", QString::SkipEmptyParts); if (1==range.length()) { from=range.at(0).toLong(); } else if (2==range.length()) { from=range.at(0).toLong(); to=range.at(1).toLong(); } } break; } } } HttpSocket::HttpSocket(const QString &iface, quint16 port) : QTcpServer(0) , cfgInterface(iface) , terminated(false) { if (!openPort(port)) { openPort(0); } DBUG << isListening() << serverPort(); connect(MPDConnection::self(), SIGNAL(socketAddress(QString)), this, SLOT(mpdAddress(QString))); connect(MPDConnection::self(), SIGNAL(cantataStreams(QList,bool)), this, SLOT(cantataStreams(QList,bool))); connect(MPDConnection::self(), SIGNAL(cantataStreams(QStringList)), this, SLOT(cantataStreams(QStringList))); connect(MPDConnection::self(), SIGNAL(removedIds(QSet)), this, SLOT(removedIds(QSet))); connect(this, SIGNAL(newConnection()), SLOT(handleNewConnection())); } bool HttpSocket::openPort(quint16 p) { setProxy(QNetworkProxy::NoProxy); if (listen(QHostAddress::Any, p)) { return true; } if (listen(QHostAddress::LocalHost, p)) { return true; } return false; } void HttpSocket::terminate() { if (terminated) { return; } DBUG; terminated=true; close(); deleteLater(); } void HttpSocket::handleNewConnection() { DBUG; while (hasPendingConnections()) { QTcpSocket *socket = nextPendingConnection(); // prevent clients from sending too much data socket->setReadBufferSize(constMaxBuffer); static const QLatin1String constIpV6Prefix("::ffff:"); QString peer=socket->peerAddress().toString(); QString ifaceAddress=serverAddress().toString(); const bool hostOk=peer==ifaceAddress || peer==mpdAddr || peer==(constIpV6Prefix+mpdAddr) || peer==QLatin1String("127.0.0.1") || peer==(constIpV6Prefix+QLatin1String("127.0.0.1")); DBUG << "peer:" << peer << "mpd:" << mpdAddr << "iface:" << ifaceAddress << "ok:" << hostOk; if (!hostOk) { sendErrorResponse(socket, 400); socket->close(); DBUG << "Not from valid host"; return; } connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); connect(socket, SIGNAL(disconnected()), this, SLOT(discardClient())); } } void HttpSocket::readClient() { if (terminated) { return; } QTcpSocket *socket = static_cast(sender()); if (socket->bytesAvailable() >= constMaxBuffer) { // Request too large, reject sendErrorResponse(socket, 400); socket->close(); DBUG << "Request too large"; return; } if (socket->canReadLine()) { QList tokens = split(socket->readLine()); // QRegExp("[ \r\n][ \r\n]*")); if (tokens.length()>=2 && "GET"==tokens[0]) { QStringList params = QString(socket->readAll()).split(QRegExp("[\r\n][\r\n]*")); DBUG << "params" << params << "tokens" << tokens; if (!isFromMpd(params)) { sendErrorResponse(socket, 400); socket->close(); DBUG << "Not from MPD"; return; } QUrl url(QUrl::fromEncoded(tokens[1])); QUrlQuery q(url); bool ok=false; qint32 readBytesFrom=0; qint32 readBytesTo=0; getRange(params, readBytesFrom, readBytesTo); DBUG << "readBytesFrom" << readBytesFrom << "readBytesTo" << readBytesTo; if (q.hasQueryItem("cantata")) { Song song=HttpServer::self()->decodeUrl(url); if (!isCantataStream(song.file)) { sendErrorResponse(socket, 400); socket->close(); DBUG << "Not cantata stream file"; return; } if (song.isCdda()) { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND QStringList parts=song.file.split("/", QString::SkipEmptyParts); if (parts.length()>=3) { QString dev=QLatin1Char('/')+parts.at(1)+QLatin1Char('/')+parts.at(2); CdParanoia cdparanoia(dev, false, false, true); if (cdparanoia) { int firstSector = cdparanoia.firstSectorOfTrack(song.id); int lastSector = cdparanoia.lastSectorOfTrack(song.id); qint32 totalSize = ((lastSector-firstSector)+1)*CD_FRAMESIZE_RAW; int count = 0; bool writeHeader=0==readBytesFrom; // Only write header if we are not seeking... // int bytesToDiscard = 0; // Number of bytes to discard in first read sector due to range request in HTTP header // if (readBytesFrom>=ExtractJob::constWavHeaderSize) { // readBytesFrom-=ExtractJob::constWavHeaderSize; // } // if (readBytesFrom>0) { // int sectorsToSeek=readBytesFrom/CD_FRAMESIZE_RAW; // firstSector+=sectorsToSeek; // bytesToDiscard=readBytesFrom-(sectorsToSeek*CD_FRAMESIZE_RAW); // } cdparanoia.seek(firstSector, SEEK_SET); ok=true; writeMimeType(QLatin1String("audio/x-wav"), socket, readBytesFrom, totalSize+ExtractJob::constWavHeaderSize, false); if (writeHeader) { ExtractJob::writeWavHeader(*socket, totalSize); } bool stop=false; while (!terminated && (firstSector+count) <= lastSector && !stop) { qint16 *buf = cdparanoia.read(); if (!buf) { break; } char *buffer=(char *)buf; qint32 writePos=0; qint32 toWrite=CD_FRAMESIZE_RAW; // if (bytesToDiscard>0) { // int toSkip=qMin(toWrite, bytesToDiscard); // writePos=toSkip; // toWrite-=toSkip; // bytesToDiscard-=toSkip; // } if (toWrite>0 && !write(socket, &buffer[writePos], toWrite, stop)) { break; } count++; } } } #endif } else if (!song.file.isEmpty()) { #ifdef Q_OS_WIN if (tokens[1].startsWith("//") && !song.file.startsWith(QLatin1String("//")) && !QFile::exists(song.file)) { QString share=QLatin1String("//")+url.host()+song.file; if (QFile::exists(share)) { song.file=share; DBUG << "fixed share-path" << song.file; } } #endif QFile f(song.file); if (f.open(QIODevice::ReadOnly)) { qint32 totalBytes = f.size(); writeMimeType(detectMimeType(song.file), socket, readBytesFrom, totalBytes, true); ok=true; qint32 readPos = 0; qint32 bytesRead = 0; if (0!=readBytesFrom) { if (!f.seek(readBytesFrom)) { ok=false; } bytesRead+=readBytesFrom; } if (0!=readBytesTo && readBytesTo>readBytesFrom && readBytesTo!=totalBytes) { totalBytes-=(totalBytes-readBytesTo); } if (ok) { static const int constChunkSize=32768; char buffer[constChunkSize]; bool stop=false; do { bytesRead = f.read(buffer, constChunkSize); readPos+=bytesRead; if (!write(socket, buffer, bytesRead, stop) || f.atEnd()) { break; } } while (readPosclose(); if (QTcpSocket::UnconnectedState==socket->state()) { delete socket; } } else { // Bad Request sendErrorResponse(socket, 400); socket->close(); DBUG << "Bad Request"; return; } } } void HttpSocket::discardClient() { static_cast(sender())->deleteLater(); } void HttpSocket::mpdAddress(const QString &a) { mpdAddr=a; } bool HttpSocket::isCantataStream(const QString &file) const { DBUG << file << newlyAddedFiles.contains(file) << streamIds.values().contains(file); return newlyAddedFiles.contains(file) || streamIds.values().contains(file); } void HttpSocket::sendErrorResponse(QTcpSocket *socket, int code) { QTextStream os(socket); os.setAutoDetectUnicode(true); os << "HTTP/1.0 " << code << " OK\r\n" "Content-Type: text/html; charset=\"utf-8\"\r\n" "\r\n"; } void HttpSocket::cantataStreams(const QStringList &files) { DBUG << files; foreach (const QString &f, files) { Song s=HttpServer::self()->decodeUrl(f); if (s.isCantataStream() || s.isCdda()) { DBUG << s.file; newlyAddedFiles+=s.file; } } } void HttpSocket::cantataStreams(const QList &songs, bool isUpdate) { DBUG << isUpdate << songs.count(); if (!isUpdate) { streamIds.clear(); } foreach (const Song &s, songs) { DBUG << s.file; streamIds.insert(s.id, s.file); newlyAddedFiles.remove(s.file); } } void HttpSocket::removedIds(const QSet &ids) { foreach (qint32 id, ids) { streamIds.remove(id); } } bool HttpSocket::write(QTcpSocket *socket, char *buffer, qint32 bytesRead, bool &stop) { if (bytesRead<0 || terminated) { return false; } qint32 writePos=0; do { qint32 bytesWritten = socket->write(&buffer[writePos], bytesRead - writePos); if (terminated || -1==bytesWritten) { stop=true; break; } socket->flush(); writePos+=bytesWritten; } while (writePosstate()) { socket->waitForBytesWritten(); } if (QAbstractSocket::ConnectedState!=socket->state()) { return false; } return true; } cantata-2.2.0/http/httpsocket.h000066400000000000000000000041121316350454000164450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _HTTP_SOCKET_H_ #define _HTTP_SOCKET_H_ #include #include #include #include struct Song; class QHostAddress; class QTcpSocket; class HttpSocket : public QTcpServer { Q_OBJECT public: HttpSocket(const QString &iface, quint16 port); virtual ~HttpSocket() { } QString configuredInterface() { return cfgInterface; } quint16 boundPort(); public Q_SLOTS: void terminate(); void mpdAddress(const QString &a); private: bool openPort(quint16 p); bool isCantataStream(const QString &file) const; void sendErrorResponse(QTcpSocket *socket, int code); private Q_SLOTS: void handleNewConnection(); void readClient(); void discardClient(); void cantataStreams(const QStringList &files); void cantataStreams(const QList &songs, bool isUpdate); void removedIds(const QSet &ids); private: bool write(QTcpSocket *socket, char *buffer, qint32 bytesRead, bool &stop); void setUrlAddress(); private: QSet newlyAddedFiles; // Holds cantata strema filenames as added to MPD via "add" QMap streamIds; // Maps MPD playqueue song ID to fileName QString cfgInterface; QString mpdAddr; bool terminated; }; #endif cantata-2.2.0/icons/000077500000000000000000000000001316350454000142425ustar00rootroot00000000000000cantata-2.2.0/icons/CMakeLists.txt000066400000000000000000000077671316350454000170230ustar00rootroot00000000000000if (NOT WIN32 AND NOT APPLE) macro (update_iconcache ICON_THEME) # Update mtime of hicolor icon theme dir. # We don't always have touch command (e.g. on Windows), so instead create # and delete a temporary file in the theme dir. install(CODE " set(DESTDIR_VALUE \"\$ENV{DESTDIR}\") if (NOT DESTDIR_VALUE) file(WRITE \"${CMAKE_INSTALL_PREFIX}/share/icons/${ICON_THEME}/temp.txt\" \"update\") file(REMOVE \"${CMAKE_INSTALL_PREFIX}/share/icons/${ICON_THEME}/temp.txt\") endif (NOT DESTDIR_VALUE) ") endmacro (update_iconcache) endif (NOT WIN32 AND NOT APPLE) if (WIN32 OR APPLE) install(FILES cantata.svg DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/svg) install(FILES cantata16.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/16 RENAME cantata.png) install(FILES cantata22.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/22 RENAME cantata.png) install(FILES cantata32.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/32 RENAME cantata.png) install(FILES cantata48.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/48 RENAME cantata.png) install(FILES cantata64.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/64 RENAME cantata.png) install(FILES cantata128.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/128 RENAME cantata.png) install(FILES cantata256.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/256 RENAME cantata.png) install(FILES cantata512.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/512 RENAME cantata.png) else (WIN32 OR APPLE) install(FILES cantata.svg DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/scalable/apps) install(FILES cantata-symbolic.svg DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/scalable/apps) install(FILES cantata16.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/16x16/apps RENAME cantata.png) install(FILES cantata22.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/22x22/apps RENAME cantata.png) install(FILES cantata24.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/24x24/apps RENAME cantata.png) install(FILES cantata32.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/32x32/apps RENAME cantata.png) install(FILES cantata48.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/48x48/apps RENAME cantata.png) install(FILES cantata64.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/64x64/apps RENAME cantata.png) install(FILES cantata128.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/128x128/apps RENAME cantata.png) install(FILES cantata256.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/256x256/apps RENAME cantata.png) install(FILES cantata512.png DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/512x512/apps RENAME cantata.png) endif (WIN32 OR APPLE) if (NOT WIN32 AND NOT APPLE) update_iconcache(hicolor) if (EXISTS /etc/lsb-release) file(READ "/etc/lsb-release" LSB_RELEASE_CONTENTS) string(REGEX MATCH "DISTRIB_ID=Ubuntu" IS_UBUNTU ${LSB_RELEASE_CONTENTS}) if (IS_UBUNTU) set(INSTALL_UBUNTU_ICONS_DEFAULT ON) else(IS_UBUNTU) set(INSTALL_UBUNTU_ICONS_DEFAULT OFF) endif(IS_UBUNTU) else(EXISTS /etc/lsb-release) set(INSTALL_UBUNTU_ICONS_DEFAULT OFF) endif(EXISTS /etc/lsb-release) option(INSTALL_UBUNTU_ICONS "Install the Ubuntu themed monochrome panel icons" ${INSTALL_UBUNTU_ICONS_DEFAULT}) if (INSTALL_UBUNTU_ICONS) install(FILES trayicon-mono-dark.svg DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/ubuntu-mono-light/apps/22 RENAME cantata-panel.svg) install(FILES trayicon-mono-light.svg DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/ubuntu-mono-dark/apps/22 RENAME cantata-panel.svg) update_iconcache(ubuntu-mono-light) update_iconcache(ubuntu-mono-dark) endif (INSTALL_UBUNTU_ICONS) endif (NOT WIN32 AND NOT APPLE) if (NOT ENABLE_KDE_SUPPORT) add_subdirectory(theme) endif (NOT ENABLE_KDE_SUPPORT) #if (APPLE) # install(FILES trayicon-mono-dark.svg DESTINATION ${CANTATA_ICON_INSTALL_PREFIX}/scalable/apps RENAME cantata-panel.svg) #endif (APPLE) cantata-2.2.0/icons/cantata-symbolic.svg000066400000000000000000000015611316350454000202200ustar00rootroot00000000000000 cantata-2.2.0/icons/cantata.svg000066400000000000000000000014671316350454000164060ustar00rootroot00000000000000 cantata-2.2.0/icons/cantata1024.png000066400000000000000000001411401316350454000166730ustar00rootroot00000000000000PNG  IHDR+sBIT|d pHYsnnޱtEXtSoftwarewww.inkscape.org<IDATx tU񂳭U;iGmXvziZmkmr&\yT a4A(h L$zW{?>h2۶?+7=OY5]f79+#HS!S #|& yHO^W?_H4}K6Bfl:_+/SMM,Mgs#I>/k+F6jesK bI_ Vϧh* O̖%YeI0TvnݽԝawdJhGad4C۷$gDmۿVwre,ixof8jO7dfX5DGcbbp&@\/ Д 80&6Mq϶xgs&za`/M$_pҰ_Mԣu4_,Ȳm;U}u$$$\njd6S~= !?-p!,xQ &&۶ֲ3eNQӧ>UZ3 sjLgOn|\|Y@X&e^ohHhE\\۶|4l,nWw/{R˲V6|>_6pI yv(g>BOzm@=.x<R%,+a#4K8>2oI~FfBt 9;iq5 ?˗C5,4\MCS ?66fQ@c>kxWEL1M !9Đ DKs2y4,0JfLoo0 ZmƄ.p-33"GI/>G)rZ^?hh,T| BA_xݶ^G43}2eJh&!HhKfyBoYyBsau1+]vM; ) 0GGn'jxGBoQ.hӟ m}Bi@*]속QBH6 Xu ! {]+Yh'111c@4{dA:J9!D$jU۶S_Qa%@ȂfY|eE!N!QMzJv16@(jC*۶SeYJM!eYdqqq_j((P\BU[z~5dHh^SUHRLB)jxrnxlBB re@x<6_EqL!8ᩀ ,2ÄڼcYa'6~B!h;Y- w[N(r !\&u ] `#Y͕%ҔX+LkMR˂!6#m;4B ^2iְqYSB!m[- /͟BHS>)? BjGfYhS]NB!Lf2ֲ,,RdB1`z4E$bXWiX.#&&sJ" HB!Њ?W\NH!I,kz_Ê\KDh>;BqAٶpp Y}[Bi !0y \OE??!…@O!*'dx<?!…x"Ez :B!I9fv7^ ZC-'BZ2@ p5Іl?+2mBHHW#VT jڵkw,J2(!fCT ~j"" 2B!$ɒjF( | 0B!${RRPkecOE!hZ˲5˔*dQSY`dSdB!ڦ\m۾}OR&z_lz +B!ĈN?{xɦ.(Aa@! c|)˻BqYV۶e*A+Er[L@!XHUpzV.zY !BzB8A6rg'By?2P-rraO!rz)H0R Z}VB!m{eYRE"yŜB!IZSQ>'sB!O|_hI.R76!BH#S2Ret/ByKtUO(&O,O.,X -Z$233ŪUĺuƍ͛ENNػw8t林R[[+>L][[;ԿK;տ{Æ ߤoU]EM9քlxD[+0hx_E":v(z-F)Ə/M&^|ErJoݻweYwQEw7$) l֬Y#rssEUU(PYYjl16TcsIJQ$&&n )!K||5jT}}ugt0IzJ[nۄD>KPH.QJHx:T̞=[deeQ__O GQcZm5g͚|uXwTKJHHN]1f$$QKg̙bӦM݇kUTT?;tQOIBBš-U.dv\$P_}V-===𗖖 6LLLqqq!6j#T ٨R. 'X iYy۷ͻ !u.sIJy|*\N.~>GH󒐐 RRRĺuę3g܀0PTOѨgzCH٪x &l\zSŃ+G?j"ō6tE/37ٶ(3*&&r2aA#nN׮]ŴiӂMM UWW[/9eT`ޝɜ`!#n\ӯvj5B 6sd[m;Z.$2n>Me j%' op|O}@ȝ?>xaO fPbfl*G,^X Q{\xE9`d.49˂Dݻ cǎ]0͛'ˉ@zJ%m`"NۯT꓂+lHu2P@X]Nu,> GGq DFFڵ+s>qZvٶe*s qBF_YYI񪫫Evvvpc J%:VN,4tQ pBuY+u.]|BN+X`ɛG`AIIzb ҶP@ v-,, >M .l8rHbrmKdY]2,(İo &zh}XSu@r hM%Mz"',$Ĥ N<)EΝYcFmr,a &$!!A,YDR@+W={TpOr,g o߾ >3d "&e绉 .ANdY,6lX"۶mO<k1"emd 5F D0PmE H"?kDnn.6h|!`̘1YDT_fdȂ@tLbbؿ?5B]y|x<88pJ .ު mS$3nj_ ˑ#GDJJ )Q+1?%ǏRRwǎ˚GtKt۶ '|Rٳ\BOR)]IHHβeL$2dشi0TNN1bk"%Umnce D3}֭uuuTrg$Z\zk_NjI 233EMM /jkk{ɚIjG+&&meB'HΝŲeDUU.*++/,:uJZY;w`.]|BNbI>&hMry.|>k*E˲tLzf9yN"DG hgbY[I.JN8SxI8vVNOOgwk5v@XY5 #Fǎr8BAAxgXI8sZС 9Ɍb%N.]ʕ+E}}="Qڦ`ӭ[7||N@Hٶݍ :))) hjc@엹@rRcr%/''*;wd@h׮ݭt.ZIJejTI(b۶9s&\Km\ HvLL`4ƨL) ]={P AQ#Pg㹖N@| ,(?}@] $Ih47dN0yfȐ!Tx\H(BW$'L%Qw2:VdffXj n|$|ʶL%Qw0:D%@3MBd 47=z۷ob rrrD^5HKSnz\?47)))* 0(//9EfB$I||Φ2 6m$vJ BZqqqm= 1!&%))IQAgΜ"ٱ,ksLL5T0~oɒ%* (Pkʕ+E\\ in[gHSҷo_K'wOBtE t?ĤGɓ'2-4pDw8ɾ 46~?!ЗڔSN.iG8P h-OLt1b(((j>|85 ij*^[FܣH#颦j TWW9sPϐ~]tLCFΝ;7RA`-[xjҔ_I\)TM8z%`ذa8)Yx}NLfJIKKUUUTK8z%`:)K$OLbrر#p+>>L7D=cY2&/rG@۶mS䊑* yO>* ԧg2n e619Keܸq* LD.z۶Asy&&DQwH:tJW!ƾ)t^@LHԩؼy3 c/1+%Vm<>MHY#69ЦM5MȇӧOGHO?[r7~X+x #G^zQS6tz¦9r(..z`$m?{ض$?"gϞO<aS@@W&O,Z땍_տO'%e̅@KUWW{) meMg!'==퀅-p!ٹ@ bɒ%ZM(6ݙpȅʼn,*}tpP\Y`l B`Æ cǎ^߮][Ѐ6#ҭ[7w^vUqgŢ7-+wN F>)2٦SB,66SL4Dej#TV׊OuRX.\@^9~lN<)H-F>۶ҭ!Z&OSp'zMY˅JKKŨQO?kBD6C\ĉ?#".LqhBmF.Ntn@ 72L*dܹqO6䑼p!dp={QWW'fϞMF.~`㹊ht i<DcnJq<#2 +W *l!83˲V1𙿷zc(~v/"u6 "66ڍٶݖnhO3;:u;v젪Xj_GF,'J98@xj8r&66::{{ 6z.d"!!Zx/fLM>}DAAUyt-/\_Gd $Q 5I.#Vg2Y7 gΜzx'ϖEm4abv޲~{ĹsiWоiJKKȑ#I,25LO?-˩?|٨ *|hOMPYY) sCLEUUUP mwB~? C>QFL4s`>;*jkkʮCm/bPՉiӦQ;^t~Ľ3D=#x.=}NJ8gԀ.eY>&:@<3!/3gΤZ1Tnx`O0w\jAwg] \)!!:۶2 /ϧZI#uB~=]S.̛7ő=ctp9ܗ p'i:.K/Q72w5^堯wW222X؅}B_\Zx15{x3Ų4F ,q O$̤Vto p+K.euwOu xwI>ET|!;Ntw_fUE*D6rM$1 me b jGw&Gz:E8g8ݓ%KEl=| e p vteF-iZٶ '7%elr.2uW5-#Nldp ;q3 ,\}9nseY9ݑtVmw(WOTV4p^x}yF @~1cSOp)eD.6iUS͞=e,AH`Ndw矧ظ8~#4 .0k,jMwdLL$a]mKNbgԩ4psW72_ۏ2h=5n=eY9ydQWW W0bV}Һ(fjkk=ݕG*a ۶q:?<󌨮0 ]1Sźmy ^UUx駩AݓOYB{>[rVp:;ÇeeeH?6O&"f"P'3.-z0`())a&_&̷0_m*l=`?^ <=]&t'ӻwoqiV_hs4!J6ΕU19Hۗ9nӄe$unYu`sgTja\ɓGԨH &tJ89xq]@sMƧJx.uaѹsgjUβ_rB91Xk.VYh7а1%ٹ 4ݻE\\5s0&&ta/YIض-X]zQ@<#% 8Ά 5H ܼ+B1ALnEocu-[Fl=ͬYXM ~=< O*iܹsa~:QDkr@UpR9':uyyy>m>|8X R;rcTWRN(gqFV?}aBq0N&6v\d~LoÉ䬼KzA?ف{NP^^.Loth)qF:w,Y THqO D||<52&LNlmw* ZiDֶnʦK-ߏvq@^~eV5Zexbjg5[\V h-FNgdҤI ^Qt#q„ eY#ˉ? z ic3a$at~ R$ NӱcGqaV1=ygh][0e^/..uINg7` l=ICy"QpYn5r~r`rrTV-ۻiy@^ƙ6m2o':tbM=&T.aoZ >9/PIav:w,?jY!_~n{4E,Ā1N8!㩵EtPw_0~cyfV)̲K> > `֭Z|vwN󓞞zhx%sGwps̡vN66 y<I`{-~MK>2$ Di8VSS~Ίn؅lێcN:?hlƫ;itsWTΣ0К@9E LHH]wo,(deeƞ[4*W>I H[/jj9qS;g^t." |3eV![hnIܓ /cw?onbccŁX}O}4;?6G;É#qLnx`; zctѽ?hN/^|Ƒ8.K\-j~#3]tx$$$\'q9ٵk={i#sgTn^gDm߾۶;1ɓEAHk&D&L@`^~5tZ~όD:9&ɋ(L)'2 äF)//ݻw0+s3ˌ_\^\$0ݨe%ajQUS+Zv-=Y9c .3l0QWW .v/Q- ˽āg$p2dA,tmg`;w2k;CE[D6&}%7cem۶¬dѵeYTgܸqΣ4zam|~n6Ѻ~. L[/jjyjӹKu (ıcǘbޚ4ya7#i˾ W4pIyyy^Ø̦{Lg֬Ygl c~? Ō6b%M>^ÜTN>&#3NΝEqq134 HvNc ۯۖY1ɹoz|PIII֦0&x>,[?[DSuwB|IV/sL"%Ks|e0NϞ=EUU33}Sg4s㉯\:tJ|S ׽ibB&RуÐx޿7m?e韵k2+>@L#HYӨpX3\qbL&xߪU=ɫt:GO0#WQUwÜa3l8~T;ǽ&jjXUMbDcccNG~fÕ1xΆ {-¦&3;q3 /@eeeу˲EWߴ8zgСjƃ4maM>.jcugWo=r6l9xo }vfap3 [`ݞfU}$F]n˖-"Cg߸cQF1.ILި,Z?<6j(d&7{ꩧIj]`;{e\}=Ѥ9ٻM|?Scq)Uӓ&e`3>gD:tǩ^xGg{/}^K%&&қ˿߇w<Ȍ %4fUbV8NB&ftJ8{$Gi"3\I] N6bĬ,6t1cУ۶տ_0@΁i)-s; YӺQNOuI^kxkbs@7)c7?\A#| a9v"@O@ "{:۶Lsa280WDZ҄9_a;~"@)k Ͻĩr&$)c2gPq1#Ӏ9;=q>^1cL}f*wH;5⏃rl OR&&1)--:u8>nio3[o%4Rɳ]/>J17ی19ܹsqN[.`kyJ2S02Ut1287<0NlsAN<)lۦ7@j7\/&Mb4wfD9׶8sB|<|SıS$99^GضͿs޽{!v'*#ycq Ϻ{nz3wsˈ#-N<7wL5b,:tJ3/u&5>|8=)x<78eY;9%++lť4M;2fL~T\q7CӪM":țoIϣq^_/ݻw̌f;p,MSf}giv1;C8XP555G>&é8eҥ̊Q;0?ȸr 8ŋ}M￑I'JJJ-ƃ4Kwtc@z9e cǎ@U1ZlΪۤFz!bFgr @]NJi,˚ 8̄;4JȐo;Fk.8/}(:Wdg\z }Sxu T>@( &MR̂F79f酴{/ʿL=T_V E jC=)w{ihx|k׮4m:TNjJf@@H4cʫ^H˜x9r$ n4BzD '8f凝gz&CC1^IضoT6.镵k2B.}nnyWN39ek; j*z%=ܴGpIǎEYY3 & Pns]3|hݠ>UTY@8z&Ro4&>IMMevŰoE m,tոzw2LJIIg3|NqINN3 ,$@<#nlǽƱ7(w{ }vz&=3ٔsI>}D=1 ƾ꺱UQU#g?|Tлwoz'rĈ e-`ŋ3w}_@z?|pO0¦f'By\?gc,_ig{z).Mvz;v, (ىPVm9̀3d{ X=3R]>f;щP69΀K+11a@1+Z~=~ru0G:v(˙aC4:޼3 rƄcʼnRaTz z*U.XuGL5it"gh.䩹o3X 4i$z*>Us7F._DH\&'BQ.?:/n~8a'wa͛7S=A#]v555\1+&'I K<aE||<^ uGMƬnVD D%;/a.cDč}jTz+}-s9#۶mcD?z'jTGزhf DW5+y:4~8 zsGv6^@` 9Ɖy:u++`PQ)1 ~k2ԧP隷6<)))Xz,!!:X#Q"|&yhK +xfFƊƹXqj7cDD%" 17>0&i +_1^4;Q1赴Ѿ ݎ33M5u Tdggk镊`Y*B'y 9[sOD(6n}n[0eee^K<* Z D?ǏgvDԪ-ih"[pT_/ďfh~3H 4vXz.20*^OȺu똙5e\dn84Ngh\z%3ZQTTID(/D?"B~gHKf&BywI\։<GXϼ5… z [^%eU懓CUDq1mstޝLD@:?z3}:;x6YTkp}208 FQbcc y֭|wH3Z`BƑ2 6nHkvD/G?8Jf"@T|42=) A%۾ *// b\HG$f!@TTTD(z|&.X,7=4AԱFM/OEh~gŊ@8Zx&&BI9 :sB\'rD @˖-'111۶cG?yyy@~#6^`O,e9 Gcٳ<ybv6 L2|&C/} PO/(:t|8/lGn ¬)kh`"M Q3JIYP7$e= qQ4/JZv\W%i̙CoOk_G?;v`DU i^"2B7fJ}P[n7 U?pt+*++qQ4/|+H ƕ>Qg˅G#Ł@u8ˏs^g~|Ϝs F l{ .NpDN!Km޽ܕ[94>Jʰ%=Т{s x6>a`ONS* w;qF* (âm -:z2N!DLg46l é`T,/h@:uJ0mAB<֮/5Zi"N8AS=]OJ0zN*EGjt`".zi"8P} @ޟТwI15y$i2JoM``0Ɠgll,2v%E'V C|BS:ɘ:u*YN nC4<׬YCxubBN>ayBc غmC+Wb٤/Vx̤Rgp |: 9|0YN `BI=AQA@)o:E'9pdBR Rh9h (t|%t80u"@4)'wKS MZ:n{r"ƭLC &ݤ &d*(E^Q9EGDS_f?'ɷM`&={P9@)d~0s;ͤdзNiRvE3UVu & .P9@)'h]t:7s:"jy9Av Ɯƒnw EG˪tn@;l[ҳ^731;Xr Z;Q/ѯA."7 6-F =<FG_NLU~=~awA<2o c@@Rr$8IwF.5%Cz"ehF本'պ6 Faè$q+ 0:m:]3v}H= ERL֓E#z`$G 7o!FGLLk"U5u◁lWՏ?I'RƏO֓n !H5*2(v !FGG&J EmBȹJbb"YO6[-HrԮP[jNXNV:é ^ڵkzww/#σR!@I >'ף-h(r\hD;Ы W+Es6/[HFsNdCCC\`"LVN@ ۑ\n;t޴ikyׁqr__{h9P @YV HG_4}gL4/[ȕ6ɀr s-h 9Κ5JJ|1o4}GL x̌3ȀrZ!yQ @IJA:kXmq2qTJRRP1d*(ɜB}'z>m6\,q"ngd@~Zlr\r%$-aHgCf\4?G|r2DVB?(;v 5 Wi 9;V&3]o=k~y `>aWJ)zqCކi 9fddP @I#tV;x:ϳ4?4qb1t!== ( ]h 98qJJVh2Igf8dz4t*N_(؁n;v (S gi 9={JJ%$a.YN'r )t+O&5a Ɛcnn.g%-1|Yw:Ĉg/S@wrrrȀrZp4$7%lB =35Sy8B^^Ph 9P @Ix> ffQ\| (Wzr,*P*>94gw.N2h_~za@CW^rh' ?S*=gr HG (†h 9R @9vHpٿK2M9t&gm%5PO2T] 6?!Dz2*(Ǫ]gO:ŦC?\%ƇKDeu- A (Ւrr$?BN sh A1 ʵa@Clx/!Jg{LXg>%;LTUUZp `Cl`v”j~sx:dp E (ժWrrhFuG 䯻'sy.jSWWGkM+h 9:;r)Jg7?g~T[Wa:hhه (պ4 c<Ξ/1e_| ];7,`cu4, 1x_tEѯfNxn')V`(x@V44wt O]cQDqY Ja{44pK5=\0;c[M+Rp tKh 9VVVR @)J+ [:*%ήuk׮/GS]ŶCիd@oXBQĚ=g_:{LW[XRDU (**"li 9jlBz"@"x_}g/Sz h 9j>k;N&i d@ix_4#fm'3^ߴWND_,P|z4sssAu^3~iW.x999d@ixDgϞR2ZƮ9+*w} oňںzӻ_PtZ}n=Zਬ5,!쮣(";GWMk|͛7:͵t!S 0h[뺴 )폖gg;_ mccŋC&oq+]/Wk(bK`v˽:Otݚن#p֮]Kx@hhhkq r%WA_{G~I[ɮ7\yGpSkT:@œx^ZR^Mi&OR]xz1RnY,_ (ב> 8K K,toӴo~;QxN'k +=O^0waҊjcȀro}YEgΜI%u{'-q.݈"z>N O=~>9"^C||<P]]8LoLL J~q됾ȵ 1aX㤨rY?@aҤId@^߰0*tymꐸ]C:Bmw@슃bA[SIi[[$x cǎ%ʵ 6m Th2Dz عi/%q2?~q?S+ݷ:ZF{ꨶ-1 ( _`mNӾN'8?]wWiWцir x  ] z s֯}Ʉ%lkڷ~h7ծzI.LA_8kCYewF.6n';\ס݌{m}7P+׶%jXu@("NJ *7?ؕW y"_P,y+>6A[(_r ms{4\BEBd]q]NBY>e@+W+\ &aB |\|jZh9fggS+"bn߄T{_ Ja ++'3-lWi9fffRLLumxaUmDl0r$xd??(rLKK"e?87tʲK vAQvZ*{RboՇ(^'Q60r\p!Ws5hk?:__߯(r1cd"&}t?~B ^A\\OV/8EE0N并s"ջPP+?~ o3H3gΤ2@YeM>ҽPPFaO擻 [4#"" F':Q)`z|r}nB+?PE#opp0CP$/"l rTR 2'w`|@6ݻw2z1a^q+Q DTҢ*5!|rZM#ɱŖl "*mIy5LMII YO+7]lKi$9fggS!Zł27v%!!,p"_xV;HrM/GR<2|vfx*|0[|w2MsCx1;Xrܴi?;Owޓ%, ټlذ'ߧ` %yQ)VT7CѴ~ {05IIId<ۘA4'MDdN}&MHS]sNƓjOclXr RWfhzZ`fkxRۨ?%G회Zx%q+ӹowi2?OйsoXͥbWp2;@^wߊaӒMoOc{n*x U5ueD: %--l'ߠ,䘜LJ$"zgLŋvmהLSLb+* L  Hǩ `Zv} Vu8 &ÇS1Ԝ˻*~j# WOc@ ҭ) hnj)9S(~q*Dz҉0'G|_MBw0GDtXX0%MZ /9h89ٳ>icpS Z>M/^ ?">?L 7(؜4'MDSsT<9>"e`(2d-?OqT0<+3Loo{~@_~d:<Ҝ!4cqU|+BCC[7g'#GPA֋v0GDĀ###,'C>jƓի `HzLX64LNJ+r\ج;@5(iӦQAp|!y!* (GOV0GDlmf0pA=!4<&b鎓L[ ) (`/^LvoYhhh/XWhLy?pjxw2wD:fn 0|wSAAA¡ݵ]ưN| -ٓ&Y:Mr4jnSi㜽X,ڼ$Cl0<ed4%l>nUUUT(C2AGD+FuFM[[lȜ楦^|w2tDD7v%'X,?媽k)l9þ?uD4ο$v&h7xsDDIR*!CԸ0PE4\ :v.;څ39GD1ar Lj@C5%%ngҒ}Lup鎓 :`X6oL&S__;X3-X*QS2rtĐp._VE˳o߾pP}m^)۲Q\,fCR__/zE&S@^8]4\@6f>ĤQ[ U5u <`HN>MSǗ\@uժUT p~9"~ :`X-[FSCnH..װ0*zC|C sDD|l0,cƌ!?GO,ˏixJ/1)GD>XBv_Cu޽T"h1\A3!IKK#c7 sixStdrDD=' v9L -Oe,8&刈: <`8zMS >2pOծh.%+#"# >`8?NRD՚# C&g&䈈:z ǂ ^]<\?C*41!GDo> ^֓ +WPY ʤQ'S0Z s)Eg o%m|7mDef!$I9"Nv \8G2U\###L,~;I9"N GDDK{^^σk@@(++:Ays,rDD`0]Y̥OK_pA<ܹ MLur߉K <`(RRRZ'kv<Ðott4 DiE5rDD]eC1a:QK<*4r&刈:mJѭ[7"j7cZx(ݳg Mv~ rDD|gR0d,|R ESNRA9SȤQ'?zXhJ8:x0r޽ZA8|2rDDLq Cuu+[qj8C<8pyv "ً <`WVJAo\\ g "oLb2uAwȷW^ks "cAn`a˖-d)\:2%߈*ܖ!L=hkl0;,VL|Eq1wí0m3tDD:s!0EEE ARGcl6<,5\n nIt&興E^ }@PP}&ߑ#GR8C@!$$ }]Sü<|WitDDkz0.\ ; `zpɒ%T4%?8:"\rA … Nj0 aoE}}=U nʻ/cfۼ!˪d@yxNj9h8t>|7e#"? ==̤onj~̃SéSR9~:"  &&̤UnbݺueeeT7!5⮗3aGDtN\b)--d&l| J+/CTM6Q<5 ;"|sp8[@}֭[GVRoQxjGQ়qg?L6R1>fj.灪cNNUȥL[YL@yDϛffYyecLרN1$$$362zi5V {!*++~p^fL1` (zI&R0aV`q˖-T@֪}'3GDl;@ʳqFt _̟#FW4z9yD&'Pzfj:uzTA[1GDlk@@yN8ARPcVZ:Q :J+/G2GDlN1ƒG3/t!c@@(..u:|1zDF sF^P˾}.F&TT5'(L`Z-$QXR@EQ>Dhhh/}=TԹ2GDqv1H\t5'({>ކeRRcĬLEy$&&qԴ^`XWݻR%| DDK38򔕕d%]8t\lcX6&N+k@y,YBQW?]Ю>e߾}Euu5U v "3(hs޽{mԴ~]O sÆ TN@DogʳvZ2Z$oGΠ TOҊjwkݸ(O]]0`FWX5ΠTPDJ;^ %%,g#|S{#F[кz%ѫ@ɽ 9`C >,c|]!3==J ב+Z ' 8a^?aiO:z3J _OB"zu`duF꿞VF9EP'NPM:*kcc hj[ ;R;v좶]_} `C=#""V& baرduO/ %q6P]`  hJ2]WShsu2N'|:h<5 lC1n82V;tvq{B"R(.oלOGQSaᆌFh@DS۞ZNG?&Yd"گDz (_on9FM###pCò<D4S;]Do.mt5=}4nH|q+Q D4#v3'OM?$FbM,0+N;څ(0>0u =ጵH$00Ngq433 7%l.B"ߘ$NRPdddI=R}wȑ#p zNXG@Dm6\u Bksq2VHM_@jz%ѮzubB"*v)سgYDwH? 0NÇgܒZI DT!^E_x$]/҉uΝTb%EU◁ DT͡`HmFQ4R|hlt$58p-XP&O@D>a (`89A k٬DbZЙuÆ Td-ڄYDwߊ'8 ڵk[u| ԩӃ,Ci߾}Eee%UX@Dekyb0$UUU97CwI; pJ].]JeFqED׻_[S,Y̡v X,p6ݻwEEETgh%}># ǽ]X8;o*,t͵ʻ]X:sL*44jρ(1[ Qpő5 bNRKEFSY]뺋j?sιd =^Gb̕`:n8*54 C)Dtk0 OXXYfmzz:Lt "ȯ8w߿la MRڽBGS`Q__OՆ&" DliZ0:\ZS- tGgS͛7SY?_(qM "6ͯ)X~=V~+l:WTTTPYV5(D=<LAyyݻ7N#w%\:Ο? ͦgnۆp>9VdL0g1t=C2 n .PšElN<;_ť2 %\,awWBS[sT_폖 $ Jm2alG",7xw^9ںz\jz=.صk8tV$rilt@0`{ǣ%KڋE !m͙#Q\k]ꛜLeQQU+O^w:ĈdѢEdx:44 I\w:BQmIS8#Mz%)z`JDn 9;)\2V/Q}vClpLPB4绦b%**`/C ·OTCQ#sFCqSs2ZI0NUUUT{n)⮗4 "#oM1W;)/ ]HBd?̛7l`wG; Ω";;gcFB4?xg [k8dXaz[16D7mk 7x U =fǎct%ӖfzOH1,~()ד27 nUӁy9hZ.yJBb#V\.8foP;v=/٪w"bp saϷ.ի'N`e9uE_{q<m-)_ɓ$LG6.x:q:taXP&>IJu%e y ֊`14Pg.\@;'`͞3CHg!ٻ^/Ưr܂y17I&fҡ]̙3`]c禉ǻL',N1$n+4SNߘVi_MCC-cGTn Xx%H4fEEU-lF]Y(RvcDF04%bâ ډe 0@SHHH`õJGb6a8-VtF0Y1s/v礊Ke|XAFFkސͻ `XO) )⩮O/G_󔨫~U{=rΝAR6)8y:fdӲE1`f%PW{ČՇj>n`ٍ{d ЃnwMNhhh>J7={W\aRtTt\<$B,6uˮ9*suÞ7f%{YgXXpp,5x![ϋA[/F / Y &// b̘1эQaʕ+qrB,~\ `A}TZrLUЉK277 Co<ɓ'ur.fAJxiBh8|2@Ovqݥ}!L"E|DYwUnA@]_@x6:?IJ;爚z:8Dۿ gC`tᢺQ h lIӖO$hkþ  f14nH%tSSS#F;} .nY|0ibb"#D*kʼnBa+N]V'/zc]/uu}D)EĂbLyq*wLٳs R/|y sNF'wry}'ŌՇ\[O:_#>m;GEhן?WY!&Cⶊs\}XvZ<</f׮]̵ f _!<P:QXRyCkh p /^={dmp-D].?8uA1yUU-CS36HpEy|Pv7+@K>}:sk[9.nQ|`vG+@sذasjs8t fc\ٳgI9s5dNmx ;$[h8;Y>8uQ EYY4hsihZ{jI8;[|x `4[###CCmHМV2ɌhpK/^:VH'VA26MٳQ n޽{]sFΦ&BKLJv]0u択={2g6%]t> ZDPP}Δ2D3 п3W6HW:2ᢾ?y800N+UM|… y176$VpU1x)̋MjM"^χ;w̉Mtb!IT(e>h0$$DTUU1j1rHr$TUE|attX0'\oҤI}b5Zxi>sh"FF2|;{'>eFG}v3^)JhhhkgKhvd0 ǎuQ34 k ޽{K.1Z<܎9C XO ǡCRFMrU1x` {$PJ>}rvc| G}DoԨQiy J)|0cTTg0mĉeVgjEepv|0eBB#)AOÚJ??gHtvl>r匦ռOUwjkF\ܹ5gcjJS}}} iʋ |Pͥn.*, jk.{!e̙3{U{`%gg}ݻeŋE߾}]l-$5/_fDAA0`sSo?'Qxi`9x`Q\\ 3%%%bСI}0$u5>tĈQ@'***DHHsQsw$ȋs0QF*FcS]]->s$H04[Ύ|9 NDFF24G0.vN|iӄ`tp3bʔ)9o b`x܈6JHH`irVknf[ٹ3י3g?{laW"nݤΙ3?11wf_MJJbh& ,`N}&44oon͛ D-Z\;tXWH=%|mrr28@#Yd sHq2 jo~-[hۻ`ŷä4II4MjNiN{o!$KC!+ Ferh$QbQpII"ʁ{?`Ӵ*ofwS yo7o4#Bx'@ $X zzzjM6檧Kҟ8^ xB$?0䣅 6></^l&+4S-Y D>ַC wEEN{|6~BfG\s\ή*3`wP8I!tG\. âկfe޽;1cY/]T_xӧg/ihzq9xlmRC':߲ɓ'g>hZq3.Jϝ>8%s I:sM6SOe_җt}_)y-(j̘1ن L@xDzѣG{}s% K>|xv&vlȑf* H,,T*e?OL@ú뮻rlvoBowb>T(X-0+Mlҥ ,[6r[wG5o d,4ꫯκM3… hX,~2|uYl٥^uuu:AwlΜ9f35^,|/َ;L۹sg|LJuNf00o,엿iwׯ:::`}lwA_$3ޞ?5;f/: *JId(-^81}&Uf-ջZC7o7}">k_Kې라qn¤iӦe۶m3GmYZ5[`'.hN @iܸqƍM/@mذ6KT' 3|S&lYgu]Z*T*f)$I.pʂV,?ivwwhʒ%KO: 㝰;``gve~O`fﴮRdM"Iy.lĉ٦ML9olٲ%2eYI۶b'*h"r{L>|xvwvFiF/Iw&T*^﷐`޾x_OOr(˖-sпS4bͼɂC;w@ܹ3KB:NOheaӡ?>{GMC6l&L`~Z= ZC-n:l"ha+Vf{^-&M)8XkBՕ]uUfT-NJТ77-tzk*ZC|ԩS8zvU*7mEe3A@/\,#iSH{{[$yuyeO>i ͛iӦauaBJ-V\ >#iq8 q* ^^yev2a@.r IN@J u[R?>[~i >M0#J4'&IF\R)[ticAx{^\6H{,tW̱@H5kVm6S gy& "MϖJ:Z%JI+V0XzuvgAԛ:4A$Ղ#_zsNݻwg7m:Gr+k$y©#m̘1ٚ5kLiЇxlܸqf YN5kkk{CX8Y@ui-X ۻw AWWWxjQ4ibahUo 8&aAPhUmĉٺuLuylʔ)fK/ ;}"B$,,,lJ9?W&/'O%Iah0aBC X~}9fc+^CC[{1{¿뻝P`Wҹ瞛0+k֬ƏoP_p{{L`Wܹs{D@K۾}{vWW=V,D U #Gf˗/ϺMlŊΞ>ꉶw8"I ,ꋦO=EZO<]pxYa~2.'`aQV__xb iŇ-]4koo/϶9y!L )r}yȟi߉h.@O(hh[n̙cVt\ЈEjeJЈ^Ygٳ=G1 IoX׍7.[rif?^*>h4Ňiηp?5kVi&'b˖-ٳϞ#4qaZ`WT.~YggI{-i5bO_;MMy%g?=zt|򬻻d @{̊+1c{;!\ ɓ'gW6Ч֮]M:^y_8h i~®.6nhbl޼9KxrVK4˲g} @l߾=[`5=zЪLk >|x{5^qM7{ I hεk lٲl߾}&\~k;v=Soq2r%P_՗ڐ;a{_J?q"v%v4wy/O|iքN@^g3@/6Zj0[U, ȵ$I>NՁ<E_>5k=ORU&__ ?B4X͘1#{衇L-t;w=NM'Q.?6 HWL/aB>_ML=83_4(xM jݺu̙3Yj⫮'СC_l j+֬Y qMk=J ց0öz;+"#Sf+W8``qkp\IjMG)>15,߳?~||򬫫DߟXڃԠm jƢFlҥK;wXgggֱcsm.JidCI lMFó뮻.ۺumٲ%[hQmmǨ iդB/N0_weeRɞfJr ?DiiӦYË5ޡ&aP8d0$yWX|ۀ 3&[dIsϙ^xlٲe9cP/ 9^ pz+y?[q ?~mM7:#Ma1?6$5[SLk׮]N@۳gO햨?fmW8 0Ȫ aAocR36bĈlƍy-Pg}5_SRcn$I%&fZzh CV^]{#]-rm&m?yѣGg/vUT6lؐ]wu~گVz6lثM \. ,ڏ۸ M:;v8] gݵ+OnV=ZghJuaVzܹskvww;u'[vmvWfaFz?@89, mfjƍp ٖ-[DSOe_}m EV,?mh^Cb>)mSS"tgq:s=-_<1c5WGJ{- Mχ} N-|bm@Ν;Z_rep *[Z3@ o 6:yq߻w ۗY}c_V'ZP့씗F͟??{.7pwg_~y6bkPt ⧼aiSފ?;xe@gg'q4 G$6NYgU0 {ǎNE=?|pk~OïrX,~8ll3.oϞ}Y%h v[6{\.[_?0w~R9 c v&L/^]6;p4Z b=~$Iʦ^j<@zF]veˈ_`A6vXk~z#> 6f)~23gfrK''}o.¬T*Y7p8s~eӔ x1<϶HGVO#2lذW'Ir T:&O-Z({fRމC{)~OY[^B84 @o >ԻSNݛzsN';x{=psɒ%ٌ3<_:ߛ$ɻpTT6Xc@@ >Pp۶mN~ /P{p_<{Zԧ][T 1*6W:k&;͛7{ -'~Moڴ);j_SL/}^@L uloĈK/^\m`ǎN4rKfs>[ #*X,~:l6iӕW\qEvmeׯ56I|J{J4).l:oK9Sj)k:S iݵkt4xFFPj8o q>UVeO>d~'Wz%~<n!3gNk0-40W|q=}ϺtsO?]dٲe'򷷷ޑ<^Z-Ioۜ` >[ ~0ovzU*ڻ/쪫n;yٮ]H;^{,|MJY$ïBpf4M/ ȥ|#>}zv%/ ?~=Z{Aw=E=W=W,޹_uKҧL4$I9ld[m^QFXf͛]}w~AubŊyյCC=T{MjW#۷?^=W?Kgl_}5>rK:¾z fvM]$j352V=g$IM=T.ߖm$)u&UL!IT¦/IrتR^#!sC$IfV*MNZ=!6t` IZ4M*J3{bas\g@$I-V!ŅBTԅ $.$Mӿ5aJlIwttlQ6^$Ijw?rÄ$Ij-3 I&88Ɔv.$IRƦ6#I+l?6dH3޲X(7@_ p\#f!IJwMеI4mIh B4H~'X, IP85lU $Ij'L]8W;I[ )$~ИfE$ma\.h .Mׇ[ 2$H4RaF$L]i^:jԨWy-`F$RF&haMhf߰#IBCdJU*6z$Im;::N6@>8#!H$Mӷ g^a`HbI\{{[4nC$I-զp M<o_Ò$I-qL^τAÓ$IMW&T*%aj$)X,~0P&^BA$5lkO# ߭BȰ%IRpwIX|hhah9%Mq4Iԫ͡&qr$0l6It~ykL@|ɐ'I?쏷JR(N4I~}tჀb5JTxO# P(IjX=dH$P{CWJ~ې0((Ij'L^FTT\Nd$:vr^*Ht~2Jå$[(Ns0Xzޠ)IjV'ةX\0pd$ C7Oە@P8%I؎xKZ[[$I>${,4Ƀ$IUt+ h`'LJ2XIR/ -IvS2T*}& s7$I`+I:L̶ Ly]IRw$E!UKB fnWȉbxU@$O%)T*'rP(`"cH(&m෤i3/IJ[[+L0Nh\ájXS">daP!]'44>n@C:tk %  붹R,ر^U$dΨT*'AhZmmmJ$U?Tv ZÀwre@C;"o5j+F|u0 ÷zfV?^qܫT*;$5ka{=2! ;($5Af5WKa>!;i>!AS}iRXk>w܀.IK Æ {СC_ ]WER_Z֗C +/ xm8H: - H3|\P8T*}&>; tOFҋ,Mvïïzv!aP8 C#$v8lv(Z})_e ?9 MRSϨT*YÊ pgHMq܏S`Qkkk{C8ds8\% x񃸇4]~~HZ= Bp|$*r@ԧ:x9|tÆ {u$+7''oF5 o!V) :RS036tmw96~ S@K!K䣇>/qt(T\iT@.U*!Cp4,7և8@+B焯?(J@ MF8ǁS3_ 4|<%Ire~4UKBkB7YR,?_>? }&P[%Կ6fƯ5vܣ-rmy_[x_g[У;i }X,~0w@ױwǟצ. Aׅ^p6/A3Y] >3)x?YNmJj?B mqa/,P1o?+|4W+ Bx ?Q~F|\h~8_O5߳/mX?asBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<gIDATx] XUU5}-SM5մ43LT3Sb5MPSDcUťr\AMIrlQ450HPCE!@AAP3sypϹw}}0{9ߎf1R@@;s B}m :KWC?ϯ+|01W%ƌr0V1{n P &70+: zC0&bz rh0%Q?wR?߿>iC7hP42],"K}T ??ˈv&d+:@իW|vi SD8f'Р[GKb(=Bыߞv^xG(D:r;xg}^D;a$)[j7.i=mZt.""M8͛7effo>VQQ>YkkB3W&LPr%87=h#Gd .dyyyL6&;.7,M%E0Weİ7zn999Jam``fcUkZf=zeee1c%p0w:6>)с>d!(~C3O>PGd|||<+--efD!OR*cVWW3EGG4Wib!Cj1tذa0+i,YZZZU̒em 4NkHVTTļ{QYSҟ#&OXB#8K+ٳg ǜ-Yݱf߃oȐ%dFEE]N濭 P̪Fߗ1sU=Ĕͬd!((H wm!-w^.}&d({ +toII r"*0[V]`H'p7c2I?`Ͷ2]%5qH`2356]H@ݞB}-]73*(yBhyR$qyz'rǎr<e͢Kÿ,K^a'n`'[OKu a=q.]/ÖH'!d۽,h kD,}'N0<X>]CZ<|>7gfh_r sR)SvU&bX[xwrbQL D[x9/ X$z̙3M?*(ύЖ  vӧIjx.++3篖yW޳'"~gĄ\dh촮!5;^# X]q년q)DngofHR\܅&[W -DtQF1`|& {,lamLd2t%W7x[ٴruX?PD*: {.۩m8n{4~#X&VG0WWWg;i4fQ9)z7x#4(k#W8 ; "at4k8WBj\x"ՌBPWV" lmSȻ{,מQcj3bU@{ %mު;q](KuNͻ >WHl.yxѢEb>̺<~;T{KS@O0| UŨy}8tI_lأmtlݺWZ.??3u2SV^ymS^7ȫ?Яy"1 v! jxe\.qa&IE +AXy2RnYIy$ȵ|ċ z_:^c`3C]jzm!_<̿m j<+VP?s_e;Ow)BWtPOx)z ,Y0 HYUTS/?RHx/nF,~i{_eg4QJ/n(=](J Ň+*s<p0#1P,gyuy`Vz~}olas_5nR+ csz2@lr =m>oJv_l]0ܗ1+gF7kTrnt[7E` dTS0gp$|`Ɲ.gz0 㝤gqHu2gøF}SО]L&x$ax(!G +!D=z<$$Mh۾CdHP蹂Ҽ oX8J 29rp0OB$9/4#7* i^44fPGyK]m |\|'%،FH(ef^b(7tPDEE]d3BpVG" S S$'B، Q(2>񬤤tGDY˽s<r4nX%@%hh4޳ ը%R&55P~hgeeѣGf<$Q/ABpu>_AQ )މwIj\-?"<tn׳FX$UI',R $ˆvZhex~( dxvG._ܙ01#w}ȇ$2(Poc##>.$9#ԧ:YDQI!HcǭjVo}*IENDB`cantata-2.2.0/icons/cantata16.png000066400000000000000000000012771316350454000165410ustar00rootroot00000000000000PNG  IHDR(-SsBITO pHYs:tEXtSoftwarewww.inkscape.org<)PLTEUUUmmm```bbb___ccc```___^^^bbb^^^___```___``````aaa___aaa___aaa```___```aaa___aaa```````````````aaa````````````````````````___`````````___``````CG J J M$^+c/f7l?qFwN}RTU]aaacfffpqrāŇȏΛѥ] 2tRNS"#$(+./1>IDATJ@o.N(HDA?OveZuyaS8Mն9}z~J eA~4Vϟ5:'e^<]M flBLSp*2I ('-i#<-2bԫXP;̓yܢ>W}k՜7E7 >(@ؗ6MX}wuՉXe-SyЕ)IENDB`cantata-2.2.0/icons/cantata22.png000066400000000000000000000017651316350454000165400ustar00rootroot00000000000000PNG  IHDRj sBITO pHYsaa0UtEXtSoftwarewww.inkscape.org<PLTEUUUfff```UUUfff]]]bbb```ccc^^^aaa]]]^^^dddccc]]]___`````````_________aaa``````___```___`````````___````````````aaa___`````````aaa````````````````````````___``````___````````````````````````___`````````CDFI KNUVW+c7lCtEvHxKzO}SZ]^```kloooopppqqqr}}}~~~~ĀƔΛќҨתحٰ۴ݶ޹߼UdlEtRNS !NPUZ^cfghjnz~uLTIDATE ;TaB,-Be-%ڋ) iZqh Z)}ˡkyr,V[aXgL̘' _F#KhʔIUxg:iN+>d|WEN`>h_mF\D֘~~C{n9Ro^F2A3٫=i5 ot\C<ɽ/?_vx(Ո[GOwI}]LT#rR}OqC )MX$ǻU*=h>%C:KO|E$A#&'@]".}ndiKBNԘnpD9v2fCísIgq֛IENDB`cantata-2.2.0/icons/cantata24.png000066400000000000000000000020171316350454000165310ustar00rootroot00000000000000PNG  IHDRשsBITO pHYs6GtEXtSoftwarewww.inkscape.org<PLTEjjjfff```ZZZ^^^fffaaadddbbbdddccc```bbb___cccbbb```______aaa``````aaaaaa``````aaa``````___``````___```aaa``````___``````___aaa``````aaa```aaa```___````````````___``````___`````````___```````````````___`````````CEH J KQYZ [,c/f6k8l;n?qIyO}QR```bfhlmmmmnnnoooopppv{ƒƄȉɋʌʕΘМҞר׭ٯڱ۶޾^>GtRNS  "#$<@CKOXZ\_`eiopsuwzOuIDATM_as3I{EN}mN0ae/߮K|/9Nc{\M-Bjdu 7鴉~ !j c0,7'E4RBG)Ii1:U ɵ{j}epAN#NЊS 쑣j fl!?ּWpvGtZN,ǵ*ɩ K61W&<(+KRɘ ꥤ%Ӻ]U6{ikĂ/'IENDB`cantata-2.2.0/icons/cantata256.png000066400000000000000000000231511316350454000166220ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs^tEXtSoftwarewww.inkscape.org<%IDATx] \UYfkI/KK&iҤ_4{Ҭ-M< ]DY4 TY4n1"ȢUdp9{/-wνSSCsg3gĀpwwua"ϙTvQ)D$T*gn&Mb.6F\BfG(Ae<% *̄$Ir*9TfSq3Ϲ܀@Xt7~B>h nrnnnnOWWxݝc1T.hH=t*cB~GPPu,+3;&gOy ƾca-H< @MڛR*Hj0 j9=; !yj&~?eFR*$*~LSjነ|I\q`A{eF|<@ c;X塚;a&z" w6⊀%3ې4T;׫;)̸:sq$y©w; 럧`+nvF`FQS? xPH> Ѷj nxKEv-ڻ[J)@"jb@o"Y޳~ndG!u\70,E9I;NB|}}ɴiȢEʕ+Ivv6پ};ٿ?)))!N"uuuEbgjjj[RRR… ԩSg31 ~Lta CD;v, iiid޽Yo?^zv~~>Yv41c8b8 }qb9^ SCHFF9vϾڢXTT$>tP:gRZd7KD~cL&47n$UUU… Dwww QCzWW;8n27P&5jYl9xdVkIII_ T?OOVP\\#|GV-A381s!HUm///xb$ 爍U1*d(C@;lJj'N$:͛7@59]FDGGǏ#DYYTE`5ASC#M0l___̕Oբb<_/6lJpȐ!1BO ѿU=K(Zd;7WE$ɤ0Ν;G/_.J(DZ[OQs޼y'`M"##EV x}u 8~xRXXLG!*"bC_"geedJkAc?LŚ1cP'ٳE Kd{tzyyynݺUۂaÆK. 0PmL 3'mϟ?_FT TID( as#\ RS,! UbP (ydR\YҖʟ rKp@} t~8A*=DtoIǐM'5'>Ŗi=ISS2F3r!u:)gcc#GT,aZC|%'>fqE-0\K:&h <<\q ьfMvbjWTymx,4{**5QLgJNd||Ϗ)PPZM\26f+Van&_xj8hԉ 8w@W$IY*mٲQ ׋͚5 Kw@y{&^Wس[wR L&E6*qۄWy#d(S=Q D8BYYYDj%h|"7W}$Tzx̤I ֫JX1)Yqvww &T,@$9x PA|J`q9!\>|ktͲNP%,X TE*NS#Wux a$ BY<DŽ[ ˹ZMX%7Q̆I ]+g?Pc>ůؕPuZyi93c^^^ܹs@Q}R2l~K$y)rkx :55'+;hM\Ox=튙b!<b昻SJT֎Y|_΅+0!ޝͿ3nIFex3 ü'Z[[y`|,0G]^-8̿Xt%#_Wx z!_cMxE^8z(c^7J"aSе;JW7[> FD~rQ(g缈gy_>O; 4 +QѬ >D }%߳QBy(pw8s84]w5Prh J?&z npP/]&n ?,B\x0#/Y\@! <+}?r hɊP+Vm; ?Ư@-f0nuydXDPY {`' <* rЬCnH5Q̙3u/"?x; >֍~dP_^*9F ޛ˄Hഓ-Y*Fre_|^ s̈Q+71 СCY+K%by!FfD<͝:( e.ed|hff&2Lp.aF33ApP"Cb]}@9f97vgFl]TO Gv`xw,#ttt G†̈$ɘY][ c%*ˇ7٥WU]cU"P"] 3|Xdd$K4+"-/xwb`OC9` wDKKKCv,#Vl>*x/k$[GHo7,o>d .rڌ< dm"kg@T(Av,wuVO{(WM۽̋73v8-8UwVmVl- 3MK& , "ԁbFrjCOh2ZY+,2m4dJ9)GFoBUrUpp0@|ȢEY*Dk6kʾtf Y>dʕ.SMzGc﹑1}TqHiV>ˇdgg#T8"ԯ{\5gZ]b_~=k CoߎR'+B=cuZ]cԲ'? HfMX0`JM0XIv-J_|&D@$f/3b}0v?2 rPkRRRZCDLv;3b}55;6+zQ4@ M $f|ٳg]*kL96W`KB}Q__ZT!MMM.)„vF~O@uaȺ*iPY> ٥5*ɦ$y<З5!L,hb㏦o-Gul 97;ak@* 8GCsTp#uс{y(JW-~ 1g!y'A&mh`B[a/6/m),'n33/{E~ XfEvp* `,rka N)5-@ P_ Х7e|Γ=HYu z!xXLEZu4.A.;?O K9 b>fσR4+|]9Ο%R<(2̰!;}3ӁsWrƂ P]`x&*svb۶m+# , 6 XZ̃`QPLji,n4uQP sqq˂ۇ%ӫ]nF&ۉ( di !SN6J-$e|6d`Z[ڃz̲-E:cUh{5UX8F> I͡A===( xؒZzZ胰3uk, i8p~~}0##C`#Vf&ii{뛀0'}i-:Ⱥ 3gk~O|СCKl̲y92G ?"t-ˤ *!'}ϱ$+#(Q^^־Rb3o(fn3IE9d f}I 7P%W"ZՍ!!! @5{,n2kqߏHVFr 6be pYп۠a "EAA9;bt".D]kee%B"zo&ipR+???r,ƫ#lЉ @сi|ItjRi'g޼yx3`c,h4N @?b.NubbbD ?>:5KJHH`BxEOekPPе:|&BQ<<<թ&L_\ BGҩ(}6+VP:-*5pnC%IIIrh4ު  KRj2cccaU7|䗿M? }j&uΜ9w7⊀0%W~:-׫;؀ F\(@eЗUr!wɓ?$)7ǟY_?%'[׆@=r[c7+kBC9V:usp/%vh b@ ?p +12_)Ph Y$())QKFg0^(9/}09?T.J )SZ얣5@kӧY ?J`(J<Аpm޼Y/oɥr@!IGX]],R)ܢ١x7H f2HFFιj4hhߥǁ`7TÇ'F|BźOfϞDMMIo%+D]hbqṘWfIǫ>y-OExzzJ <uuuVėj]F9j T 822 d@ii)TU2mCt T$0a+:I{Q-LP>t3U9"8=zC/H9rTJMkK L_Gfr$ӉPFGR޽X:5%!#!Ω*\fMIVV#R <N`3;YpAlI&`Y4T͵Av9xP?Ropn͌boStq4kwPy =ktaG\[nǯ1lذ5]Q\;d7颧wz)N7&؏Dp: ,ځnF,BB.8Sw>gmM,Esr"AGPS[begklz{ \IDATeCJFVE2Ȉx/*3gGeˈ#<ҩ︄DX;g|?*:C%X{d jL0%Emf.7m @p Jk|B v=Vݴ=r&H6S|7>{Eӻ@jhSլ$=EdVzX7fTUOdD >l=G+T4,Y  pI  |e3zd&逗TTՓb_檾ou3M? 4͋|AMKL4U,2 ()[rWЭA=S$#6iq" T橜Я%`,m/ X*W**>b״% [<=^BEJn H 6el)1UȈ,"؆IENDB`cantata-2.2.0/icons/cantata48.png000066400000000000000000000033721316350454000165440ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<wIDAThZ}PTU_?rrtrrgg?$Kq aJŴjAtl #mAvђEKX˴Fe>awMAvΛwݷ=3}߽{R) '$$y0 ~p~JJJ'UwSj\PDh |wLqǠ}0y)soRELlwrr EOIIY H呒b2HMM |w\`L -Q)@_:KKK#夹@xj%eeeܷ$0ho0 33444p bV+Ed\d5™&J"UUU\"$tzǑjmm YBC뵴  삝ܦ"6MNfѓp8HNNsM޳XR{8;;tww6 }#QX!RWKpJǾp$~Gw.eY,axTޑ1ѻ̙`"MLE(V!`,l/QX hFQ/LI1'tUJ,m&RrzO饙yO^dΐ!e-7,qRJ*)|k}g]CC(%8zJvnseCd[F^٤.zA0 x&я 00SJ N]o8'lc1lNJ zz7|WGiFPg%J߶ 릋Bu6fh'exPRJl)7w^\`i*zeuu7Seբ!emm|.c|=nD2͂ t)Ayܤ<%WǾiS]W=CrM9Y3An{d \u W> ĐVl46/ţa!+>{MvjS҃AodKU^l 6Aod@Cq.hC#E|Q^H%z1e8N\Is- @cS,`Y/k<1F8}?U̘7K04` g4t6s,9| ?($7}N'HyS<@ߦYbOLva Wө_eΊ JjMktZWdfϸl;F`!;T'T#Vo-^(p[rӘ,ۗΑBQV^4b"Zl'J"5crs mqqqs\P9)y#<`KCF]κE>1+Ua]pfN,nMp6þbMJ$ pȰ \Dt:XC3"7s͊1;f0=z͊˂fe7XP _y&{ToFo&4`J0w+dޡ`jE/Y.q6`gIENDB`cantata-2.2.0/icons/cantata512.png000066400000000000000000000526331316350454000166240ustar00rootroot00000000000000PNG  IHDRxsBIT|d pHYs7]7]F]tEXtSoftwarewww.inkscape.org<UIDATxxUǃػZVUu~]-uձf  =ȈD!$ @j PC q)̼J}=v@j2ի׫f4 \Y\g Uuk*ބwz0~|m;|;w!\?3:ug4Kӛ+f6Z¡5 9ys'wYcN:ȕ5ޱ܆X븣wNf8& x`%9KgX~?EFN3mUsŵq ~9I?Lj!AqmW8߭Wpru%hK #HR#}WY֟bv~/RqANR[\_16.wNȇ.C@bY嫻ܨ媅ڡbJd2>X,?lL81H W\L9}RHC 9[y4hy _Weoppc| r9v N;g+8H:pL`0aT@H znY m;p&Xˠnl8*LTS3yh>:iH;P*X?_i>S il ʆᇠ Tr[ZR:nCUuˆTa!}O +xп5O`!HŋmJy H rcwsd8 hwЎ[7OC۔28~B A˸ FTL%;u<8 qqYMsSK0g6ʷH3̵qX7@k7=0$1 ws0){!H~~~7Q7\09MXB'= ІOo` ~dJ + d8 Vt=XL$l6 C+,ð wҶ8AlkaM'oA-=3?R-|)@T Vu: '\@%g<:_A D)z-߀L~AfYXa\ #A;6NVל?ϕAd2{yy'Aq)jс He*'(+F @9OJ`X ooo6l06zhfΜYBB[~=KKKcYYYȑ#v 0M0͛7lVTTĚP 6mY16 x:mCȑ#ٴiزe޽{YII  h`ڵ,661Պ$N `h??D c!*1İTV1[ck9a`zC'6 qV6ű;v xkQ]]mH*t|]oQT' x_je *}6!!!1ۭZwV>!a'NgѴ?P7uuu wi0U"@sP >kO[ʉsg  99~o߾R^@x ,˯Ow|}} V__o)9[Ϛ51Ts$w;\Ř?!p zvH907ܯ}((dvҷ=LN7@!RUL&ӟ-lW3&Wllp]vb1g\okq𝎉: >~ t*@-_4L @X71]ڧ-~mnnCh>}0\w9p2_D]ol6?@.0+J K}d2ҮQ0]s$^rqL^ፈ`yyyF1c}Lbn:UVO]ڝw2LXEI\@Mksy;2@LW jvJbP3555rC uN+ Pʿ7W&rכ7oF 4fѣ1W1_l=ԲD6?2@c988s[Yƿw0ј1cPqa+*TpI)E/Z/0Qdz.|`uN#G"qj \-8NI׹=thTÊ6=Li,%%j7nd踚y OQg&Y2QJ hZ{΋޽{vt0JGcr_aaav@s[tx'PoaGLmBTt P ]] 1Il֬Y '@0s 6O kf] =///خ+Zs+.uÇY@@lؕH)5NWdd$n (R^]q' P`\*m6XP$`Νw/RbXM$Ƶ^7]xEƠ_bb@>hn'$$:l%6L;_ Eۃhu`0^RR,G.@QFbhsαѣG^,;-%Gv oMahhh`111j5JjT DIB(Y$''#qХ 4h;tp*!TkȐ!J)8&'aSk`Aq[COgϺvžʊkA* .P$`ŦMa/;ޞdMxf{~Az`.0{Ӂ}?p&u X"t![Aſ`;eܥtWJKl_οE7FN, sϦ۷o_+\KY4V߀2j%++y{{#իwǎ.R֥v-;AG|A^^^?U)aHLL.anWv6Zum`ﰵn N.] `<5P q[ТG<#cڝqwvWtt4jhljf1U p7dfϞ `^I;̙3/V?_< &&FA 2 h4Nࡲ# (ؖ&jjjبQ4qWxΟQ+gРAVU㹁6d:zx%٭͎Gs~}p%)9n ??Vh 233a%ohW187fKK@^y;+xP XV[34ls.>ٴiv6r^}gK2@(6qPcѢEZ zëkZӧOG? 3 HtaѦ.le8PPڃWA;رcǪ 8[Zi) MTl^1OlV^ zo"- 7ba`&]~"VGw2ׯ.7iGFFb<6U3@,w"Z Y֟x+MS>l* _·Q 꿏_~ٳ@xzdТmp7I1___-FI;[fedd`v)ЏKt@罩lq 8,]׫h?tbb"f5G/|pKs5 W֬Q?d?d%p~4C B6388X m65Z]G~OgZī_.3kVQKJQ\\l7 2۷cypQaq HKKQNw=H^ %/on@Um=P$7Y֏9l0VSA@N %9IjkkeNY֛_-?2C mlT؄ȑ#bko2HH8vy9 Nl2"OOe_o_SSf*_|2N0d[dl(#V++((,s;@ri8(Snʕ@d8r~1/>q,00566bfMpp =?5v(*\.l6&ӿeO7T ]OWPOZb؞C8>lǎ`pC-\h&8m'B%ʆyI"*#:ڡۉ>l;S\R5 4IqugKਝ? G=6~-H k3[Ə4p. X߀"W#**Ju}???VUU4Ƕ]|eOb] i Jz"GINNƬdua8f뎷'1@RR̻*c9~fí@6>5H#33 h {ݜ3 H!eKW}V_:O0e2iGffH#,,L `0`lˋ9s3 hȍB9KV/=_d#[-aPu nF('I; H)}G$zoX~f:3 h7F,A'ޔ]ikAV@ejjj؀d N>1%g)))Up%s<7GAt<ӄbv6l .@:*a(?z׌I;ߙ,cn ~e5t+(A2z8bj^iiip*sյ u2ڣ{`;Ok~n۶MOn:K`Pzќ^;_F Y^ aawjz 1Ƨ/{d^)~cS35~9LVكsNzڶced͆?1jvp\yUV _/ub8+|2Ty4re X|/s~ m14"Y Q´+a>2unPP=XOn~}R'Z^pfkr3FQKE=p=~Pgin籩Ri1iFjk2?p׻_ ~+C=?!ll͍߱c{n /L~Xz.)ۜhy;mfk_Ȗhj ӽ. lk/q\Τ7-rќ\a :ћ Hܖq˶ ;W3n KP ޷-tN%JjjlU>>>,9h ؈\r97Un7mz:0<$?x`,TdUV S (PZ<gKQD(11QcY:ۛUWW sjPcS3R/ g'/QPo0v~N1=}t&NplpluR?V~"Xf&Qjk!%Q9tڋ *%7G8'uI ֹPisI?LSKJJe pL@%s 駟m5 zkӧ't2$ ;?SZf :mdSx1-}E2Uw*܁dw2t+//U,Ma,W OTEE7H9c#C焇âD,Dm˘)8+iԩ2|h46:&M+@t]B%F@m-dĉR[G͢7>)) V*]HTШ>RkVAEgAe%EG׊Xq:vg&E@eӲXב7|tȍ9>~5Iɛauذa2:k_ ,sT_kc>+o?;zkcS4n̖vK 6"[yBmϱ+E_[[˼dfP  PG-og͔"Z90~x^nO]Z N"6u+b)H=Hnjk0\z ͵LDZ'AIsJ=rrr`0\`5B:Y)˃ <!< _һwom бczIsz,/tӤ cƌ!ږ 7^p…tO "V ݯ ϗ!HD@z  [ %.-|@iegRdl\-UN&CHraaﳄn}S[D,˯L#F@&.!Cڜ/l{ߟ@e$ՎDnY`P XyP>:C_-1'En`jj*,7 @DtHϖ {0Gkb>lڴI٭:OOEO|IXnRʪĩB9 DDYejDnmhjj@D,tM DM-2wimȍl(ē=I{{ 2yѝHG.%%2.q'"7j(>2Aݸm477D\n5qDXmbŶ$SD[G Y&ܠfFE"7VRBbAsp-497 dx sqUX,@AvNiIJj4je47&M΍Ff6E.xBb ,J5iQUgsv)K MϏ!CSXu@gi3F>812cǎ}`ܘXl~4!SN~+r4x"##E/0 1 (贮OLq6{TΟ0Msdٲen"7f˖-(DYUIMf`]zkVLJIMM=hl׶1999(ġ%B:'ڵP s, ;#S@//Z"7#~g]O$v Cyg$=0Lc~` P tk515s$ ~Atj!j#|||0PoAlَqCtSwCWࣖ6bذa(ȤE;tRP^ 'ĸC^ڲJF=#^":w&'=me H~16Mc-6"44#%6 ]eƴ1).ĉE?Ӳ%j#(#@9t |$(.ЈdLV`1P'[uQ?]jDŽh8яi(G`,!V.nӥ]9g0M&IF$'c %ypToJ5Lpe.ۍp֭['z(OwzIʒpuo.zVw܈,D\B@?}'SXpu222DvSpȍ8x F" qt+%/=Q8/$h#~ yyy(Ձ;Ծo6-oL¥v++VF;v #X㰰N2͙J)?QP}=zT'OH@!DV\^w'd$??_l6?+r# 0PKv Ⱥt7_#3-Mh@x@9EFc$,ta._)IlVH ='r#0P { %2J`${=  "7#PB@3E(ِO2b+@ee@-(r#ꐴB:}縤(Qo G/cUb+6%܈&DikQ\GBV #@Ӆ HvY-bw=Y~PIB 87*d0mkבSU{~\Iu%@^mdہKTwJkv|`%O XA\_t!)YS& ` xT F,ZRz'nk_#6z d!A"H Εװ 79;OT'=z(4ͳ߸اQXg|~ ٳPem'[;UbWB uљ;USe)K,J|N|uj{|g20 g{xzz.r#P8ܩq_1.Jcc}PX:*fC(ݛ#^WbRجlS dp5;U1 +"Or^oxrssEvyt"7@>tn 3޳-oLRM?sWd\ |r-[& UFc$jJBNnȢt#.`Μ9BFK{;Q1sLD Boߒ?!pPjz.gƌ t j#BCC15=zk8oȮw1p'N=r&j#F(9>]G rt$.f~ `H*}0ͷ] ?ܠM6ad:K GJ6l zPq1'7oF8zWrQ$..N$@E6j\r( I7qƉ`t1{;ޞ GMa"+zZ +++t+~zpLF2zܰLPn1vp@pe۷Oh4cj*P'%$Cce$&& {/L?0*Ǯ3!Fy:"""DJ=ZV4ڸQFa:*CLOQ @WB^^^ jsp )Y%P 1W Fw"7ɓ c R'|^ 8׊p4j7%lܸQ\-ЋT8t7!U9%DEEP+O"7pĈ >(NRd.! @jF pVQQI^TgC>|&tg\v)`1 R 1aٹs '5%rC,X +\ !3{LXtܼ m DnhPPFls]m $"7b_ X,߷)0L/PJЀQ^ X /lN^@U66Vf/4\vqu;1qS~[<2QP[xSR L\TVX!i EnpHHFn;8W^n~7!&&/p*Ǐ!з+0L^"7wެ.JCCipO/kW@7Eo}0KO%Ui޽[_]zv6/86PTVͮ}e" $hRjٳeܣ#y Z ? N_ x/(j $w6&/pN.z㓒0“8H81b&/pk֬!>  DP+P]ۀHH _ Ƅ d6ztHxW`Gi8HHE؋ =;L1wBzz:Ft+Iʂ3Ԧ)ݻW`֬Yѭ0un8HHVc[a̜p&p罩) !?C "?b $^48={HeS$H;t >`!E`0^Pj/w.C8H8MO؃ y{{lk uΊ)t\B"j_YL^8;wB%ѹw m Y [ޘ0yℇr[S틤@Cp*Py@q7G@?hгgϟpk.8z NJ_ق g3=osh\Oߛ mOař2e,X&3;ˋcGa X !t;YcS3&-P2fXd)S zj6ɀ 11a$&&ʲ?z8 ΚhaL8H]( YYh4Nrrr0DI`HVVW<@Y/'2tTdd$Fʚz֥ n9r1&+Pӧ}||nuj@ *V+wg jůۏ [`+L:,)) 3A+d UE-,k֬fd2}h4"C}goȆT+)ց9RvTe2t\^^f]D8Hs(JnnLyFq3fLpx8Hu#Q@c̊0*BԊmإRXXh5V6ЁaA%NR~gf&..Nyd@*\UUYR$eaRE߲u-fYY:qʕb\Tf&,¬XB&N]./p@4hkh;cwj8 Z9( Jpw@Y:s͘!E' An_(NJJL7&!4ʒU`D&8"-}(S]pwc4S3331K8TùC\. ۷O?_|(,]GQ%r(/p_}Lg fU~~~7ȒDxc\" 6Wn?I'##C?y Msnj㠢= 9]~(ė_~)U`0R1k?Vn+ N rj?`Ϟ=R93V:tfË7tg)[ cG 1ɀ 5jl@1:zݘA1jv* ٘\)Qe@/W Kg#/4c}&ȘMXi<Ԍd w؁tGwp^Pިe֭[est5UaY2Κ0Σ}8z9afV+-މ'U9N&>>^տd!6ӧ1Za7PB ;W^ʩS_)Kh4>&TxxZ1yd̰+@\~+ @:^]g*_~lJ֡JnoezFaJuW.qNw,-# :"i{m`n+ Tpy~[c~2a0,ۇBSTV"7^t┃mȘϡS|l-cfvN_XLZîOEpp'^ (.@`` klrzk8[OGNbBoG?G+VTZ]R> ut; TABB_ySh)D(rP-X7vG?9` @zտ&I3 A;QTѱ$vLP1cd^xhk4i{ 8wW]G~\" h6n6<eҥ2;RoQL?$L=tf gs`.S.[{PP5tbHLO<wV)E+)WBɅhgx:|+ w{Bw+%[ yO,GuTxWUV7}T+C$=K}54  Ys`0E,Oxamۆ٫m:i^t!SO)zW24?-;`LJa*Y;/^4WC7>m@<ŭ(Rp9Yl6?n>ȑ#Wx(U{=qox)쓠셔ofbKHzƍ''@эc'*k0d_m)Y=g{ߟjM?)cy%,pV=zL?#qc$o>j RYSjL h 傛dtu,@k2___?_Z_ -󇧪uuup2W;__~n9E2|z B(w< }_|ZpuT^#@999͓k/`<|R;Y0`VRR,eeelРAZpgV?<h7f-8^P;._nzZlE~s+;v!}- E"4 4*z? X-qF8Z‹w._ ȀuHKzzh+jQE o߾gZq|2tV ;@ܶG - ʇ]SSj{8tNw=GC2(G@mm-@Xرc  Nf]c2he0M0BB+44TKΟ'S;].Xhe@}׬  dM)uH/K#[ @E'5`ߵ4Tccc'[v!z^A P-ע߄?e/ҥKaecɒ%Zt%axcAG4@-$&&j>vop#> ownlܹs5Fc$<:^qNiqPRMʻ ΂lĉRJzx[> |:-qơ )PU?XL&=*$_>;w %%%2v5fYxWq^hu8=zV iN8eO2ë iZ}atLu?UA@Vlfׯ72Ţio2tM= 7ZTD mlŲe˴'E?XYYJKK٘1cHnS"|||n6S{РA,77?rrr쯇)QZx@* K7[2`"""p/BCT^^RrxMΞ= k(**b6 NG;j'A222` =F1BM6UUU ,66Jl;zw|Pcb\Cړ!// 6 6IPg>81A. ` ~KUvz NB0Y._KӰEz^\N;T R^*Գ꧹ ժ ~ z>W&gѣlԨQIW/z^ w'W|Cke\[[ K t/]Q?o:G*a\āX@@lEE^8QQQ'PQQfΜ {6U {j[a!K(]KNNf1mSh|^ (0m>|8ۻw/7 ++96j[gЅLkɓ3gSqÆo\vQV8{nr@P >r \u'jIc]g6li.=h*fs7x%RFy:luz lCTb0^7:i;#F؍A[q+ᅀDΥF l15PN?11;ѣG 6vX0_^ 0A;'2Z;@tW_an+qt?w^ |QzV,X__O2an޼I|6Ⱥ^^(E=\l C.֮] jA=v=&rӧ=PQQ< P>>>>;6x |^o&ӧOgn%77>iLbn*ןʱÓ_ @@O?WRJKwS1$+y5%!WDөx  9bccjŜsNrgx |PYLp* +PVV:!ԇ.>n'Rb4<5SLaiii +U\rV{zz/jMmۆfˁ w222s 7u:]Wx 0 }2d6jtg7{D@`% {ԯ_?ۇclSenk0/+ Fݻ7?~!rpY=:o dipfm<"^à۟}х0J Y>e<˪C~\JJ (0a}wʺ@zoqAb }+YUd2f+'|Pw:b&rxbA1§Q/-T$ 6W1?g+XirYJJ g֝rӦM>|8ƚxh'~~~7:bP9zrHU}NrtBEPP2h%Io>aP䐯/ flÆ 삂MPj+NG('Ri  u'aXW0d6vX{r5HMMeܹsB$-ߑ~Ww6P[M6͕m?]`gZ@ʄѦÆʾ """hr֭c۷oIˀDgt@ɴ~~:dk{s-=o'%%,3Ϧ߁.Cs H:J݀GRap RE?e pઇ M* 0LQNm"\u\V7B5d@\~H 0EQ!D0-*8XA '\0\uR ⧰E[)%'ATl3 g|2/A -L&d^ ''| ma-$qA):.t]a%DҴhr*@enB3Ϳ@PF/zˍiF[Sݥ;3/y.?;3̼kll,LSWG988J'Āۀ999e`J5b,0:ߧ5k/F ZDf ZV_ mg Bg~lE<ʪ߃Ao pC.-iz*Ք z{{8M HMM imm%]]]>6c0/-jP0^#iiiӧ 1R4dHR\\LDSQQP vvv㄀GiBrRbi*))!JpN釢Jruu%Xpt"f-ˋTTTI] 9h~:޸q#iiiVK&r'Hpp ԣnaXXh4>7|"m ܮ8P4(?pYPPPHy%5=9h|||bsM*Єܼprٷ/KzhПMs165ol,C4hBX$ %c?F3L׾56kMQǮQ0v6ޡ]__\*j"V݈yaFm8jԶܩj`AEv[EZ4T@˃ >s 12M((C NZ5RflyW3FZi1Bk Rp j]FK#Z㎑ZQuuuV@ʕR*×G?:SCh<o9"E'D4aސZR%@W:55U H@R\òΓa cantata-2.2.0/icons/dice.svg000066400000000000000000000116171316350454000156750ustar00rootroot00000000000000 cantata-2.2.0/icons/gradcap.svg000066400000000000000000000015711316350454000163700ustar00rootroot00000000000000 cantata-2.2.0/icons/media-next.svg000066400000000000000000000010231316350454000170120ustar00rootroot00000000000000 cantata-2.2.0/icons/media-optical.svg000066400000000000000000000161241316350454000174770ustar00rootroot00000000000000 cantata-2.2.0/icons/media-optical32.svg000066400000000000000000000175061316350454000176510ustar00rootroot00000000000000 cantata-2.2.0/icons/media-pause.svg000066400000000000000000000003731316350454000171600ustar00rootroot00000000000000 cantata-2.2.0/icons/media-play-rtl.svg000066400000000000000000000005301316350454000176020ustar00rootroot00000000000000 cantata-2.2.0/icons/media-play.svg000066400000000000000000000005261316350454000170100ustar00rootroot00000000000000 cantata-2.2.0/icons/media-prev.svg000066400000000000000000000010211316350454000170060ustar00rootroot00000000000000 cantata-2.2.0/icons/media-stop.svg000066400000000000000000000003351316350454000170260ustar00rootroot00000000000000 cantata-2.2.0/icons/playlist.svg000066400000000000000000000022611316350454000166250ustar00rootroot00000000000000 cantata-2.2.0/icons/radio.svg000066400000000000000000000031631316350454000160640ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-devices.svg000077500000000000000000000005221316350454000200160ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-folders.svg000077500000000000000000000002431316350454000200320ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-info.svg000077500000000000000000000004001316350454000173220ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-library.svg000066400000000000000000000005471316350454000200440ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-online.svg000077500000000000000000000042221316350454000176610ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-playlists.svg000077500000000000000000000004541316350454000204240ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-playqueue.svg000077500000000000000000000001661316350454000204120ustar00rootroot00000000000000 cantata-2.2.0/icons/sidebar-search.svg000066400000000000000000000010101316350454000176270ustar00rootroot00000000000000 cantata-2.2.0/icons/stars.svg000066400000000000000000000011041316350454000161130ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/000077500000000000000000000000001316350454000153445ustar00rootroot00000000000000cantata-2.2.0/icons/theme/AUTHORS000077500000000000000000000006771316350454000164310ustar00rootroot00000000000000svg64/dialog-*.svg are from breeze -------------------------------------------------------------------- playlist.svg and dynamic-playlist.svg are created using papirus mimetype background ------------------------ ICON THEME PAPIRUS ------------------------ SOURCES: Sam Hewitt - author icon theme Paper (https://github.com/snwh/paper-icon-theme) CUSTOMIZER: Alexey Varfolomeev - Papirus Pack KDE (https://github.com/varlesh/papirus-pack-kde) cantata-2.2.0/icons/theme/CMakeLists.txt000066400000000000000000000026211316350454000201050ustar00rootroot00000000000000set(ICON_FILES svg/audio-x-generic.svg svg/information.svg svg/folder-downloads.svg svg/folder-temp.svg svg/fork.svg svg/inode-directory.svg svg/network-server-database.svg svg/preferences-desktop-keyboard.svg svg/preferences-other.svg svg/speaker.svg svg64/dialog-error.svg svg64/dialog-information.svg svg64/dialog-question.svg svg64/dialog-warning.svg ) if (ENABLE_PROXY_CONFIG) set(ICON_FILES svg/preferences-system-network.svg ${ICON_FILES}) endif (ENABLE_PROXY_CONFIG) if (ENABLE_DEVICES_SUPPORT) set(ICON_FILES svg/drive-removable-media-usb-pendrive.svg svg/multimedia-player.svg ${ICON_FILES}) if (ENABLE_REMOTE_DEVICES) set(ICON_FILES svg/folder-network.svg svg/folder-samba.svg ${ICON_FILES}) endif (ENABLE_REMOTE_DEVICES) endif (ENABLE_DEVICES_SUPPORT) if (WIN32 OR APPLE) set(CANTATA_ICON_THEME_PATH ${CANTATA_ICON_INSTALL_PREFIX}) else (WIN32 OR APPLE) set(CANTATA_ICON_THEME_PATH ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/icons/cantata) endif (WIN32 OR APPLE) foreach(ICON ${ICON_FILES}) get_filename_component(ICON_PATH ${ICON} PATH) install(FILES ${ICON} DESTINATION ${CANTATA_ICON_THEME_PATH}/${ICON_PATH}) endforeach(ICON ${ICON_FILES}) install(FILES AUTHORS index.theme LICENSE DESTINATION ${CANTATA_ICON_THEME_PATH}) if (NOT WIN32 AND NOT APPLE) install(FILES ../cantata.svg DESTINATION ${CANTATA_ICON_THEME_PATH}/svg) endif (NOT WIN32 AND NOT APPLE) cantata-2.2.0/icons/theme/LICENSE000077500000000000000000000466771316350454000164000ustar00rootroot00000000000000Creative Commons Attribution-ShareAlike 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution-ShareAlike 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Paper available under these terms and conditions. Section 1 -- Definitions. a. Adapted Paper means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Paper and in which the Licensed Paper is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Paper is a musical work, performance, or sound recording, Adapted Paper is always produced where the Licensed Paper is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Paper in accordance with the terms and conditions of this Public License. c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Paper. g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. h. Licensed Paper means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Paper and that the Licensor has authority to license. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Paper to: a. reproduce and Share the Licensed Paper, in whole or in part; and b. produce, reproduce, and Share Adapted Paper. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Paper. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Paper. Every recipient of the Licensed Paper automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. Additional offer from the Licensor -- Adapted Paper. Every recipient of Adapted Paper from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Paper under the conditions of the Adapter's License You apply. c. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Paper if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Paper. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Paper is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Paper (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Paper: i. identification of the creator(s) of the Licensed Paper and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Paper to the extent reasonably practicable; b. indicate if You modified the Licensed Paper and retain an indication of any previous modifications; and c. indicate the Licensed Paper is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Paper. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Paper You produce, the following conditions also apply. 1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Paper. 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Paper that restrict exercise of the rights granted under the Adapter's License You apply. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Paper: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Paper, including for purposes of Section 3(b); and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Paper has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Paper under separate terms or conditions or stop distributing the Licensed Paper at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Paper not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Paper that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org.cantata-2.2.0/icons/theme/index.theme000077500000000000000000000003371316350454000175050ustar00rootroot00000000000000[Icon Theme] Name=Cantata Comment=Icons taken from Papirus theme. Inherits=breeze,oxygen,hicolor Directories=svg,svg64 [svg] Size=48 MinSize=8 MaxSize=512 Type=Scalable [svg64] Size=64 MinSize=8 MaxSize=512 Type=Scalable cantata-2.2.0/icons/theme/svg/000077500000000000000000000000001316350454000161435ustar00rootroot00000000000000cantata-2.2.0/icons/theme/svg/audio-x-generic.svg000066400000000000000000000022201316350454000216400ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/drive-removable-media-usb-pendrive.svg000066400000000000000000000065711316350454000254360ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/folder-downloads.svg000066400000000000000000000025261316350454000221340ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/folder-network.svg000066400000000000000000000724471316350454000216440ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/folder-samba.svg000066400000000000000000000670211316350454000212260ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/folder-temp.svg000066400000000000000000000052161316350454000211060ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/fork.svg000066400000000000000000000044731316350454000176350ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/information.svg000066400000000000000000000016751316350454000212220ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/inode-directory.svg000066400000000000000000000021431316350454000217640ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/multimedia-player.svg000066400000000000000000000070711316350454000223150ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/network-server-database.svg000066400000000000000000000053351316350454000234310ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/preferences-desktop-keyboard.svg000066400000000000000000000111141316350454000244300ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/preferences-other.svg000066400000000000000000000027571316350454000223170ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/preferences-system-network.svg000066400000000000000000000305011316350454000241750ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg/speaker.svg000066400000000000000000000063051316350454000203220ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg64/000077500000000000000000000000001316350454000163155ustar00rootroot00000000000000cantata-2.2.0/icons/theme/svg64/dialog-error.svg000066400000000000000000000036311316350454000214270ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg64/dialog-information.svg000066400000000000000000000033201316350454000226160ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg64/dialog-question.svg000066400000000000000000000042711316350454000221460ustar00rootroot00000000000000 cantata-2.2.0/icons/theme/svg64/dialog-warning.svg000066400000000000000000000035121316350454000217410ustar00rootroot00000000000000 cantata-2.2.0/icons/trayicon-mono-dark.svg000066400000000000000000000015611316350454000205030ustar00rootroot00000000000000 cantata-2.2.0/icons/trayicon-mono-light.svg000066400000000000000000000015611316350454000206710ustar00rootroot00000000000000 cantata-2.2.0/icons/view-media-album.svg000077500000000000000000000011241316350454000201110ustar00rootroot00000000000000 cantata-2.2.0/icons/view-media-artist.svg000066400000000000000000000067521316350454000203300ustar00rootroot00000000000000 cantata-2.2.0/icons/view-media-genre.svg000066400000000000000000000047631316350454000201220ustar00rootroot00000000000000 cantata-2.2.0/install_dirs.cmake000066400000000000000000000011321316350454000166150ustar00rootroot00000000000000@SHARE_INSTALL_PREFIX@/cantata/config @SHARE_INSTALL_PREFIX@/cantata/icons @SHARE_INSTALL_PREFIX@/cantata/mpd @SHARE_INSTALL_PREFIX@/cantata/scripts @SHARE_INSTALL_PREFIX@/cantata/themes @SHARE_INSTALL_PREFIX@/cantata/translations @SHARE_INSTALL_PREFIX@/cantata @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-light/apps/22 @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-light/apps @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-light @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-dark/apps/22 @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-dark/apps @CMAKE_INSTALL_PREFIX@/icons/ubuntu-mono-dark @CMAKE_INSTALL_PREFIX@/lib/cantata cantata-2.2.0/mac/000077500000000000000000000000001316350454000136675ustar00rootroot00000000000000cantata-2.2.0/mac/CMakeLists.txt000066400000000000000000000007261316350454000164340ustar00rootroot00000000000000install(FILES ${MACOSX_BUNDLE_ICON_FILE} DESTINATION ${MACOSX_BUNDLE_RESOURCES}) # Qt translation files... file(GLOB qt_trans ${QT_TRANSLATIONS_DIR}/qt_*.qm) foreach(qm ${qt_trans}) if (NOT ${qm} MATCHES "(${QT_TRANSLATIONS_DIR}/qt_help*)") list(APPEND qt_translations "${qm}") endif (NOT ${qm} MATCHES "(${QT_TRANSLATIONS_DIR}/qt_help*)") endforeach(qm ${qt_trans}) install(FILES ${qt_translations} DESTINATION ${MACOSX_BUNDLE_RESOURCES}/translations/) cantata-2.2.0/mac/Info.plist.cmake000077500000000000000000000023751316350454000167300ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable @MACOSX_BUNDLE_EXECUTABLE@ CFBundleGetInfoString CFBundleIconFile @MACOSX_BUNDLE_ICON_FILE@ CFBundleIdentifier @PROJECT_REV_URL@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString @MACOSX_BUNDLE_LONG_VERSION_STRING@ CFBundleName @MACOSX_BUNDLE_BUNDLE_NAME@ CFBundlePackageType APPL CFBundleShortVersionString @MACOSX_BUNDLE_SHORT_VERSION_STRING@ CFBundleSignature ???? CFBundleVersion CSResourcesFileMapped LSRequiresCarbon NSHumanReadableCopyright NSPrincipalClass NSApplication NSHighResolutionCapable True cantata-2.2.0/mac/cantata.icns000077500000000000000000006506051316350454000161770ustar00rootroot00000000000000icnsQTOC hic10LFic08Dic13Dic09ic12[ic07%il32=l8mkic11is32s8mkic14ic10LFPNG  IHDR+ AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs%%IR$@IDATxս[]z_:ҋ4* *EDإh_^yK7TBGb74F4A#ai l ˺>sf~ݽw{.˝3g9K@@ d &4ԃ!pB9ا]j;ͳxWBB@@ ؇]@@fff5kԄ[|s}ne~֓ =m?zs6h;mL.}ޮ[/ˇ~x~P@gp֟@,0vئ)))gaK=Y4'侕>kqb/PUlǗvKmm$P 6@ &&A@cƌVOjQv6 N=Ahŋߙ@Q[@$++~bbbg=ISD>`>6m=v |Zz f*V% @0#D$Pz5_ W; XY7'l8)`NͭkiF Ffn@L@*0qĚz~w=y2W;sA It |Q)`u9slיЦ$V@ctxAIA`u􄨳xK'F0+*>z衇,@I@ TQDLg&3PE3Z`>(,,dѢEy^L@˗ԎxJ\׫}ỵ$b/PU[`>G6mڬ)}Ԁ e:ܺP 333Ff~OjSOR=0S?Q X{o( k@pW@ L<.}v}yIHO=I"7A Q4-[QE @@\'@뚄@wp&@=' w4Q P@[@.Q92`ELW/rm{2GN85Y_=y'M&;M_NF>׎9lɒ%ǭ˂@&@@()K`ҤIAz?H#7ve@ Yn_'%%e٬YL' OIC& 0mڴcǎ]_G e*(ҿ+Uy]iV@t|(pj *)Hп_߉7̙3gk  EtXXT%r}ԮG*ؤK^3z+zJe@KH@WOOOTG5@ B==},_ay S%  \WI0}Ԍz5WB*ئ3^Fb$@@`)(+W^ߠ/Ieg@:i"~7SxjSJ=4qsOHQ\gs @UtT% @zҟXf͋J6R5C8]@0.NE , q PF ;;a/7ˍ˼ŏ  ,"҂,ZhoQ O:dQГNz-;k֬cgޝw@ PV@*t27ꉿ߿y@ vg3`.+̲diH@ $l۶^uF a|ǂxA|!@/$@ ɓ'7/**2'S628@ QҚ"Pک p.o C ?x~/Wω@$*Q+T& V `u<DK ++~QMfB*r@\!v-55&tE{8$@CT dff]_ #"@ ֿ?言1@ &! ^oFߢLGXG X\l"Bh mQCW 3u~WA!qأ5_tT}fC<+@g@Lu&mh6@*(o-ZdIqE; `6#@'NW2R鎼 @9ؠbΝ{X+@MG P^ ߲ep6I; @Gy p]*Wkz:@*/&&&^GVžV6 !@UzԤ~uڟ@T@G|Eꨀ @<6u!@T~1TD)@ z_SRR0k֬8C@ wr*Dp233̄~Gp@b pL˜_3O@ t)*@@T9) -')5jC-ײϋv @J qʤ@ tD@ Ze&Wlr)@8,:~#C]T-@@T7n\#eye:@ v埌Q-@@T6IIIw2Kh=cYe# OmGtxM] @Y:j3] ;;L@ l~>ʏ_ԄG>  :ic#@  |FyN8"a2N oɆn@7V |$@TpR'@{4\dqJx `7#`'61"D4a U:ڲ䅀YYY]~a\8T `,:Wl MD%0iҤV%%%뗖I9|D "t6m)rQh `i6nY{4;@>[G<'-΃@a:nGv1cMIIyܭC X#'c$4p.nBCSLQPP0]c4KusĆ i>׎w= @10#5=OϟHsZ~ -zk %fDgp֟I,~}M+ (;?7oޗΓ@ tD"𺀞/h*vD?f7L@<$JJJ]`RE* r8^(3Ͽr#@(#(Џ OT"@@%0&N8DN@ h~={Jz p:΀[A@';_Fs|@ Vf=C>v u|+@o.`OMMѓ" 6w겁/(gRE>IؼyXךzCO  pJXh:P@tK@@@ W_ӟ"Kv'&&P{`A@ 벁 ˽ί !:<Ԙ@yesQ o&$$̘3gΚ3xCo#Y -?ot֛ T.-/^lV`C$ J&OܸxF@ة9%a! B:\(@geggcY! ^n׎-wKڋhP` wE@@ 2cz~dɒȊhpJ(5-{Q("@@3 7oK_@@ No s,  #P[g%2   nz1o +@@r~;   @%=M};wJveQ3 L8fqqVgڏ@@9ҽ-Kޛ=@*:}Ы=ݝ4c75jiii_\ZJJyNJJ xNLL=潲/ߪ@ =z%K@ L:„02#T׬YSԩx.VZk׮-,'իW0(/r!9rHÇ~7Ϧs (#\;1Gϙ3gCB ,vE)S;xS\_ԫWGu >-l: <(rٿ\8-}5"%:I1Ы5k33eOxW oР4nX5jx6|so賹GbP!o>ٻwٳ'W_1=ME$D[`4OhLyxY/.D@Iڼy^TB Ss9oҤI$D<+z6t-)P03+ .\eoD@|7Y.0a„S!||!`N }f$##$O(عsرC> @zKL%`5(  >U%Cjď#îȉaZ-ZNI|4m۶֭[# U @LJ)͚5%LbR "`vGt_iu߬*0nG6m̰φ@̄۷o-[:]Č$`C7h@p.kqĉkꗻ9U"#!o۶G˖-tY/3J`ӦMq=qu $= ,6q:lO"/9"|^ͥ]vrg-nJt<6l G2IyPx@NjC:wKI/@mH1_1("@@}s97Wɾg߼ΆWh3`_|E`~:\?DX",K0#0}TRKb6#>5R 05m4pucǎҡCɴ' |駁Q$,ďDW`v,4o޼7[,!`vQ@`ҤI SZ1("97aN|@T%`0-fyU˪}^p!쉀7fU: =q;0C;uxwyZ]~D`L.fyر#KY* (n^ F ;;{ ej֬)۷\ڵԫW̻8|pC`ڵrJ͍F/;':B9sl%I^ RF?@&3*_zدB+@ l3yYztt)asriK@~[_[y, Kes GJ _nΝ;Y !PPP ֭+VtG`D{`v`߭G )))0ߜu'y2E`FU MZX^?{=(2pnnbˏݨJAX.wңG[ޞJo裏?|#ehykS2*'WKro+({Kj|g@]lgo9~IRc =o<44JRƍkޱ5𛀎~ \իW`φBYf,_<0w@^^0]L?: M:ٮdz}25 R} sO"#,/mq'kحLɶuRtgjv dmf2+'3}rO9~Em+W_V^-̞xG`z;)'{'iZ>k_2@,gN/k׮`^B% ߑ#ب[>6G'He0nӧ9ro83!z_ 0 pߨQ`@ {e˖BJJJnXpVDKA3=YYY҈/'j"E 4sY\ҥg>. `Fe  :`$= Cz:保if?ZnJzz\-`ׯwu@:'Z+''$Ҳ8'pB:&'Q'jR.UV 7!6رCyDo˳!dbD j֬Cs0 NSwDzX"*p?hРJJ"#@ x'|"o[N-lxL-Z#" jz3t7A{}W]r%ҢE eN ݻw˻+o~H#~B ?)tނ,#M<Z@!?55\JP>#yWd֭U"S'rܹ+m 8-@ߪ333Sic `(#,}sm۶eG@?7n7|30y] r6 ,x<tiҏ;Ds|fR?3ߐ!Cnݺ c@Ç{'V/!`@$8_ w:GKG@?ҭ[7ˤcǎ  ~zyWeRRR!бK{:]999|]&X*/jX] Pa 6p^DN`޽[ 'N B= ti $i"Q:R`„ t24Z (#PfMK':Qew@ RǏ xA\ԓ*6j`ŕ$@tx='k JRArˠA$%%%ȣ @ 3O/ye18!hZ  8@-dذar~=y @ z@VZ%>l޼9SQǩW̚5뫨N(@@`;?U@Ak*v̔@vdҵkW1!8+`& 4X@ꤖ/\pGջll)9]!®Msr59EЩAt~iG+qج.[`ƸLg/4{(/`jl nذa,]no(ǷK7;%@S+:c8p9m۶n X@ظq rJ+AnM WkR;StQpD@OSa<ӑ 8@m=aOyH˥ta֩4Ǒ^Γ֭9RZnmOD Mgիu4;CV2RGʨ#@ %:VLeT `}K ^0s<{-5W _CNMmֲ<ӧ>| 4Trf͚fݻ> @8k׮\n@ (hLyD`ʔ)5?nCbR"@eĈrEIBBBG  Wfn3"`޽^M(())p§ HmV,fs_PP}eGj֬)Æ /\<i DCX}]yg(2WP<n@UtT% 7.MO^#*CZj2|JJJ%p _t+tR1?!@ղQ'RiӦ;vLh25)tRj׮*@#G{N^u!Ɋ4,(֋g,Xв :,h$Cau6O tQF--Z7 "Gp\`ΝW\x,Kbz0ߗٓt >uo3eu~ yFF5Ju@fŀG}TK&Ifn0CP& 3.:l7FmOLL|I SrW˥^jYA#`V #:%zkc6s#qE-f_E$騀СrUWIjjP9 ??W_zq0:8G5ÍԦO<=Wij^򧀹o3 @x ٳG˪U]5WPgߠ[8T@gZOIKK3/}gb!P^=3Ǣ|D@ 3Aŋe߾}ƾ+Pi'qˆ@XtA:? ctuk<#m+hOy -k5 ???r8 #tN7ܟ8M8%WcFr"@e̘1ҰaPc_@*+O>,[,R/iuN|=IG$@@D|=ٿ dKFIVVt)P6 QXz,ZJakWh'KT*X;@g/we$eȐ!rKӦM+@pHq2h 1lذAJJRlD] EKNGJbŊQ/=+6m{45v5P_ڵk'f͚@<${nYp[CY\\tbO]T6[?~jl5>SF2r\3 F@=9>@y%+؃X`gqqds%Q&^&LC{(z%R"{cJZ@@I=Ž'Q#sv<pn'cFf3ɟ߱cG v@BXfM඀}t;#j]r e ӆ%m=IZRf'r"Po&G=zV ! i=Ig}V^z%1!ECvJ[$PV@je_g0Wu4o>9@O UΝ+vT^$yyy#,YRx$:t]''v^5tDE$6 ӧK&MlL@@zd7nh SRR y뭷bۏAʼnP_+ h{ "0K͜9S. xL 11Q:u$;w3"ȑ#ːtr+Vxѡ֥ta kKJJXɆ+dԩR~}˳!|@b+`Frf~?K齻w~v嗄ɳj:6&M'oj*q@FFu]ҿ1l  P?]vҥK@'ѣG>=8`اy7~i a JۥA/  @ݺu墋. `n `C B+`vxBNNN͛QeOvڢGJ׮]<# #kٳ%7cDb!{/ gwnGa'[niѢETʣ@@Æ 8 ۷op|p 8`F[ MQW.cǎ~.wF@ },X@txWM}ؔOGNJdhyp_=Դ3y}cY 7)! @|H9s戹5 ptuޯ-')@ˏ0>̐ 603_s51#@@ ~^3.--M֭[(0~S >mȑ[%^ȉ x+O9nܸM# #uQ8 T,`>Nn ؉W(pϟq/kҪ{?oMޛ=@^䮻ƍC .S 0@ ag={Q>Q#@@8jWUMizRRRoѣGKr2wđ@RܳgOiԨ|R\\\1@Kw^;Vy=,@|j999 ~L_{@EZ{WuVۼ .0KzׯC$*°@9k׮o\u&-hHC RAoݺi- P0^ve2uTUCQP- "%رcyPe_ $iH ϞYƁ nO%U&&L>}DP " EzǏ;u%Vo7k,n!EYOcCR w#۷t@@ 4m407_|!z &B749$kѵkמtC@}:ouzUU/3g30 /PzK`֭'D8GG뤀fp6 F-Miĉ5O= qx m?^qGOu%z`:ׯ/w}w`Ea* (`V ЙEvK^^^G*P뤀&Ƨc ZN{=W;0ks=Ұao; xPׯ_?ٹs޽ۃR\bv=X.E9,@ uZĢlʴ_@˕W^):?l  [+yݺuIL/رիWqk"23k胶Hқ%N*C  0twiݺ])**+ !!wVq ):Bx]B:}!иqcD"_D@3 4i$0 pȑ3̻:b$U'4Y.@ X箦?JQxZLcWׯ  ` 4K3/*ji'U. lxT_u~0C$d^?dM E=y@G)4 Gt( }k*5N`5Q [$WgjԿ/r"@jjL.;7dAY:_~o>پ};&`R"4T0u6K;+pe&3! Luٸ3+%:vɂvy-Xq;/X&L*g9IpŤnj c H(7햝=CM1s?|p_k jFHm/H xcQ*Ԍ w+-[ @ e/_EQOh`:(?ԫW/pK5kѣ~HQ"Ũ+Vx?a:n`IYX{{ yWϖ{W֭$ |%_ȢW?iΟy@ $tl݀jSWD^x_^84+D]xK;x(Y8[]6mKaM x/3gΔի{')2A 7eӮ' Do3)><0E]KTs++ؽ{ܹ3: Aӻ{!.nk:\80|,0d[A}! u<(sQE'fZi[5[TV͛7{!%r\ ];zkk/z:\ړ'Onk}LObW%] 55UObXG`΃shԏ9QP$/s BҫD D-Zs9G>)*j^uh^gi^W1<h֦EKNRC>? 6 ԪU+߹I"+ouU붙\.~3 sβ|r)((pDK:`Pˊ(;4:B&LhqJ\'P~f\!ޑ/fQ)DAq`4GA][HԨK!x]N:ÿEǽ.YPM? f>36( #'''СCOK__M6 ^t6@{F\gs\jW߮Nj 쏀/ZJ*lE~rVpQt1L([>MpYX'm!^xe\f~_0EQ\xgInnk" fg ޽:ޠB:m+$ 1;OR:A @~-_űȪ2kUg4@d-\plڴI瓬I4}vx)ttҿ:z>U{W;c Vw$3@Y)l.w̫t4vE]ZHtpf9}Ν;e׮]NB j'? pg@AP$ˆwmIrrrk*@Y[_=/gv|ᬥ$),ש prW^rٶmdz%3\ܵkץ:3[1 Wo̾vSk@IDAT-AĉȆI1m:o|Mvi5 N2:#GdfǐE'is'|:6V'ţbJkpM PN~(m;PU;ݶֹ `guL'^qj\$zzHoxEa*:lӧ!r0 v@`Сr7s=U"%:X~D)S^Vй4]=J%S 0]tǏƍ+pB;{EOY\RN~=`#C>6l=[@ O~I=}Gd˫.LmE` v_|\ 준YQ8d~Z=++ {Yj'+eԨQNTM kyc@JG|ܜ<t!H$hUv-XvɠbLj0̒zKP;9Vs򟙙ikčDM7K>uG<]G)ɉrA FQKJJIqP#-(ݧ@pzρ!kV:jZ@]ߝ<ﮠb 䂎Ҥ^ZjXЫt|F~ '҂8>8:s^zèHA0'W_}$@@ GNs߉GUcwn^`4@މ"ksIdWW8/`:U&~A<uN\ a)b]t{~28H9r$'"@>XS/%%'eٚ}7cN X$p9&I,P PHעPET!@@@Q|,zL2)f믿 B^=&Z OW&``ut3kDA `4ŵ hҵkW9|lٲ%nm!]tYjժ<3qg3adee ~*(% &p p EQ=O,]& pd 80&N(fg6O y&{2('E@:ˆ֭X"lh-bDlvp `Ge(;^zƾh'uvCKu,L:¶z veKRSS宻  [3]6x:u.l0_깬A[ ve Jh‚h _vahRvV BVZq h2muBg VU 1BcQĄ-{6̓9 bG4={ʕW^yk {̈́ *iJ^e2eJrQQc@!йsg{I `o|$`IcU扂byfp.ܭJct%Tl@d˖-g& ZcÊ+Vvap EM4SJB{P@y' =)3Avs[護*BtnZ^WLɓ˖ TV-0k5,p@ fe5vի#g6O 4INNSE):*ԡзjW6/[$`zwo6zw-j3BEo gZ?ټ/Fx]%z-p.<YFtT^ % Z֭2 `@AQ؟T(h YxBg\y-l3RRRv{tkQ_Ʀ^ͬl 3\s5ҽ{NN \=ܩX/'Ze%ֈ+b㯍5I&1#fQ&L[VH;:ZmV .6 _, 8}t.9EH@ϒ-?Z*~ȴiӄ ],m@F0g2D3`Z2c dQRRRqD.pѣGG^7JL;ߧ6.?Z*0n8iժ6 EIL8x4_n}çecG ТE 馛 1DI@WwQ*b8|hWﲺ5 > p@p@~!.hBx aY꧎@#0x`0`@8r;$$$Ļ >+Q+мys?~| @ c\Ż]ŋ2'.!^ -[~;?X/p'>@wW}bٿ?LN^Z}ܳtKP? @`%o.&͓tT60 Rz+B:ƒ;WOl_ߤdfm537m79( `#lhŸ'7Oyh#'W15!@ƍ+l h;ޠw0aQ32ddP6 B2zܶ@\gѣ\r%gމw())_wdffJ -+|@g\={ rnn)b3Gf>|ENh ==}ru $ܣņ >FMɓ"nS{k)4bA4wN3'~1}TOdb+uǏBbw 7]&?W&qA{ߑ"V̉ם0tMAhsi*̦r|PF{u65.ЧO߿/ 8sUs:ţJ':VE]$\p<1cPJA)S4Г{b'W 4jHWFP p5 ~ F`͖$g2).{p.!. 6tYT@=0_v|_[-[oիX@;vr[E%K/-<:uT1-gE&vgZu3hjhL&nAڶm릐@npݮrր3c .hӦ9%Fizoݡ{=\:;w,C4zF'2khg|<(09࠻3Y n+Cn 8U'ovӰ0ޮ&"RQ&L :T%@\.-.o 3ۂM0IoIK3,H[`q!m؟NJHBlWk!@j#^W>-{r?=@ݺuoQGlX4iRgpgE /P̲l v 0% ]&̆|OuRN\^o:m|] )ED~}?slؑʡ@F%M4Zy5~5z@0'~-~XV%%EeJ9 p.9B wW4.fSML{y )0|piݺ;#*@N0 h+vSge+Bm۶2lذ d Evkl/7\ 6m*W]uC$6@ $DwNɋnI@e^{Y٬+;\dmgܳz= ̬fdFGH  0 `bm=y2Oʭ"Y.0ھW)N˭X'Gxӓ7X&0 9sB>@)-lEuRtHVl!>[̊Vlv !zarW';Əi7 S7FL a p @p[w<,xC)1lX믗ƍǸ^TXTP9J g8 peL~o;r}}G Aq0m'O,.òXy%A{@'k詹$_^ve]&ڵsit +qxmy0^r%>W7}\ЏV>bXF bCxMѡ|KeO#'L 33S4hನ'f_T 4xbF@GmC'@+¢CXΪΒԜHMMZ\e6~q\#~Iǎ"!)_P@9,t\ş5vYhSI^+tBžN L!oѧ2--MnF"&T@P0-uzD6>䆐ccƌ:tE[u{@߫qFvKFZj5ݺeͬP8ffgCQF}<'G Iq vfl.ԁ]uI@r@j+_TKSoh7tÚB#co@D$@0/g.:Gpl(Z׺Vvhf'J̤=z}@WtxqɬRzKY- P̄ٝ;wu^R]嬫au&Mj]|@RRK8O :4!8 p(/_F9ŋr,0hk4#gߏ'$$X,uŷI~pٖСCI&M Q(IR^T|=;2dȐ8Ju Q#,#n[k-Pqӡj׮-W]uUs  2V$HVl+-,Ң8'\sԪU'z": [2 Ka]C8:IMMEє `@ɓ{K:"?r@FyץgٓnիW#Gz;+p^Nv6j@S\Z@-dN@  9!E%Gw|ENc=̿ߌŲiAeF:4h4k,R^_W5evNI5j0XO 0`lzj$%wG~uVl ,bBy/W(%yN|\^^NH穛aC~֜V豌k׮,_K`F,w%ff73/ziLJ ?~V?FXj[n|?{$5׋ֽ#) Z}\_Tl |1rZ鷎&!IV|;-vdP.1ٹ_Ȼ FV侷/# =7Vz:~^%7"grg#J}q{y׊=wVmS+o'h"skō:Zݨ\O'NXS[c|Zrb#PZ51K! `Bon([mş 5k>B>>s̕?!?]l|fB͑ffDb>|ԮM3Y@@rjpGK˾.(.9)==OYE RZjɰaJ"{t}-ocGXN@  l[N/(繍ߌ/l7I;UY1aCtp-Q'FD &՜ϏAEZRRjERBضX6bUNȲߏ32ʎW;潼:S2bk)m$i:91 ]] mذ 8S9  @d' ,♏=Ԗw7&pym_,>-kJ ts+6Iq=gMlͷF r( 4o\(@+wJ*ͣvzūTpx.XRQZ<'fi@6 쉙OڵڤLϵ~E?.@-;+qUbnml:Z1BH6m&JOv),4$QB ՔЍ[ޥdWo{޻w3yh=sfKiu @0F ỤPGw~,6!Yf/ D#S?CJJJ;'@EEũ}~ Qsŋ@7Dh6nN%-gPZT 9!!%>5hn՘G-qr{FAs5 HO;* >sP{i DHDEY%_qC4czuq[e Z< \yxf}^?$1ү.pqb޼y[i `{90yw_X\{Tݸ0#NG>nVbZP ,J( D@u]m΄I5ܜaO/"GgYn~.k@.,~zݙk"$!jz?s@u K`f ^YIf$I~牏^zt.\w@K[x P):+: ţkE3]wI8|tuB Dwj^ZM:?rGi bٷ&T@_wwwWbS01z?k֬7@xZ Φcg qYw>F\ߋ6Qhj=믟@;g^,O`D Hgӂ%c" U2W6BB/og|7tI-#ŃgŗK{Gz.,^@T"` PV LOvH vv1rvE8Yx"*vp&@Pt|K`  8J@@  @*FSq*on纩اY /bpbjRkp"pNfT"h"1w\[ @!"; x%Rׯ?UjdQUiWmO {:E60|xOz;'Uߙ5 ?j[ cLʊEW+N_ VU(o?媉إ'hY=7tSq*Ҋv%𾴖 ITE @ u[Q~5YO߾qi[\ {g'^\;$cz(Y~SvYY\wu'Kh\!pb v@ZE{GV߼2C[{k՟l*b֚N= =e6[[`뺧N*.\躙@ػ#Mt@|I0`/0#gba4=P'wM6COX$pgIVW]uUENA@UIу[ FpLћqıw @Tq "5u @2d[ԉs>; ҢG`ɒ%bرћXfcr狜Hd2@7PTɁ79}g/02_C`}ϭ6-Z{n&̓1-k+z;lL OOjJ܅ 6ت*2U ĻOs.a{ꆽ/QuttMC`ҥbĈizq&y W ;ݐ|;! Bj=fC-h 5ܜa](yl3GK {u D6BP˟uY @𤋮47DwwG~~'&PTT$87 @HA tݲq `2r+/?3Y-!3!pg 5!5K`-[N2R r,PB; 0֕ 劻|8mK!1qSCh9&'# SO=5m;@Cq[s` ! K@ȈݹؽmzŅ_\Db]]oU$ʯB//f7Jx1x@sP'\"0}t1otёYytt:?-3<~Tfd@EE\|x2Rd @` @6-+)١zR윅Y)^=1!us2LRL0ĄБ A%l z_^Rୣc2|Y70>aNL 3ދB@Z0}/9<2ѧEm[ IN8 Q=%Luo  +B!Ė3Qx}(3Z?~<>ʭsc >C܋I9G6͛7OL2 @zp 餌,Vg}MZI^L:U4mWJK <`=tv` X0r)2  @M}KE8Z'$cY9溃^\[j-$/X#xdOPdNjT{|,Y>7 @~ w(2 P^@Qa^O=g2%eOUZY|ɩns׵ZZZYs#-A fǓ:7 o!@@#jBTjGw).X2%^;Lw=+::0a1cF[\K@kv? p  @U-&72&&O/Ə,NtkPY<10 ^(#3N]{@ș-f?I?>}n@%.Z:xbPx f`C,Y";OeVi"&G9 .eeeq:s CxoTSӽs{ <6]ZI9R̛7/U!f@8GPn·bVRx  h&@@3x @"ѳ䱥nq17e,/XCxdZgkiZ3E"Zfc!⨣1@&P:8>x4&׈}gwPk ;69˖-"{tL.2⿨(;   "5r4O //Ovm/#Ṣ@c"'xb\ h%PS Zp~ѳ0~WDWwaTrlH]۵gۂށJJJ>" @  D戒G|ƥ/AH'Vms2LJE# 8 dʬY 7w-) '/SD|f @;O;'.&W!8c x#jy:m?vӦMYꪫrO32s%pǦC@UM: #  &3l7]+{C4vk %$p1$E{d {-r~0 4>|X`A[~C%1NŐG=D\w.[qBmc Y)^,ZHzJCt0,jT?vDL3qEYIE!JA@}w<.˖~c**%#pZDN8JS ԘU"p ;q*=AURb_؂T\__qoYu|VFI(NUq8s `E8>fW#Le '#)d(uZc!r+9Xx1t&@ Uԫ=.ӵ:O2/_^vRF6ZӍbT ̨@f#8@pk680@i Tgg֙^ ˸^fG5Fv$:Y䄲@$"(K3i@P{zcY`YU&Z݂fTfC#jAsg4f̓)j 2ST1 @fP g-Y_~ʡyQ>qFkPV\\,ϟA"t& ? k"O@Eh -V#@IDAT0Gw2 @T^{4JI*Hs, p a~|)C@L RUM!<|fs @f>`,qC2Do#Vovх}xب&I'r"ccM&F܂9"O%ste~屙ba=(G`̘1bʔ)")[#F8܏l2Jp֊ q [f7ܤF)f̘>7 @A[V\0"WGpBZJ;߾OAd{gϞ-Ȫɖ~O q5a xb @ 0G"XuXo0#-ijkw&PkE#~Lx?'Ӂ2%XRqs@qvqaO63v jZb@8ݯ>!ՕCy_7U ȩL= T) @<f E3ljFc  5p*@Ah8!SK|:::T8Daa~H !![e%>[ןGr uD @ "1gNY硚cudjTB S_=m`E$ x 4uA^sQ $h#P&&A1ڃ×-[V___Y@B{/cf@q'PSw ٣=bDZfOR[ DA`$W;8PYY#S j!2EbA[d^iL5WzAHFB9Rs є|867n/-Ȕnq_@ Tבg<衩GBQj B!rS*38r7Ro|c  h PEDp 7NzLɞL}Wk<<@,'Tԍ/]0\ c(h (+]/>:Xꐳv :8ڐZ:}" VI&#Gj@?(xL  N@ 1cƈ &Z, uj")T dBk֑^Y`L?L6ut{^'m/G? -Ql(P\\hdz';+zB@0' ١R e͕Nf8c5ؽQDę"z<Q Bz3ᇀL16~1 @tJ u~N\0)t }MA@~@;0!ׇIGl&d*++=9S^1m̙3Ea!ŀG+ b @/1< l_TT$MfK=z(߰a*i3} !$ cŨ EolΉMpC2 H/x2s/TCp @utz*w{bxP 60ȇbdNpOuRG&I3H[   <\{$o !WLZltdQH ώ },Itݟ3yN@kw'Ok@mXg[Cp[ya=sBgp06 2IPG=ܘV<2e(--5 8E"B@0rs2*C73l Ą k -{z@#@@_`pB0d3xڛʝLu'/gQCfVI4@}02c=JptKnkޙ5k̰***6J '@ {H =j[BJZe,RBف 4"qU`T^8F ^ Kǧ=AVAػ) j'@v}CaEQaU(C`ҤIX0dE =mkQde""jґY`0 @@2tL/S3ۣdxߡH?hiMZ̜9Ӛ~ ]@AA|)f߰,oA@jdn9Eu @%e@P}~Of=)$ m2iC @Ȓ@{GonR ýRWRkjm|#(&C;S[yhi@i"N7itE dC@ݝfBʬoc ̈ӛ `ڐʶeꜶlٲwŴyd<' U@p@ XL$S0\N---p˛̔23sT3@ 9 uSKGp‘Q#[p|t@EEp9nL#VPM3~g\%PUKo( GgƖzKG  r nL#VL>=g $fR^ZhVaLm1ӜJ~$N?p!L'4bȖEM<٢vTCP&A^!@?ݕU #1cg cp!Z G$ #0|i F x"@&mJ Bw>9\w͘4ivE`C2|2(//Or!Zd! @Uu" /i+zJ.|„ BCg C'+0R\K c >j!@ )꺖eY,zf#yX @y ߮H @U-s \I3[i-u ]6J՟0 QG,53 &9FqSocȊaPӾ=O+`X%>fDi*/-40~DK[Gg`( X1jf,j TG%|Y,G- $@ %7i]S__# )ZBX8ᢢnabKdxm@nn?~9h x$P]'t` BV=iG_ HDžL8s>a@[iYU5@p<2L&l(l*yHch%4<Ԋԗk0ٗteM@I H"RZT rT !TVWEi n)-);ksc&@N[,{7Te*an `?{D/ @X7z; d1gQrIsDX X ":79^%- `YOQ0;+Wo|-;!N`buX `H`p&$7 `Sd筶|G V3 j$AuW.7&mMRѣ3af @!P][`(x=020:*xرBsNG@>XYYWoH"LqqVfΌ!^YPfX߾O|' fT'NG ''G3=eQmN@K-G- %iiPVRU^wgQn:}^l`=c6R:::  ٍˑ?FC@p(DHD%OQ[dn^nEv:rZw_oH@oj,/3[@a"PU&sCo+52s}m?Ӌ 1,%&1)bc!,[DK>RE2h  p YqwFȌD;T3] LkZS #g\&)+)00ښ[;? >:P=?YeU L4 5a9!@2qՋ $7;X  Ӿd@:@ f˂ÇxMB@hլ筊u;a QϚQ\ZZ*d# зc@@O4f{ @ 'ZRwV#c9K{w?  pщCl`cds 1أRk]ݩ;qW+ZEIؽn(M)s̻ܘXfd+A^(Ed |)R X%PSQL@"OMk]ns!g9MpCaMuPX{ѻdA6`|)x@M | ,/a/^*.$G(~ϛ:Z\zP؊1:l*N76K&CJ@AZ@3G 2niՊSD:Nx__W_=RE EEE4 @U-ESףE[ vn Myu֭eLL@ BT} (֍Kq# hBETFe*G[[[y_@n%W[ҌZ@@jlH'QH[d 2O\0I|#-hFe =y@ K>` vLwgGkE=2y,4KQ @i } -iAhdIVLBB?J X T´ @mchԗNzD՛ֽz Ң_ξ+uH3XEaa4"$M":*% 9P1{|`cYdvAAA4"9#_?"m'] |gO㴍N}@WW5,/% ^Q)zS^0H3F,hcP/}-]@ G! nU$$Fcg#}v_@? $#PSdl>B^ {۵Q4}qw_X}pNeذaa @2J_K0`CoUl /#}zް9G}(+E4{! @(^xD@ByQ8#rk6L<Ĥ10 *ncM2GaxD/@0O"ơG Kq#?bƄP؋'Ǻ)7ҟ2  0N > )Ey f%L]"Ŭ#]3 {"LueZ7%S3m@! ƽS&Jnsqz+ `&i'ԕSSVgB>_$ }z `$_XBZ@HCc z{\8U5a&՞ǿ^q)zq '^@wwwDwenK?mlm0H ;lbK~;B@ÓGW3XX<[l/m`"*^T`ބ`a!=#zxG׈ٓ(8:k'G;L`):Mh@J8= =ɽ/mGO{Ŗ-A='4, >ck^@\!P ] E3ljCPahKO+˅/A X59"أ` @r6A݉RbtIsB\>z/_"yM! ZH-`F02$@ C`j⎈1cbdIA^ٿ+~ѳDn1r}(Ǻ`| ,G- $x92b"N^H-@>,IN]"?`q\]l_j 71\Pc,[G/I]Δ-֝ /YZ@HI-)rsͻ씹 2Pu8\kĴeouPк#0 wGoxy1ѨƥϜOiB KRHW 0@@pm"Ņbn8uєõ L?L<ϾgkaFڳ'_h^K @6f=0w(1,~NOC=g&^Y8p+z"NSГ~P/ @F dL-#j@Ytё"/@(Ζ9A`(>CrTʁKK u"\Wv pI7?"}vzO7*v'@t >]s 4Ѻ)15/ALϴ!Z;EM}'[;DWww_0Uv4} تzzw죧[$.9i(G4@` >y~חPPPM;@K`::!::{Rq_R#Vok6W˟*~>WlƩ[{~#7 6m8|Xq|pQ\?syo%dxc[WzhϏ7gW 4zWyI8︙gjzO`퓜;2ƏߴiӦnT($$mtA^ rq߻W]s~W^Zw<ƾ[9l%*%yMjR@+2#j|SW+N/[$Vh0L Y2RO¥7, `c]2HH@=M{nΞg#T>>jĺmz~Vx{ )a> 4550@Y-w6^66ɳՏj3&=v8W5VA6AA*۵- C-ue=?ј@8 7yrC2*@3L/aਃ@ m7GMO /Uϼf1S8u<:@g"4sc&@>>}0`  K`:*!1Mb!M{*DG&U`'yG-:jzOO#&.zlaFK 2Og@a&Ǻ~`ݢ ngC g˧32#piUK牱ǠحS m@46٦PlWy 'J=O)$h  ST?,L=cbfg͚U?9Q>*%Ek6W=VbN)*{~fM)npŢ0_S%h>ќh 3@ʎLCGz=T_1cxI@ vT7y][{> ;q_=s`)^ 䴍yɩQ{i 44pyh9mO|`xBUkA5@3K #P__oO9-} K:`.Y&O/{^@H@XdL!\tS"^8a!º|A>M}]wtl, Q  @:v[ k:::1|F#K.|KbƽA0 -dD˟HN5Or6&3S}%%%&tc !"E*@ Т/ fyzOWXbE[EEEFo2:h ~x E4DJ"q<@![6G0j:dA׶9gB C! @@ .-o5-~28\ j_?#y}GZ@i (촃0FP0@F2?3d$ %{f\xq T 9ݘ1'!(PkzD㜜=JiT\uZZZDQQQ\0o8EKk[w=+6mD@!h9@ 466΄h|ܳ'`wZ-' <^,>W7p_@ پ wA '&Nx }}ͨ^A&x=AxLY6| 42q_ G#vkmOC0 `@.@= `k5v%Kj"A|W @N@,fy'W@:6.Aq$.q3x^4vs")@`xZdj.Po?zy" ~ؼ.mA0N /ơ0Nq-_\UghܛKݙ m߇׆05'ڝ+Z0@ 80grZv@}}6Y^EH0쫪*/d`_i;5 @ fa; ZTWs$VA[=db uHxm@GGJh =[|eJsC =4@&p-Sd ) ?XHLt|Lxțn8/} @XJ@(8ŝP}(ABI\ _@"4TqBgbgMcHg`b.\C}9xm?2QE 8 ЎV443 h  L}-[[008J%$XH7~zSU8 J@>P4@"@vuw;& `Qvpo_ž@o' @I ԗD5 C3 ŽT7}l,#4d7ף!looѣeI@%K YOT.J|{0KSc ;w]]]a056H;$Q&ܶoyZ;Z" ງȆ;X=TNg:ӣ)˒ -Vq |m[|/l BBN8jH aۦb`8)= > @l 3,X8 ip5} Yp=i8hh5G`˞:qg~'mgN) 8F9s XhGXkccd!тy u*B">[uoE+P @>N,! pP[+V<&i|npgޛ!i5ZPOU?I 7U@8 {$$|4P\\T* $OFljR !@5B+ 2#Z&3^AV[M_ Z3Q@$A)B2bz `۲.~H)p-lSqcZ񳢩`fΌ]#p?7_=Dk~@ B`ǎB7:x9FO)[N#@JMpl썀 @@j@>7|ӾXb:a"⍭$yE/@ S0=$z@ @r'URRJ:K#;b fđֽ⛿~&Sg#"0L`ݺu5n0aÆq77 >}&^۷ԧIG <.[]% dK0[ `@]]ʬ{A9pm-&ʚDbz^qï[ DRQ)@ ]V->8u7 gkdo0-O.Q0aB 21 W`* )O![ԡ@jL&@Nz[Ԕ+>uV1- |.MBO ?/G~L7FcǎxCp`rܲߋ)<xQL H363^ݰW?վ׼ `% XB`P$kx[3)lVo.Ǒ.6qQGՇ2@: TWWݻw,2ݢq+(Ϲ0,5k֤-#7Dgg(@ ;+zBXj{FӢƌ-?`l쀈ݣQ2-@1cʨK0Ȁk `+VtfWy2!L Rg;ՔgCL[jN h$*%"/L_Gj_C=:m۶!oxEA%>'0l4 @lڴIȴ@d#43~ mVF3 JJ7yJ?@Ȃ*XBG{i}nеo} .: ?Җ=@L],'@M@ ;|hnno MOw`q!t{2y\蚔5| +,qxW|lFZu2<*K_VcAwbL &s n@W^l 1ca `JWk ȓ-lfc!޸K`N`x#'8?/7rsbB\%p  cK%-}k&U? ^"@1Tt@@Q#釔$@`M0wʌ u@Mk@%Вb.@mΘ(J!#_ycP3塃i.hll*et 8*#CAtA #Eu A5Y@}JTf>kԇ@iQgRQ މL1"3hO5''G[E'wK]*q  E92&Fqj  Mjժ,S.5ioo/bvB G$ `cDyI5( Uzb C+VԥE{o_-۪@'@vq9qti\!'@q( " x }Ҟx(#2Ck֬{̜ȱTq9 `7 /֭[_#Ժ>,Rp#0c]]]xE఩5af @03fX# #5/_E5F4Y[F4@SFȴ .R!X;&?e-a@7|s ]@`Æ bϞ=42,LS@šٌ@< ܹSl޼9wwmZ e,VXݓ+Web>F28pz)'>{f̘}?w`Y>ya?^káa(VB9 S!cxcLɩR{Ao+X$ʘo>lL72E_@DQ^R3 X$j*QSScT' HF}`5IRY4<X 8i$WM.@%p1y`[zaU@A_|Q466F}'#f>G3 9.s S/Mq7F{[oC A^C"3#,r;:9tlbF@٬n` Hr@}㍉>9k"TD0@C\Py"Ґ\ '|9bnPwnnnhwF߸qغuk&D% 8PZP@ ;7o[lN/ئUh?aAg~:yi{Z9F9n!AcK:@ud硠 XbEĪ4\=>E=E @\~ʡ" !7F3<i;?߮?ideLc;I ;z /x@8rxL&@8iOYH%}J2PQb#e48ib$RiƊ8@@3xg5KE+n rR0b7:TL+&.  @*@uTfuP%ycĞ-?L*mC &B@I{[* ˮ[u];KIH,A QDRH HR?g a9|Lf=yg s=g}{PPP0I6/Z6:k֬ cOvqI t)]rE""pۏI)B@h \RU߄i hLlv0`SםR%&  {cWc$@@ UdvJʓ{{=f}= ⏠@rE,@` \3E8I^ ;;[Ν<&Q*/;'iE^%H=e ɴiމw}ε9 #@~sKϊ̑ GfBjpt@#GܩIN /`nATCKJ~ X@tLi(WlW~JdWeUZenrrfZ/_}  `Y|gN`رcgyUL:tC3ARW|){'uJ`՝ʗd@ R/Yn]yc@s?::2\$,&ƍѓͷON^ lܸQVXUu;&7AFW7wgՔS+C`ҥbfO@;&xUL:%m^I裏No$`>pKR&W@ lzɽ7_`1cV7)0'55+1hٲʱLT`}vO2\QW6@ctb:@m&-9Dcu :,h<`D`aC@Rxr}ÿ@@N, @z3—ylb+ĬƤp~*nζ>#@i|Tx F ''G>sk!c>GĴ ##KsGRM''"аNuM]I<@2Rԑ{cdTHV=1:V6AeB  ~۷Ԯx8'Ѐ %1!޹I%`skmfy&?.VSo2}U'Ԯ. +8$GJi(wpw  L6z-m-X1cƬ zK ɫN siNN x^k!  KY@"1oTbpiKv!g϶$°AfDyd6B  w\^.z(̙3e׮]=ؚ7U^$y5VSLaِX[Zo+;9N4:B#`@yg?NᔊDP,{EDؑ#GFrgE"Ө'+rv dQ #PEG>sg!ʑ&M.`ٴirNΊ(`F!py}7u;%#Բۺ$@ouN;vXGhM@ZZY|~uAX+Vʕ+}ߜ=HK] \% |] 27!@%^InoE;1v_ o& $W*"uP8 k!w;4uԏ%1[Z>\If_F6 iRl 1|/dF|CןJ_ @ N+Q s#)|yG6B/1bT1Vu=zfq20 */]} G !/z!~4-o'+#:L@:<wP`,USRZ%w l0W-[fC(P ofoyusss+;Ɠ ;'ۅyXbC гM!r@GowCQ6Eh]@fffM XXx1sa(U+\ܦx@jK'pπmҺ?&$b^`3 ~c>> c'~#5q:. pVדtV`̘1KiMPEd͚5p 3Y֫Q̼l ^V~~ x@aUVq#^-D+; Xe[*vn( C@tj(4ɌfbC"o%TW\>նZDl񈘹iO‰?KN6#V ԫUMzjA >~2Fp0Q#G,Qi˯L8,hI4ao=G qz,eںYc7@b{-nBy?ms=&3… ]g 23؃@ <2b @r ̛79iI3^ugu@ZZMii(Jk:)%OQԄ6b0eC$PRR4؋iuAc2@[?:"6m3g \zRH3<@ t?u+D!0c ټi,oY:<[c@G|x z\fѢ"ȿ 5v/c/B0Oy&Z@ ;;)*_@Eo882Cعs|駕/-Ф~L{vТA$9[qS%(!L:UvQ;d].Ӣx~:0U#A`o-"W|mtDD@jHWd@3!T|yw^` ct5;[}0a„=x-0%-~h`Cl tĻ ij ?on$Ҷ{bsY-P`VzE/,vߡMa T >Y$KZ gy]H9K= tddd,RLn6e%M46z:nm x@ @fj6ʊT@5_]wm6];H7FR0,>}deeYa&PK}_Y ?k;EhC<X~|GEXUhUhֽXUC@;hW_-꺀/w7Ƀ/p@ ?ʠk8 +E!@ wknm'4B_4: vt˖-El'`WKB$#{!w nW]ԄDA`t(Lخ稾w߶u]E$7EPT=0,\?nsk P3荇zsE80ߥ'NhULS;voD]YWQǔϫ6l޼Y>3B! \n+;og.ZFRUyaC.0uT1ߩ٬ /ZQ^\\(<*Z&`V˳,*@)%EX!P'9I>x\#Ŋx@nn[)c ;5WKXp]v5wvj G,Ub.u`> a?ghƒMQ <HiRG>~V٦G5R  ]LY|yt+H u3_uri'#j=?Pl (9q:;?^,=v4\I*&pnSe;cJÊQ efؿfޖq 55u2t&^zɦŇwkN0zBFh L}692\^|w6ߡХ?F[ѹs=ri;OMF O2~toڎ@Y:/w'/*1K!A={{AI'y,ԙ$};`gdd?#K1RPPR8yxE2;X%   d:@ʯ曥^<)m8݊x׮]fQvʼon)f6pCQ9A\n8Y";#;3/֤LEILLye/Y$0e1! I Խa?3ՋD %tf.@۷o>Gsz cC-H͛WҥKmk#B90޽[9N ޸ ++XqV 啿( jWl$n 5Jlؖ3hҥ1IJJ?l3Y#/y77 NЫji]FcOnZӮAiQTT$`Ϟ="yY(мqmxM'ixٺ'( /z|ĭ9mi2Ct9*-^SXo_]1w^ C#(bq̙3Y4)Wto!^tfA_F ]fsќL`\FF7'On7t.:)`7?5^Z.IHJX#,])7tՒɷ z@֍啿 I ˓aÆsAo`%?LArssC ;wgfɱmȽ7t5%瀙 *#`?s_/+e~%"&L ˖-wE?VWkH Zځ`>֭vIÆ 8JjB\pV:7-+6߲hd<0F9}S1m@W̉`@a\\mQ0/\n }<}RX[-㤧wϬٲaǷ@MTtsmgyrEgJRUiS6DH@~iazUشt*@ˬ=6\ \^~*XP:vX8 mO/4p^ahT𻀹o&{}hopUG]#)? W_}U/^(3^70 c!?5kHA(0>E]R\BO3-U No'^ ݥANj VAެPbY+mرM$(u~ ޶D`ʕr饗J||?Fyխ!f T5[)}𽀹M^e7vS%>'@")PTTb)+:mdW8b@l>\uFT C 5'ŝNo6Ejm{ʏ5BK {թ`X 33SB RuƸ0uvX@I^Z̲s [R'uzrۏ騀.zBT=4*`~VsDtv˝:HZՎ݁@:GD|$_G j}quf]A5ի+Ոƍ?IJJEԉ@J^箓N^(g ŁR}-=gYڏ @l|:tlݺyFaqdLN0`ړO/$ eEO, Z( F4'_.41Y) e^CRbL'g%ORfy?6@ ?^,Yr*oժUoꫯ^3#'ﮫ?V룾~s" `SaY۲O+,q @J:-ͲL;5!@p̉SO=6Cz?C/_NuY{x){RnݺK͚5cu#QewɫӖ:VlѲ) p%& ݹ$>ιR Ph2,|S w g 'lp_}VQ7ze4 ?t]A~_]^FsVl u*Yi!zt`\4璶RF1  `#dΜ9I| |xUǦF';tf΄u-Vva֊T^=t+@ *r8 ;=t5w\%Y+w60W{Dnܪ'N?\;CعsM\1g ;---ݞUMx%I:~;1!@qAdShds;t!53.7]Zn<֨V8 @tqydŊ8C,XI/Yg!|UEo07L""8u]( ΓOmOnOl%k[V6nsUruT"9@ ~[&Mƞb^ )==mcfL.wȀ+D#/'u֑/}y 2M;i>m0NVU.t\'a. Xf<#R\\=t:yZs)00H;FU0=aA1 uQ/GC ?G*n ]yG=ynFy?@Ɂ¢~&+N|}_X!839z_F硟5^O~nj{so!)0{lyL> YHeUV2rȊ}Icv . غu0@@lXv+Cأt<o;ۼl@QQz:N`3! Ov%cƌmGf@  ^ L9WQgq˖-}[4$t .\ֵkte[ [t菴k( @@ &M͊l>о>3k}GTgYɝ㭷ޒsZ! V`޼y2yAЋ?Ha,4NR?DϜd7޶X =z%@@8Z,mGfE7p_^;gGy~~ >\rss}9" DKLC%///ZUP饗x[j 1bJ ðym۶&! x pxҿ۷{PUx ubb*0[`nݺ] vPZ\\,:t0:BB@J^3fxUDY@/ݞ:xF M`ʔ)2k,"@@fΜ)GQMt4D O@9Z1--m>f_ @@ YYYѬ=V}\l΀@@Xb R SC-AS%m9T{eĈm6[B"@@ l߾]{9)**` f]{Yl1c,'d1neذab~! S ;;[|IʊzwBB+k+[}zv+ڙغuw^ٰas9,v$t@pK$tK'?~>Ǻö@fäE|=_*(:{\3s>! x)߆ڵjC=zh̘1K<ҙbS;tI*(:F۷o'|RtFE@  {yyy`f?[0~ժUfvϊ[*7*@Sxos=W<@@̭O?Yƭʶ@ӽNoq+me:+鯴>X{]ŪUdRTTuԇ 8#pxE&s-Uhjm@IDAT(Ł0AdddI=Zg>@pB,lɢE$g:'s '"z+VwGUR7n3MnD[fB@+˸qdƌM̅zK:?3;F@@.^;ꇻ 6>uC`bVv MJ  C'ӦMaT텀,--mu^~t@Qgi^U%ҩs>zeN=  <s?~x:s2zS^全p[0q.?^g33YN?"# @,&N(~a,Cno7W=r>޽{wǼ/XzKǎ  ^zQuV@z]'L_0ܪ1ho^4..nVD]vF  |Myw#Bzzpvd 9r\ve XW˗/xi۶&X@@k_ U}̙Slʐ… k'@G0:˖-c$@)@-0eyч%'E:ǣFZQ:"YtUiz]U9/f$@aauY~ 8@@Off'uQItWbvUruUF?}8ʕ+/E# &M$-]3[(4vdU7 !( NV"0E# /t+b9#C_5~lg20Q:,ioy={.N斄DQ0dggKΝCsDE@0'/|GVGPQ89͋JaSEwG]dT#Qz֭['{A'@@@3sW?N*B y+"q<:,|WzBTêbQh,QҵkW:K  `^,~]MTgnnn]zqT+ɻti P;VjغuDD* E@*PTT$#G/2P]z_W-h/䒩dکGHشi]R_2¼ 1((({Ntk#zi^K}'`mb[b{&ЦMyzIE  @4t 6LJHl 8 s?n7| +v%KF$%%EtC@0=vZo+6&&&2o޼B!ºg tN uPT'9j`1 S( D]\x@?55us afavXfR1Q B@/l"mCK4o\N9HI  @tЄf?6'f׮]g.v2{$MOJG|kś/IȄ] Bhh@JJJJ@@ 3f̐^xA̒lN ֬9r.'QtldQi'U> +)` 4t]*Y# ؿ<ӲlٲAXEQ 9AK,N]vi ] ={!:D+Q"  >hhDbGg=zq^pStu$@cA0_~jJ6lXR8 @O`ʕO涀ܛ6mfO?-9s tiޛBD c2k,_4oܱI@>}1B8Uc3:#Gde P[`=zYXN8L03tر#+Dؗ@pY,Go/2.}jJJJiӦ&zF`oL X:YF6n(YVpI@ _xOZF K76l~DL?$|A'ROǹ#!eς*RzHI  ޽{C3ӿ_zfեͫ~/-FfF4 cwv @vvҾ}{SN@$-@@VV䰴{$s/oxp2r;:-گktwij Kt!`&ꫯBuYLX;vE@5aĉ+Hqqk>Љ,]w?  3V|.Aw!`39yy)! @E{.4z"sL`VwǫǍ L6POHs"iC`2w\СԮ]G+ YO.9Or+nrEjjrV `uT.6m|vfU+ `zͼM4M!%r@@J#yꩧdϞ=(C(PȘܜO̽:ݻw_iTI-LsO PPP #D<@@ (P{=Yݥ0(iG*b,ۿe pLWe<`4}Z.)s$''4F@ ߿_F%:qtyezlpuRou>Oj.9h5knZի! > vZgAQxe˖N6u xTvlN GdGW'_|T^]8 %>@?/f[Eozg Jy$@@dh'2]d;fŋY)cǎNd @̼?cǎwyG̿l"I?t]K ` zttSu'ۗظq̛7OڵkRn5="T_ I+ߚUʢFts:-U˦< a斀ƍKf$z@Lad.3{:w  3GfyzH0c7GBK΀K|<&izD?~xyΆ@!x(dplpS%Nӵ_ԉO sKiӦriq@7o_|Q6lC۲Au~^M}HLL>}ȕW^鏀@-|z;=)S`^Karaw+N sγuZ ӿW2|U$gu6@C,;fy͆I]t)@m:ڲK\+C~FΥ]l"f͒-[J \J\@R`͚5O7|ce|ebs.k]dQ]XҥK:qv7UL3Wf̘m۶C (oLˆ@kuh]a. [t$  `8Dz;KCW*իWw'y2E@ ;wÇ˴ĭ@.OKK c_v o׻wA;Dz[ %99YRRRJl@9sgs[a kA[Hz}MQƻ⊊1 S  5kt/y2F@ m̙3\L1)`yN%^갲 1y[j׮- K"rR  ,YDRSSeϞ=K~9!?F8voʤׯ_im+SǺ#`&Ko$w'S@",PPP 'N Mǽu?nJ}9|I@ ʛo)ە-"i5upIUQr|]p.2ܮjs~f5kdҼysiذaG  ZJ~i?>u0ҥK=ha@5Z) 8=POVLf @̽oW+Oz ܐY:-@@>{.v̽/38~=@FD;}'ߙ=@ʕ+C3oݺ5^(..J̉r=n@#zZoXuެ:@]f/B+ڵdE  O\7$--M_cWedd|{@t«e,X`qnv.חo!pBuɬYSOSN9 AЋ){-Z{obJ]WM~-㸵`0q(TS3/,i۶T^ݧ6 '0^z%{%//e Օ"~H"pz[9s0+?R-[3a  A0|φV J^S zezFAfO 0K. zv=QdbEHݺu"  6O>oZezϿ r Pn28^@O>ёfU(}dH6m$rdOF@ fZOj {LMM*yG^țRwU="-о}{;Yf.@8Yvd #Oا^9v9-(MIKSᵈ{޽{S)B8$sКyyyrgHժUA@ )Gm۶E>*pV`N yNɿ%Rw '!5k֔oQz%:L.uQ8 f6>L&M$n"W{v.7۫ -:jXek:7M4'rg?Y2DL`ҥ7mYTv=RO9+@Q TpX@ ÿhtAn6i޼y4L@G6o,,ZĹ#M4RW_!6*C 0TzЪ&p3РAGB 䭷 7C@`K'[A]TbvQO,NkkFs6@N$/b!@>q\?J_o{<pJUVr-Y9 @ ?33S֭[dV:u.[$` Mv ^{B35 @#`:nfiݺu`r"@V\)&Me˖}"^`jjD;:$X##.`Gk"|pO2R:٬Mj? 7 "~oAG8w`]/7t"# pO_~-?Hϰ?Z" An]rC pV}N;Oς#@@`2Xu]꺨f$@`eF6~0stMҦM?O @VX!o,_<𹒠ojԓ}1:!@$zIII23pKΝֺ~h"yw\gC"bs]oE1 !: X-$ohWY(9%ЬY3s|:;"йd֬Y6mp.=x t Pߟ 鿵y4h W_}\z饒qT%PTT$L~>K/3ts7 pNj:9Aխ0o^*& t@ P3G}$͓M%z"0Uoȑ# 7TV r|LX;3t,gD.z@r?i:g$%@ڵ墋. P^gJj @١!޽f46!A!h1Q"||-#h׉3 r9e]&[v&oEXjL:U̙#?>(֓LO*Я_d]Uz@n zꩡQfd78p̜9S>Y~3yhrv=7l6>^`С YYY,C]ZjryF4op(͛eƌ駟JNNNeg,آ3ߨ͵(&BAtlyL;`sĆRRRČ` :A ?L6M.]ꇐhnjUNC+cx0)ׁ쌀E5k֔??t,jBA2t4^hR/eJ_{?>/1'e PV`R|#pHYf_|ԪU @*~j`C (zl-~%'@>2dHC}]$ISf;:z!z/S,##dٲe_fo H~_1%!9J:lߟʤlW^3W@&f5L6@ [l MgN v }cj@#iۃ",`n8 / FtCܹ̉sCWW\:X@/~3p\J_'5.M轊ҺuPg@L$ȆTD׿`IEDVA7['LߴVDqo Sļ $0`F8]@e= -7gwfu>Y$@KM!Ce/W.]v utI]H@ 3yo/\P8Q@`5_;650'$@LMXn?5?3CBtsߡCmݺu9n ~3?o\'c~AYN!P+i 10C/_Zoo߾GDX)V6 ABXSw} y}T:PC f$g;cǎҢE 1*pS@6t?ʕb^cC L=z [CM  S8#0+  @DY\o ɉr@ jpw$ !H wY[{?}!&MFڵk2aԀ 兆+u+I/m.71ES"GJy\U_5 ]]CN=-fG`Cc8 k֬ !93ko+':pa?\& 4"U-i @`0#ׯ  dggڵkC.]pt%&&\FxJEt #CK]QT@6m*[=8ЈzIsbyfYzuaoٲ2E<~Z-,{/`n V>⼯@jO#jժ.P}suߜO& T}+4cǎ]l_hD}t&D]%2e KZL,تUУybIIIΉ!`߰a_>t߾ʿm+u:bl?PCĈ t Kk2Fu 0#̣e˖RvmgD ʦMBWB?~ݷ/yӫ/4?B jtD]:thܺu\Y%'Gի'-Zh֬] .=>7+{+PA]:1O322K@T\q$Gt4zhȋ[nԎQFcB E`:w S׹իV40#BksĆfћywVXX(_E7&Gv3ļg~sy<7'97>?H9@  SX~C q(2@t!OJ߃W@@g4?\ɿ3mN1`@sv<5U q@pU`N_'[*y# FB:9>R;VA@\0iꤤqBmE9C&[y;es$ A a j䅀tB@סpip[g&Q@pAX|Fkff b$@@tj8v5@@bJ/pܓaLS ԥ$Uc@0^dZ>|x>* `vQ CsVxHI?؁@@;Uogtdw,..p @(ԟ{@+@mCd84>}_H>O@@;fzR;! (MTx K p]fi n YҫOU3?X,@ōCh&Ⲳ~uKۇ@@> {xPU @"E  ]2>Ţ~D@gꅈ?g8+@T6a#pX@;~_Gï@A-%]7P>E"@20#@߾}'''Q>X2 t@@-EƎ;ӭ` $t4:$o{ bIaiȀR  ԍ@tj -QtT@^T1cd=WC:\iitN`n3I}sF@_p7+\"X,@ S'%R) @(P#_^_F? GGؑI@0&C |(@NXW5+F-@@ 0+5tdD" pB:NHW@o h<^, @Uֿ_~ @I*W@Gܠ<  KDiii|A#@0" ~%?{~U FVd  s񫌌/@ :hgDsi/q'=@@/,Kĉ 򦔈-&q!x@0?WF0t9@R# (pyV;" *PtvI\ ޛS#2dH:QtcN 8*C?o>E6!@@8 juٟk}0Qw,@ߵ)(bA=tŀ o?n-CpD`[-҇j!' ixN$#:{G  UZc999yQ@ 0t)Ix7|Dkf@Mtn5TTq8bB#/@")PCjIVB-:joE Uր>Z?&Q6  _/..q:7"@*E"ಀN@nݺ[h# P zĒ~TEp' I PM*ޫCɲ) @ _32333ϑI|&@ppY@o h\裎 `~e˖ZdMT"@@)(v 舀zk>-!PDszWG=3H *@@@pA@WONNNse@@ |Ҽ\TT4lܸqb |^ 0~)w>Z&a!S`_o$OJ<z?}!0Pɑ  3gRRR&qԇ 1oNi0b E@}Kx2--m_&N@ :Qb@A҉Q7?'C QX%IfGۣV #1 T =`jVHvjCPD;jH[:  }qׯ\G Xxc&>@ lرcEd C,!4@ݥW~WN~Ԁ CФ~o90qP5 :bN `@߾}k֬yv.mk$A* wac0ڿEp )I"%0p Z2RR g{qyV3! `qV\hg@?}ܩ$6"jG8 }1cdd_F&'a!CߤaWT A@璑`TjP@ tIiw,)Dl  hu_ ]eqr@ v#j@@oHXnݵ/F}԰ ,B@,q냜(!  *ՓͭanHt@l[&#S*(pC7ڙ,@C;^ժUo*V3B nM]BS$@MA, 87/}40p  9Td}KLL|ȑ+@|@^M]EGtZAl0MGfNNL  %:K @YR;āfdqY`AՎЩPΝƈ#)9rAl ͭCl &\oYXG\I3g-O8%_3άUևÇw*{E, @ 2a~~uL xj^gC&;{w&{qM6GuSA(mܘ[w!`? G.ovWVi2n*8l]W7$m\zs91y'M|C}@U,q!,Nm!~H}P ,+Κb7GFF\@? SkQW~Dw4or`1$}/i*ӷwT9 pTQ@_ *~x[BU:䋊SIpA`[A|2#"@ $ĈN ͯ|J\p:`CR 0_kOZ?+u%x?@Ho+ TskkkW֏nTg*HE@/[肾Gl2}@ yR}@e===V t8+24C ٿuu?LFGKt Z4?B =|v@ (Y]onwww~ff& R*D=Q"];m[,. Ԑ xR'J!]ʪ.J G \9D@-շ *Eͩ$ Hx9  ި ȀH]Ȉ쫃RRP;zg;ς @Q$@N"kp:5:Pl#N1 ÿ:)ݎ/=q5`Bx$^?V@wdًhOĀ]d2( nEa)mbدrĆ -jjZ`xx"Nlh YlkV2.諒+V̾>K+N@ ~! {K d2&9d$? ҫ*_F,iB`A 5B8BF3謁NXnhTZrs{*:OU},ONN>x_  !%"%ZuVulAF:KiC:rͿ1Gߥ&@|@'`S hPgI%ݨu0^i/]75BdCjuM+٘   f&H@ hAeuuu:m# ,9P$Ak~JTz \gIDATz|R[pmus{ϦF2 1_,  > !000d(kx^3v~- A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs  :(IDATx]E/ÒY]% OA@1gS(p(H>2HNJK ˒q 3{f͛YH+)cʑ#G۷P,YƵBQb+ qC8$x?~ \D,.5*QիٲeDk f͚uKjjiӦ]ýR@15J^z5:{cP4u$ځuZ~֭&Lf2SibPn}-9&"TA6JK.^RMW I&iM;!=T%  ?~e@&~?DIYCR1DP=F!3f)eDrhݞ={ŨKHKv CBwP(Va7QA`S[Υ"ӃE)PƍC0D Q+gRcq&zc6HYAie~T0dܸq0TE׮]{CJ H#:B10pAWzWm#0ݎFTrQ`t!AUΡu0ç›0*JqZr1ZXn!4Qw&;?QzQ {@+FYo?8j }lԴn @/pR,c8bT0eN@Q  Cg{f]V) PzM`4 e0T?b@\ t~ީ@Q,05x FDlCq s&h=W@X`wRH[)(N99d ]',X+FEX~ʗ/uY8Ky+!x"&(_t.\@+?<%''ӧB.%`%9 a}) g@SxTlY-)SJ.uz2t:r>|X pxJNtp ˅3e[TYkF_JȗG *PJP\Q|0ڳg޽[$aϰ7 u(3t9] iUZj֬]K*E<xp1ھ};mٲvAp*㎶ cgk.(x}\F ^zZR 6=HΝ;5fam:Գ 2zׯV? H… S aÆTbE׏ѣGiڵb MN^vsGݨp!~U͔F]wx:e˖њ5k8Mo0ϩ\xD[3@h>GԲeK8)peZz5-^8:b ݢt y<]tz;ڵ}Ce߾}4w\Znc1 ]v:®`_!h|YoѢQ[iѢETJ.:9};72Fqy u<ڷiӆrVEwkRO! ;q$8r$w}7uQ%7ҕ+W͜96A/AGZTknhGT5k-\0}?},y${  æNdٲih:uУ>^Jɓ'iMԂ-'NxL1E,GZjKbKO2:$\rԨQ'tQ okmm۶{ O^.3g͘1ÉES8?O3'}^ldɒNBKᢓ5жmt JUX-j;bĈ rJ \#a$t>f͚E~=oĘ͋NՂyprXsv98j? \v\xs/mN5* p&Mhf!PSJ6m鰱*3w?U6R?'gc;*:x?+ bV\Ww|mci.⡐DڨϣES۾mR54&▵k޿`ela g*\ŎJ{ Ӌ/C5OEf_ JqM2+6Ҷɣ[A!^S]`K쟁۞6C6fӁdHgoeVҷ,>Ժuk%-hX&/NɩR\,e~)p$q2.?xN.@.C*z}Y԰W_}U3w~[Ӵ%;iʢT3(%,: bGݺu5?6J)%I[]/5ylsS|yz4?..Oө$^ ѮԠrI*/&[A*7ּ;wζjT S520B矋 UQ)e>dqԳu, $ vui9k eϖǹ~:+W.jڴv3Y{ ]Qw]@+e_ ~"0t:RjHu^E3haS`$WDt%@&"oժ'B<sr:p|XDN7YJMQV( d;ٻw](<*C V$B@Ϟ=gTy?/u X7nޢXHosl85hAVl M |Mp'6*_$Ȃyw@(P.]EQBP`㞓 ߞ& wީ |Mp|PLʀM DAg ڵEGT/Eט3NH'Qe\- 0`>`+X~z}$0t;4VfHv٣GeFjaJwz i &Gvj\kuժU#vDjNӑؼt Sa5"VXÇvJ}~Z䣤)&SfLv zubjd~P$@Awe4[ ߌjA#xi9l.a%u~gk(Go_z+t+/:.[X#(D/0/)D_||fXCௗuh ,|~\Wet `nΞ=k: aQ(כL)3"AѢEl: pGl{>;5b&`{8 h8e}@J *4  Wߤ^FOT67 jߞ GmǾd Э[70Ŝ? ᦋ(O8p<}5Sg/ј[ԢVl5l\0^9SL(s2V4dƍ[%Hp1}C:PXKfl6|jjZ+0Q1=Jw r˂7GΜׯi*tPf30 r4Eah&#2Z~} we2S  L )KߘNSV!IP{t;x I*&N Zg{D[o޺M:N4_)dy]+CշuEmȔphӦ D}`Ec)B(^(}?ݶ}3z?Vo?J=+8zIP%))n=eb):@oH8Kza8DhQ 捁Ï,4T*im@J a3a>TS._Wfz ]v4+{@}nzkaeygY%x;L)Ax?9$H[WW4xZMe}yϛflE?XbFzh$P(+i;{N !!@ÄM? B(;/ׯܭca3V_N g0Y 5C1n>*d;^ AQ#$UCJZ-鯎gz2Xqb>[lIp,ꡏL)` `n{O,u]Ha;H=e [#*pΜn^ OpQ >Y[6LJL]4@Cc <$lذ-'?}^0} |\٦z(?h}4mː6h e$`+c$#PBҟgQ@ Cf^nGm{ 62 ۇ$д6 0UD*[,%$$X" 2U3{6}T9Mǿfe. mrd54 `_9ZQyU ({VÌQ={ YfM 0OQyCЋGr՛MxUxyF%J /RK>&5 KQVrf'6 gbo!_U(̇pX bH $#ps`#ݗ ]œu][jE>"?oW,Nq-);v@} WI"ԣ&OQ@V(}! uK#}:8x`uH.YD">OQJ@%nV'8q^j,0Чç3 Y0;C,YRVd"=KHԱV*=L\[!W㦟ʒ0?AI^ΥQ|) @7UP!PuzĂVOJoR@U1| >A: 46iV @& {QK% +IF_9],Y*,qG"RyA7JGNuQjT^k_@OqB߾J dfj+nɡ=+5X0!" c O,4Pr#tE/Q1N>}OYW }F%@xiC) R`pJ[}Sk s㋄UUFu>nZpM\j]ݕhUx/GPT) nZ&:%P=(Q"]t,zgϞń3fΧ[W'hF T@ѢEG`9fcgj`fvsSQ)'<'v;gB!>kY)lc5s2Hg V)_j1-pU կ\Bx9 wJgBs FCN'x#˗;|?o]NM_'Ly>ߥ~gH(F@"++4{GUޢ0c;iU{$bg͚Uh0"|*'_LJͦK'Z{x `eBJQtd3O\_^Y#:=xb.Y6 RX ]>Cj>@EXj,9܎%&L]nn/bx꿄S8+ĄB$y[a -ևN 2᥮ d,)@3A!^l^ia{l)<I8# O2꭯g#DJF^~.^.8B[YG/2g *@LlT(ЙF5Мw Wnfs* VN>h])+S0xDs1ds&k6*B%`'/>Ԁfce?'8 7I $=[W[Z{L裧ZQ,F7` 9* 7n`"hB&4nj¦^h}MZtj } +"?7o^mDZॊs]3| -t-٭:OS-Hz4 @B%0R^^zΦ^xDN@ԯw/j P5Yxsl_ݶCD !3 NGS>p69A$>Kt:uk0wQGxɮ+66)})~׍l9DJ?CL/_V)IZT| R:fJZHWfo!qAoo{8'9ۏewJ[qHI&R0^7-k%7oݦ'Rh[>0h>e*n0BEk:=E%pYpE)rz!ǰ}}i$#:ԁ]hqx{J*%VԔa Vt RK҂y;) .F ;IVJ=܌S.^)Nlu@2FцC"_Zt QJ@( a 9P }H~ 9k3MZMM걎S&s)ST ]E;I:g# fi @3K_Kqǟ>bWJH#wg|X?ZO A^yUW*l8$kQC16bS6R($z!;zk}(2d?~0GAxP:s-`رcMa,ɓmy˩)$4r ^ĉ`[ DQ՗III"wy]| b~+41Kg 1.hϞ=|q4 z)8v?(0FݻwK`<ɓ'kTH P)dJyk|>ا3 K<p*b8r; ;K#{ s?L |, [.ljY{YEl)_1or /XG::t-ܯ 'Pio*-w-FC% 牆jZVm۶YW~Kgr -[My-nXO6_\1CݺUSJc0rҘ1cҗ30e;v cC<B,"Md[]vO`" sB/FivqIT|(S,Θ*SH'iI{+D4Ӧ[E"f&;ƚIi$Leيׯwr @ X2(~G6WF'+Wn7F$NBEs/A'V-ٳu7x Vғfb8p 5_Vjcz5@J%d2f.0a-h5k3 J+i RgƂ6edbB&N0s9foӒ) Q'D'f2 ~ lYe_Oi΋K?}!Р=z'8P8\dFBQ?%~~z I~4lzjEʛϙWGX"H+G% ?#\Ru105d˖[ |h"+3kZ* to] 4.]陀@뀈P$,t>uѲC]T(R 7=ؼR*fsA8dZܹV&Zw>=F9%=]r+3g:e`rV=_n}p"s M 捡gn6Qy LK]qe0 ڎĿq.L,`C{͞M)+o`W`=X 1>߾h@+#(HK8.XV[d ]pLQBqݮўb\ahh'{+Vf-Ǐ6$cнaB{t񣁭HaE XrUd K{8(:H{947TFg0gX|J{RYkPZe%,Ο?_F/ùHH^HŒkEVԏ]݈ɿ~4矉 zV.'Xge(`޼yt`Jy 3Bys-)۔iS]0].oːx^ 0?Sm@<9i]}9s _Ff+3$e8+RBfV?nN^|zgb5+Q̈́bW9A5Cq$F«k.I4f( kKJAahtu[34ߟѝdF%k`; ez?0ߗ%N#G,}3~ƍ믿ZzN>AU-ŧݩVy56oLac)6\Ӌ!.v,[pxSNb) {6eob[hKD]uP߲͛$HSuݠ{ ɱVZ{P@ MMMT9;xP Թiu8NؿbiQ1OV>&rΜo!b҇0{X!u3ҿo:qℙf|ф1HH~?s N41TJ|6nP$=s=jBɩiQ^EU$:Mi_:i^n Q`Ҍ~ٳ?ņm鳛\ ސ"LyD^:)ܓfX@a]Yiҝtz6ЩjGLScf4xiDX|޽'C4TFMY8x`2NBة123;LV:XP` xҞ=Bj{#7|P@^*`ZЋ(ҥ uHG~ -zVvሦ7 [5Gj=|T"r~1c}3o>{BpyVo7ޠ *cmGi (%Ϊ$\f:~Afc߿yIpeUI;Ta3ݻ.HRN(V[7oOtO))5{xUϝؐ]q_8.ӳh a%}8yXi- cp pE8I ^zu#uTqC^ e!ο  Nh԰+ 9D4DUF^jGEnwR`ӦM24|LK#5(2+(V}i(# I-c+<Xr2C o؏~HgϞXZQ 3Ν;G| 7(_UeZj Uʇ,ر6mJ0 ]W|G}DG ;/́4A[*pI0J UCw/e JP2)~_|^Ե00N` Cí+cpX80IM }[~S-Rn]My&ǍA(NE;cl`a~ a\F1\ocbf͚ޏE(0}tY'y}Hsa kQZ@;Ju$w^́ȝwJ= #}O@BTBL#`*BX/!xeg1r7!P}tC-+0 `~(1->Dw֎c[VLWSyPH]"R3FrK`$!bf "'o^c+S~L`0MU) O)Q,c> JAmݶ^zXȗ8>|J^_n]2o0P rleJa\r>'rl'Oj, ΝWQl˦;wz*/FEk$#Bph|5kjժTԅ YUT嘤o*6l!vFUZ]~V)0` -gofeʔ%K2J |%oF8=P< YT/ yP ރҕ\>/^Z;]-2EX?{lȭ}6RFo- "΍F=¶9eBeB0$ٴWH6ωG؁8W{Pq[ϛfGL`{T U| Eb]&rx˾^t/_ e/ $,eR)C[WL!$0;=vmg}F˖-sʖ$$$<)b0X&pC 0.^lŊڑwq(.fl}'|D膕"~okgO< lsm'CtԬY3MW/_>ɢ:۵y`:?K1,$X㝊BinܰaCj޼9URE4ٳ+.ޔNA2gpPu%`ke$W锘'ÔF`ixkڵk>\[}7wwW|$B^,66`9%]xNb]q+H.O:Ů #ba ? @3DpJ9rh>א-pMtqR&߸xxOe肗=z(s,\@w](}HX"BXZ"6>r2|RR5,R/(1v^LZE `a5+/[goH*[՘=Ve~&{J:^g7|,Çӧp'w5M_'tۈbcAut^yWޓhѢzgew= ,U0#a%4rk5LMM%cY|MII鹓B[vs5`ku̅PW8)pE1~-.@0j G-46yOAm3u @ vlgLnݺeI@O Wt[KS P ʇsyw^$۳ĚFWǚ(i+  `׻m)*PD,T_ fsJh4p$*}`oatj(Y1Ļ ^$+BcIUT8Im,:5"Lc$!$6ЯaӁ]xiO (`Xba!(JCeKH?~*T@xː$|gR݄K`1h;ӦKf+K@!ϟ)$ne\dl a·pR@0X '#xٵG(iA.H#h6]uzMXfK1M茏>v$-& aIg"z !L<ChT "!J\cn[Ύڇc4C`% 4A(A%؊ApF'dXO9;3xT5P@%Gxmu [̶bt? J0bi/ lh PWϋk8px;~ I3 cY4ˌS"c`ylpx &G۸?r E{A6bIENDB`ic13DˉPNG  IHDR\rf AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs%%IR$:(IDATx]E/ÒY]% OA@1gS(p(H>2HNJK ˒q 3{f͛YH+)cʑ#G۷P,YƵBQb+ qC8$x?~ \D,.5*QիٲeDk f͚uKjjiӦ]ýR@15J^z5:{cP4u$ځuZ~֭&Lf2SibPn}-9&"TA6JK.^RMW I&iM;!=T%  ?~e@&~?DIYCR1DP=F!3f)eDrhݞ={ŨKHKv CBwP(Va7QA`S[Υ"ӃE)PƍC0D Q+gRcq&zc6HYAie~T0dܸq0TE׮]{CJ H#:B10pAWzWm#0ݎFTrQ`t!AUΡu0ç›0*JqZr1ZXn!4Qw&;?QzQ {@+FYo?8j }lԴn @/pR,c8bT0eN@Q  Cg{f]V) PzM`4 e0T?b@\ t~ީ@Q,05x FDlCq s&h=W@X`wRH[)(N99d ]',X+FEX~ʗ/uY8Ky+!x"&(_t.\@+?<%''ӧB.%`%9 a}) g@SxTlY-)SJ.uz2t:r>|X pxJNtp ˅3e[TYkF_JȗG *PJP\Q|0ڳg޽[$aϰ7 u(3t9] iUZj֬]K*E<xp1ھ};mٲvAp*㎶ cgk.(x}\F ^zZR 6=HΝ;5fam:Գ 2zׯV? H… S aÆTbE׏ѣGiڵb MN^vsGݨp!~U͔F]wx:e˖њ5k8Mo0ϩ\xD[3@h>GԲeK8)peZz5-^8:b ݢt y<]tz;ڵ}Ce߾}4w\Znc1 ]v:®`_!h|YoѢQ[iѢETJ.:9};72Fqy u<ڷiӆrVEwkRO! ;q$8r$w}7uQ%7ҕ+W͜96A/AGZTknhGT5k-\0}?},y${  æNdٲih:uУ>^Jɓ'iMԂ-'NxL1E,GZjKbKO2:$\rԨQ'tQ okmm۶{ O^.3g͘1ÉES8?O3'}^ldɒNBKᢓ5жmt JUX-j;bĈ rJ \#a$t>f͚E~=oĘ͋NՂyprXsv98j? \v\xs/mN5* p&Mhf!PSJ6m鰱*3w?U6R?'gc;*:x?+ bV\Ww|mci.⡐DڨϣES۾mR54&▵k޿`ela g*\ŎJ{ Ӌ/C5OEf_ JqM2+6Ҷɣ[A!^S]`K쟁۞6C6fӁdHgoeVҷ,>Ժuk%-hX&/NɩR\,e~)p$q2.?xN.@.C*z}Y԰W_}U3w~[Ӵ%;iʢT3(%,: bGݺu5?6J)%I[]/5ylsS|yz4?..Oө$^ ѮԠrI*/&[A*7ּ;wζjT S520B矋 UQ)e>dqԳu, $ vui9k eϖǹ~:+W.jڴv3Y{ ]Qw]@+e_ ~"0t:RjHu^E3haS`$WDt%@&"oժ'B<sr:p|XDN7YJMQV( d;ٻw](<*C V$B@Ϟ=gTy?/u X7nޢXHosl85hAVl M |Mp'6*_$Ȃyw@(P.]EQBP`㞓 ߞ& wީ |Mp|PLʀM DAg ڵEGT/Eט3NH'Qe\- 0`>`+X~z}$0t;4VfHv٣GeFjaJwz i &Gvj\kuժU#vDjNӑؼt Sa5"VXÇvJ}~Z䣤)&SfLv zubjd~P$@Awe4[ ߌjA#xi9l.a%u~gk(Go_z+t+/:.[X#(D/0/)D_||fXCௗuh ,|~\Wet `nΞ=k: aQ(כL)3"AѢEl: pGl{>;5b&`{8 h8e}@J *4  Wߤ^FOT67 jߞ GmǾd Э[70Ŝ? ᦋ(O8p<}5Sg/ј[ԢVl5l\0^9SL(s2V4dƍ[%Hp1}C:PXKfl6|jjZ+0Q1=Jw r˂7GΜׯi*tPf30 r4Eah&#2Z~} we2S  L )KߘNSV!IP{t;x I*&N Zg{D[o޺M:N4_)dy]+CշuEmȔphӦ D}`Ec)B(^(}?ݶ}3z?Vo?J=+8zIP%))n=eb):@oH8Kza8DhQ 捁Ï,4T*im@J a3a>TS._Wfz ]v4+{@}nzkaeygY%x;L)Ax?9$H[WW4xZMe}yϛflE?XbFzh$P(+i;{N !!@ÄM? B(;/ׯܭca3V_N g0Y 5C1n>*d;^ AQ#$UCJZ-鯎gz2Xqb>[lIp,ꡏL)` `n{O,u]Ha;H=e [#*pΜn^ OpQ >Y[6LJL]4@Cc <$lذ-'?}^0} |\٦z(?h}4mː6h e$`+c$#PBҟgQ@ Cf^nGm{ 62 ۇ$д6 0UD*[,%$$X" 2U3{6}T9Mǿfe. mrd54 `_9ZQyU ({VÌQ={ YfM 0OQyCЋGr՛MxUxyF%J /RK>&5 KQVrf'6 gbo!_U(̇pX bH $#ps`#ݗ ]œu][jE>"?oW,Nq-);v@} WI"ԣ&OQ@V(}! uK#}:8x`uH.YD">OQJ@%nV'8q^j,0Чç3 Y0;C,YRVd"=KHԱV*=L\[!W㦟ʒ0?AI^ΥQ|) @7UP!PuzĂVOJoR@U1| >A: 46iV @& {QK% +IF_9],Y*,qG"RyA7JGNuQjT^k_@OqB߾J dfj+nɡ=+5X0!" c O,4Pr#tE/Q1N>}OYW }F%@xiC) R`pJ[}Sk s㋄UUFu>nZpM\j]ݕhUx/GPT) nZ&:%P=(Q"]t,zgϞń3fΧ[W'hF T@ѢEG`9fcgj`fvsSQ)'<'v;gB!>kY)lc5s2Hg V)_j1-pU կ\Bx9 wJgBs FCN'x#˗;|?o]NM_'Ly>ߥ~gH(F@"++4{GUޢ0c;iU{$bg͚Uh0"|*'_LJͦK'Z{x `eBJQtd3O\_^Y#:=xb.Y6 RX ]>Cj>@EXj,9܎%&L]nn/bx꿄S8+ĄB$y[a -ևN 2᥮ d,)@3A!^l^ia{l)<I8# O2꭯g#DJF^~.^.8B[YG/2g *@LlT(ЙF5Мw Wnfs* VN>h])+S0xDs1ds&k6*B%`'/>Ԁfce?'8 7I $=[W[Z{L裧ZQ,F7` 9* 7n`"hB&4nj¦^h}MZtj } +"?7o^mDZॊs]3| -t-٭:OS-Hz4 @B%0R^^zΦ^xDN@ԯw/j P5Yxsl_ݶCD !3 NGS>p69A$>Kt:uk0wQGxɮ+66)})~׍l9DJ?CL/_V)IZT| R:fJZHWfo!qAoo{8'9ۏewJ[qHI&R0^7-k%7oݦ'Rh[>0h>e*n0BEk:=E%pYpE)rz!ǰ}}i$#:ԁ]hqx{J*%VԔa Vt RK҂y;) .F ;IVJ=܌S.^)Nlu@2FцC"_Zt QJ@( a 9P }H~ 9k3MZMM걎S&s)ST ]E;I:g# fi @3K_Kqǟ>bWJH#wg|X?ZO A^yUW*l8$kQC16bS6R($z!;zk}(2d?~0GAxP:s-`رcMa,ɓmy˩)$4r ^ĉ`[ DQ՗III"wy]| b~+41Kg 1.hϞ=|q4 z)8v?(0FݻwK`<ɓ'kTH P)dJyk|>ا3 K<p*b8r; ;K#{ s?L |, [.ljY{YEl)_1or /XG::t-ܯ 'Pio*-w-FC% 牆jZVm۶YW~Kgr -[My-nXO6_\1CݺUSJc0rҘ1cҗ30e;v cC<B,"Md[]vO`" sB/FivqIT|(S,Θ*SH'iI{+D4Ӧ[E"f&;ƚIi$Leيׯwr @ X2(~G6WF'+Wn7F$NBEs/A'V-ٳu7x Vғfb8p 5_Vjcz5@J%d2f.0a-h5k3 J+i RgƂ6edbB&N0s9foӒ) Q'D'f2 ~ lYe_Oi΋K?}!Р=z'8P8\dFBQ?%~~z I~4lzjEʛϙWGX"H+G% ?#\Ru105d˖[ |h"+3kZ* to] 4.]陀@뀈P$,t>uѲC]T(R 7=ؼR*fsA8dZܹV&Zw>=F9%=]r+3g:e`rV=_n}p"s M 捡gn6Qy LK]qe0 ڎĿq.L,`C{͞M)+o`W`=X 1>߾h@+#(HK8.XV[d ]pLQBqݮўb\ahh'{+Vf-Ǐ6$cнaB{t񣁭HaE XrUd K{8(:H{947TFg0gX|J{RYkPZe%,Ο?_F/ùHH^HŒkEVԏ]݈ɿ~4矉 zV.'Xge(`޼yt`Jy 3Bys-)۔iS]0].oːx^ 0?Sm@<9i]}9s _Ff+3$e8+RBfV?nN^|zgb5+Q̈́bW9A5Cq$F«k.I4f( kKJAahtu[34ߟѝdF%k`; ez?0ߗ%N#G,}3~ƍ믿ZzN>AU-ŧݩVy56oLac)6\Ӌ!.v,[pxSNb) {6eob[hKD]uP߲͛$HSuݠ{ ɱVZ{P@ MMMT9;xP Թiu8NؿbiQ1OV>&rΜo!b҇0{X!u3ҿo:qℙf|ф1HH~?s N41TJ|6nP$=s=jBɩiQ^EU$:Mi_:i^n Q`Ҍ~ٳ?ņm鳛\ ސ"LyD^:)ܓfX@a]Yiҝtz6ЩjGLScf4xiDX|޽'C4TFMY8x`2NBة123;LV:XP` xҞ=Bj{#7|P@^*`ZЋ(ҥ uHG~ -zVvሦ7 [5Gj=|T"r~1c}3o>{BpyVo7ޠ *cmGi (%Ϊ$\f:~Afc߿yIpeUI;Ta3ݻ.HRN(V[7oOtO))5{xUϝؐ]q_8.ӳh a%}8yXi- cp pE8I ^zu#uTqC^ e!ο  Nh԰+ 9D4DUF^jGEnwR`ӦM24|LK#5(2+(V}i(# I-c+<Xr2C o؏~HgϞXZQ 3Ν;G| 7(_UeZj Uʇ,ر6mJ0 ]W|G}DG ;/́4A[*pI0J UCw/e JP2)~_|^Ե00N` Cí+cpX80IM }[~S-Rn]My&ǍA(NE;cl`a~ a\F1\ocbf͚ޏE(0}tY'y}Hsa kQZ@;Ju$w^́ȝwJ= #}O@BTBL#`*BX/!xeg1r7!P}tC-+0 `~(1->Dw֎c[VLWSyPH]"R3FrK`$!bf "'o^c+S~L`0MU) O)Q,c> JAmݶ^zXȗ8>|J^_n]2o0P rleJa\r>'rl'Oj, ΝWQl˦;wz*/FEk$#Bph|5kjժTԅ YUT嘤o*6l!vFUZ]~V)0` -gofeʔ%K2J |%oF8=P< YT/ yP ރҕ\>/^Z;]-2EX?{lȭ}6RFo- "΍F=¶9eBeB0$ٴWH6ωG؁8W{Pq[ϛfGL`{T U| Eb]&rx˾^t/_ e/ $,eR)C[WL!$0;=vmg}F˖-sʖ$$$<)b0X&pC 0.^lŊڑwq(.fl}'|D膕"~okgO< lsm'CtԬY3MW/_>ɢ:۵y`:?K1,$X㝊BinܰaCj޼9URE4ٳ+.ޔNA2gpPu%`ke$W锘'ÔF`ixkڵk>\[}7wwW|$B^,66`9%]xNb]q+H.O:Ů #ba ? @3DpJ9rh>א-pMtqR&߸xxOe肗=z(s,\@w](}HX"BXZ"6>r2|RR5,R/(1v^LZE `a5+/[goH*[՘=Ve~&{J:^g7|,Çӧp'w5M_'tۈbcAut^yWޓhѢzgew= ,U0#a%4rk5LMM%cY|MII鹓B[vs5`ku̅PW8)pE1~-.@0j G-46yOAm3u @ vlgLnݺeI@O Wt[KS P ʇsyw^$۳ĚFWǚ(i+  `׻m)*PD,T_ fsJh4p$*}`oatj(Y1Ļ ^$+BcIUT8Im,:5"Lc$!$6ЯaӁ]xiO (`Xba!(JCeKH?~*T@xː$|gR݄K`1h;ӦKf+K@!ϟ)$ne\dl a·pR@0X '#xٵG(iA.H#h6]uzMXfK1M茏>v$-& aIg"z !L<ChT "!J\cn[Ύڇc4C`% 4A(A%؊ApF'dXO9;3xT5P@%Gxmu [̶bt? J0bi/ lh PWϋk8px;~ I3 cY4ˌS"c`ylpx &G۸?r E{A6bIENDB`ic09PNG  IHDRx AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs  @IDATxTE͙]r9AT@2"pAO 0w;&ԩS333[8PZOwtA~ޅ3͛w?w\SKN> t= Ab-dl~78؉uǬ[g`@>b - 0nܸ8t{g|A?QI//ז/&C `[ I&UC'8h{.CU`n]˜9sQ5a`sf|[p0ިRMb'pO=d//`><>S lяE)a/,77>S_|F_ |A5pv#pa@exu|>FTx|pAiL@lHU,hiFiWҮ@iE#r# _å]3 C/. 8}I/3oqb D94?NoށO7L 8#oRi+!&zcѢEǘ/_[y:uWb'&<%p/XG.{>kɵ|.`n3Xml|3l8&FdddL'jn_'2& ]hlRv/p֍f0 12c_8:)UqaLt;K w o.on I 7Ai`OSN ~6SJ8bL: .E`A8?Ru]-3OnYO.sE {'ږ|w/㫡'L Cv  _t8`~(QqbI-ВǼ/S v*1& ,\{ +% f6LC-nCƍ+[!{* AV2#22YfeQ!!'67]&?+{_eu+aL@g( `N*S_L42 ?6)V 0$ߣp~6s =fL &@>;{Tק7*`@,Ad=ia%_1&`_gP'p86`R{{m߬"Ё*t ~h޼yuvba h|`L6Qԩf̵6R E;{cpի`؅S1NsV"YMj)5fR `@ gcΜ943El(Љp~_qߙ`z@C85gU'c6c[{oUCu+bL 8'p>ҜGe  T?3p$8WL N. ;B S0Ș1c^k` z9--e˖~!6D 7e.xb|s> 0&`4~Ν{ΰFb ^#Wh܋{r ,Wy⊙`&m={{v)mSc|uR> 0&`&EaaarN3{q3}shSƄ#ZCDD}?YKDLQA>geeztk❓*Z; t]JJ {9gddh?'KIjxI ;D5T(WTPUbłQQQ@\ 99!..N{ώիW5cBdX6}> #P7>|T@e˖5j@͚5zcbb'wEM$IHHH(0.\gΜsin qEw " (&zZR TVMсj׮ Ѽ !n%2N:?N>]>& hGmFb@#Z,>bQ O9PjUhР4j֭ {4cp%8y$;v ?/^^RK-X`%pP 5LRc띭[=~Æ WӦM5F9ԝs.)^6ZԆ1 '}&Mzzʧ|b=@4;pA8r/X-%JwF`.O/ %xKm<}Z_gD7o͚5VZl3ynK4@4.BR%BJg|W="")ioӦWSBa ۷(aPgxٱnadɓ~/cK & ::tCƍ :?*D;`ΝZ@#ԓVUC$iPp6L4?䦹| cCǎE308ql߾l٢E94%M{ѯ6N(&-Nc@VH<#| < ~֭{?;T 8|0lڴ vز1.$Ql8?M379 4߹sghٲ%{]fh|nݪˀyp`*v… Wת=[~L}WOS{Qr.L[i۶m?ٳg@<3z~)_.6%yKA^\Ηx@i]O>^}j> N[6o A[> |n|kk% I&UéjkP\-8߫W/w)?֮]*0Q7[y:pUM( oN_~P^=}+ژ(Gի a6#M0ްlX1:w:Jsն.$$D[׿[|!1@Y OgL 8 }ĉv& :r-[,W{ױf K ?ի開.a}OzmY:u;>S S 0@G\,h9 |Z*cYDk5wlD^ad@'pzyvmZ*(Cʕ+СCʡXI{ ب^)?>V×#Jsww;_|qPIW*MKlx .x.%OCjz L@I4#|rUR?倻q9+U9628/}һ*"Pzu۵l|0[ C>Xz6~A#v_qz+2?=|=ߵkW ;ػw/|jطBOF#`oj6<#Gc*Fe|j>h1b~-( 4C 99/'~ߕGxۓ|.r)&Y7ؽn^§-|S?L21=tMZ'N\<"@۴igϞm]iӓpԩS?_O/*aRJ޻ Q0&3J2dظq#Q-K1죏>έmv.:|p!+V$c#bQ98 .Q^.nHGi ^ƹ: 'ݍ<<_ !⧩~8L L ;;;oq)@27PW}5;FYyD6L}&MQ0fLDgϞéSLlUΦw=ZzOA95_j6 1i8O$yZ!.?A`)z L/য়~҂eeeY#<^@#'M'8Ig8?y.ָqcRJ.L XE0o<\J$F@4ld OpڿNQ^o~IkoȐ!Ϧ-5khIrss)aowhhhٳgZм0M~`o/zRyS$nݺq"lO& شp@[8%P5''Ͱa>[vmc.>MC[8El~{S6X}hq9'I^vс 1r }222bW=TLƍOkɟǴ.Hddzw6kK^ @/x8}lEEԹ-n\b큶0NS;+q~xwg= UfD~ڶm p hN?yHO0p+l;BϴO>$t l&,YI5SJK þKj>6lhҤ5x޽ˁ.M0\p: J% tjy{ٶ#<;w-V<\Ivú‡מ4;4 u1dZji!w vO-FMhlA#i`BC=bȍ2)iܸsO?\b/%As4ojY"|оQeo.k &@9fΜ W^5%᫿[ϝ;)R$FUw3M;2=SPJtc$0۽ziYгe ^1FEEANȑ#iDZѩ{Ϟ=m۶-OKRij|+Yeum֬ɏbsaf{x1}AUzصkW8~8߀<51<"T6ZU@PȩoM& @zPEҥ ;w.]CV ?@^6VΧ|A,>GWKo6E;_l8 X"е|vo.^.\&/E~a :HJ@zӟ-3Exܿ'Jea)c24߀ WSᦶ (@+idP(q(xm2F@C߬}Æ ?:x$7BnE{0ڡbᏼf8˺e+Aʶ@k+ 9pmukbeR8? Y=c"/a8p 쇶ZX[8t*t):_KɄ? WӠo(O)̌n-Z@VVht{"֏=1rOhQd>C+PAy 4.K{C |k_o>nhE+ـeBv娢_&p$ 8nr7703 40oGU=l0>|;  x6@5y(V K >Y.~ RR#GZ _t![\|.uyS |ahQԫM|(@NN] w;pk1^tNw@kkNv_zOرcy|n( 9uӛS54߀>kB0xQk(Yrr2:uJ:eb<hd"3_HSp{b\J#vMKKm\N^Ltlh7œUA>Đħn+D~AW^cU"LJe \[S qO2]Nӗᖧ]/} 'd?^`z7N&"4]krfڴid\_H,$Xk, e.nG)E ccyx'I!w|ӦMGͅ Fyf 3:9 =g C13fk!f*otb@Em"=Me$ B߀U[O r2R|z]r ҽs,ZxR:2I? g3 87_zը\ ,ڊw~ǡ| gD~8n۶k6(AD5m/44x # EL#[ـ?/7HU[6N华~œ~O+Nѓx%TY apt%-3GK,tU)P Blz֭[s[*wkٲ{5&e;0qĚt{7o.,} \IL^ j $F;tKJȁ=U .if!N}oT_"pJ23JcC laVLyyypQ+x6g1W.tr-ājhs@& ,YDuK?|f@r'QB3pj_תpzMbwEe<7)Sp?#rHN6n*e;_J)nigw,6P,kMO |I7+Wz!#TIVfZN>̟ @A~a;z}z*26\H7> Hb2? 8v40ůy60>2w?M qBo&j]@ٲh; & #|\40O4>;-=zIP.}8}}*O7!1Jݻ5UQH^ >P5*xas_ E}={,n1QR*"! tG(o큕_XH$ Wn9-|ȳ<(6(岳@DDttn)9& 34?o S@; 00P@ūq& |̳x#yG?(7iӠbŊU51dYԲR>/6p{=@rGUad#F-ZL#kZ[6D3#7 GYf0lqw(ܬ9k4iR5IŖ 3p 6 e½"H>\#0x`/n laX")>b~)! m@jzQo֜(R vogƎJG |oʏJq3%ZT"@ޤsV[-~@LL L0dN8K-3eT\Hz4zN:]-,'.}D Hϲn tP_ݰC+߱J`ZKI(Arq-oǝ2b?]<Ҿ}{h޼8$Ld4::hLNmڴT:2*0!@hh(mb]O.ai, *0wcU JjF6*x5Fmj(t^8ނZ]߭  3vkX~}c4\z7zS8s%Y9ݼUd`t-WnL<6U2nMFYf&+)gteeÃ2ZW/[ݴFoʖ-+ށt7߰}T1Y ".1ͪɊޥW3`3:'UV@wBW ?Rh۟_8't38JLd~.׮24X3^*ҳgOVM=cy-gjXlDÆ S6ִ1WL6'? +<$~i8 \ϕRy*$ OMkmEk s=\8~!Q: eË" /_C@rr2|x*N:u5k},'9f`f,ݑ;w@9 pltzFZ(,|P?|p+,W(mIsS prTL:u.u3pE׵ ;N:ЪU+0*, R5~e`_# V9K}Yp)\fIV7ڮ];[b~tVV.΀hОFOVv-g$ &>$)t=eR#Y)p ظGשpr֭A*R6=pםN,(h. J˅ 0 %ɵ̙vA<̖ATL1w@FWADJ4iDqX& 2 +mP:%,_%z>?0f%E( (okrc W;J 2^i~OFVl<&o;5"##zqOP!XN{F^E$@fe2 3tUN/}n=YpLUV-e}2bccP& A_^&Oz;xI=/~ tr-\>(霒GTL@ǎn/XV'e'T)2i7C8T|}!duۓOE3s6x3Au|FkwZeC8zt @ U cdž#<%˩8%ЍͪB@F'@= B˟ԯTR^ >R:9Sf-LҎyeOFRF/L]2: s:U(?= rA$P}&LPtWNiC߯_?QEc0R@NV>E#\dHѯ;5#(Vʥ*GcOxGrPzI+& K7U~i8>m/mWFgԯ_h P3d6E=UE lܽEuZe k9}Fدx aqfѣc ai=6T  0 P(`ي\>sxbRkҖ^@?th,Pb.<^gD ΥJ1۱)c",4 >0S]o/-.G?ZF_ vRPz (EVE4R_f;/S 1NРGGO]IiVl,l6jUB cbHH0Px0&2_ X050O3[lԶ(, 𔔔^JJe1[K/1J שV+μCY ||O{衒Nnnk)71]ʮL# [K?0%"wٹҭRнz)nm#22R~f5x gg?_H ťxC1iLz;62iتsxU]Y H!{*4ӷA'?VI~-p;ܿ@3;w,%xUا$@hBIYD& /(Hгcԋ32۽9 hݺJYa- ))VN"C(AdY6& xS1]HjUVV6zӝn2W VDž 0h.Ti G FWV:Zp/[. ͚5@R EZ xkK+Cg% RlRewK5Л0;Z`۷WiX'A 'mκթBPg_I{lO$i+*yp/~z__4zed #Z h ',zX?0^ZWSEJɓ'+IR FSR2|G{<6U/8 YF PT  A]ʅI9g;+b$+1,pN#X+"С` #DbY韲VDZ v~z Jv[%/>* !KJNZIB\𜀌;HK gQ֓JL U |/qD ;;J5r| 0Rj ڧQ'LMђ14M4e TDA8t YF%eH3rPz@YU)SEP(ÑK4@Ʌ 0$H4 鍲?>\%/xwcIp*cGI. 0ߩt_XD 8A\tmSUP%,0.])))mư>wg*URJ>2{1;)<$b 3;Wj\ qȑN-bZ W6g1 FXr"zª)Y챑aaaMuK7Tqq| ACWʄ>r6HԪSG@PŋS, ȸ `߯ X!i?VhҤ*9dN @ #Qp8q,x>߾qX&fnSJđq5@Ĕ 7[8}ǒCbVDwt֬G<޼ysON\LteH yP#PD! N$ij1Plt| |ZZCJݰaC@ł2Zˬ1MתUKfXv&`9,ͻn jV wkjԭ[@ظhi"O@0&-Qa/iP=:U8XRvmǟ2n`@zΣAeXv$ș@]ۤ^U <:ʾ S fl':$K*Yf9%( 626ϑpȕ.+2>} c  i :;H},@h @i  [$/gϞWXb~YSZ*.&' c ZVL:<(PVM`"""rκPc= rJQa)'  `zfsaEgoߋؑ7 [ǪI1gy$p5YNpy 2a0}3fLYU3ȅ 0 Kjȴ~jeq Ucɵ^zg0=K@5)3&|' :jE3%B*"}$d/o7$DQ`L xG@L4 KQ(! n䤱 .DL8Cec2 0}8 :=OgYXKʴrlRfM&%_BȝeV P&`Ӂ+_w{RoE2 y E:G<<L$5NsC i:KVyȬ蕢@dzg@bYE&$g7Y g뎈| %[͊!϶K*$EƘqG&cw7YV0| }+V#G#Pi0%c m3)gJz[kUTJ6^-i92 "&`,?G}> PKgKi'u!(mstbQ% tEY&P Z l6,z(!x|`ۄ+eHȆ<GEr\~xaF)9:&2׶9Cgx6,+#(g"aӖr{CCJlh eΝ;Wޱ/u:gnu_ @|g]Wk Gs>RDƜJ+PB9 =BJ@D@Qo΅/ |K{Z=PlXI滲eJ+aBCCeraL@<9G.[ _b O,戈~srf̧@38lB ^V>x@U-/bY9=2U}m _ LF<p#d+Ly{+/v>}($dI06",g@땾3ܿ ڳҺzcO3@ d .A+}ZL{G}+'I%P=J,`cC;+gv#>%x*Jʤum1@k\Pfw0ev$ `wB$o@X\` Փ1Zieމ&@IDATv /\:Ǥ_5lRkr6#™Oy*0 GZ{KuGn ޶~cO_JJJƑd?],)ni6XX4mTsjVBO!2wEd>d}'}@ XK ͿG@D(q{`s]Td%o@@F^WYLd@c.L%Pe݄|1Г3Fp0K}G)QнEu)Ih>P c 1:`B\=p3Ʈ;A JRgnͫE u?|6@\5| @unA K@8ʛ=)Z ݄Hw7T-'m(yքcRO{]|6`u`ݪ9==1J'w wJ-tY` j3X6 37Mo= i#2 ؓCqAщQ{efHJw+c[%Tvk`j6F Ny]jEb]CKRk@'uK4% ?uK ?}[ԫ ߿2f=|3?(㇞R tqw(< kT@S}N%կfSP ͝tCM|x̡8r1C jT-anpЧuMCʻ6M:7P?.P@:[au -"pąea<769ԯT+lƞ42M`eX talb ]bZ:FgfO@ӶA֯j 1HuorO}'zb33:"o5)0d]@'DEqERBƥ|A3!9aL#* zը Ekc "k ztjR6u/rf, E4sѢL~b-di TjC$8x*~L9#KjF6Dnњy^mT-(aEdf4XZ_{>ﶟǮc2)|Nua0 z]ri˖-˛0aBv9u;m/Vz#!9=[X鸬dZAMNlThh7xD(ʽIr+15 NTVpQ~JhTb$4ٞum".%f2@Z+ Jvs~u=Mˮ&=P;M{}!Mڸ% ւ[;ՖS|Kk[G@'yDh9Tbi!@O?8wv#8kB ߧ08u=i> +2| ' ؃ɀ qDNM30#`R %t mU 5|;G/d-H 0cϟ3fӫ=:A/C{}b/lXEU& S0HIURpO쁷WDž 8#KΨ1 > (iGn}X?9..-:`'@XsKP`Ip* ^V h) WSե`{ +'!`^pHƜ, +o16jo\*TLLL -qFc˟ls}'{%֐:;/Y+odzBK{ ~pEODnT~~na}51Gr)c4lP+6'N]ޒW ;$ CA[` 0mYe X?9  l1;̺ZL@1G33"ԡ٭1x{#oNunxi͟ Ћ/Eqco4pJ":qH"4B|+\Fuڛ?/0_{Ɯ  ƮE:w@r'<:\x\gId_I%8-k,WNi*URw&q6?gbܔݺr} 1-=Cv@FhvMh2"/Qfx/6ux@%8 @teB d+ y ?ssf|oTXr,Q(0)Z|CclXǞZN~çDž A͠m&XsuZv\?z… ?Hd28|VڜR&$3z!pywN³3 +u)=hr7Ox,pt?Q`8B3g &B>sql:r%OFϝ~gS8@emڴR)1Bm( hYjj -ڄz}j.ױw/>:2媼#>q㫼#Klx|} Y XN ˻V(0OAxTTsDemڟKVkhѢ+7 &n:/½_JHaO0Q (=a9c ٳgwh&/\p˵LC+pxg {’` p Osnn.\e9]T{xr-Ii66SۤWc$/ _@?N(| O<)8go0}˂ڋثĉV6KE؊cs%'.$_[kg .4'%x =~ Qe](y)Ϥ}/T0E92c(`\$@730)!&v4dA.JT D(6,S\)='{/f`RL8Tpdxǧ¿?[%|50@T&`}G!y9,@1 b'PKeƙ;xJ|TxDfwQ ]EOo6='Rz<_)HdX1% ظ%gbccUدi>Xȅ @ "e'eɁӧ@^fÎ?N =xB$)XéSdry;M f'zGb>}z1~9sU1k;|\šb':*'|%p cRݩdl=&` )On)d"Lh3Eƒ-ХSR;|0dgs:gCz{;c%P9&L\X2 deeѣ$W3i@N-gx7/7peHY"ZgDdq_p$9. 9D;tbdV0ٯ&g7D4- E * ի: #oazw4c:^ݻW"i' 7 M)#,tTx. )._BfӬ%Wq.P,2(L\\4hAvU1(& dW#/ ڈ&+=/_.#+M($ HabT!ر%?n)ȿХFv;Ɍ[1 4U^LX* ;b]IQ@ڒ*;@8'p?;'GE'=$|H[e/PZ2ڹSF"NL2~ E r;vPBvpp`3*Pg rFX6c uX{n IR q{gwtR8&Ь6CJG{i̐OP@%cimDreDn xBIr2 2V\~Ҕv@KBz`׮] n((bbhStR ǿ={H){իW> :u]\3==8 fbF3Е@u+cDgddHg`8Rw2 :[.E ̹AƐvN@wg2:*Q줸\$P1I?@sӪ@0= oYeuqqX)233ANt紬[S<& mj ! Z -uG ?"V(}t^]9S߈@vuœ*wmP v `y %%fnmB1`иO~gHNNVߣqvEyڴi.7*m[TKO~eTW3 MnՐRSSv?>yH2Dl+]v-72Ej^tL Еׯ׵N +[l2- R aP9]t?~\TTЋ:S_QT˦i`c-{l+ ޴׭+1KireU+ó"tP@TXP gWLs aaaEzk1uەHU RrY=:aF԰b{r pEwgND LN 9eC*O{+nݺ?ic98yhbY"Ov9%QwLҝ&6r? ~/NTF 2 _{ژ^ZDLvmVvA&P K{J@~oW]3K]B^*?Õ0= edG=㺘Î;T!?^ @ن?~:YV$3eh& mQ)t2r@̙.^ @yӨhא3 noM,!' ,AӅcT_xC --M`7 b??V`#4  C۰3ObPQ_/\O>zS$/ivOE6.uB^V] /M~RPD Wiɗ|06O'6.u׮]AvS|1@ahT2b([bbbi هDwq@j#E*MaGڿ}ljP9}g?<"(R`"r|37UxftwcjKsJd/*m?U/u50?0/2 s-P(e\imca4g%W0G#xqBF !uV$`, 1fIP(m$SOm>aŽ[Մϰ_ooO'իugݻڴi hK=\ ˗ڵk"rp{pl<}ryX_ ~1 0ewݰaU[Y+oO 5omH?sȐYe % Gh{\hG eÃU5@zz6sk7tj 鍳'qXEHiJy7.p[ALDoA֓EӇ@ U˅S!X|r\=u}:d @Do$`^.]$Tb;C8="W_̓x]D/^~I)P|nxjWfͅ>LK7}}/4YN:"5ԶP.2D X a ,YA8W .pP_ 3iN$0Zbg1(᭨O?m%LnK o?‚yITJE;E(?E 0gFi{G~,0ϯFeCXZn]0h[%%%0!~L%1B NM]}cl\pO TBK5䣵i&"}ƐRpgRr4+͆c`Q@)C@FU``XUW1 CA:u|!N7t$A%m۶kLlOͫèMąD8v1B穷eˆB_VR  'OC曆f3|x"e1RP)r:բa#Uʺw%*'n@ß)`BC`߾}yfs3hMg10=.71Z!3?z(xz]MqI`K֖w9rޘi]TQbh\ ~'[69Z:΀S#Qƣ=!г$pJC s gW ǤP1]!4 ` Pz). (X.لiovIYZ{mt_)W?m׮6L%ewwTI!:#,h۠J.GIFD  p?^zQ$R0v.qFޓqm롔>9O+^՘wuvT{[oAbbrԸiO$,@lW)_d FȀl}]zOZ0mHj8%CC 5 hLą XI`ʕe+E0mӞI S j,E;v́@r ޢ:<>оtC|6!5.LjW5Oԗ8 p[W3o&=ۧi3g@56=k]4Ѯqyap5Hʱ+  6P|mrLxwN/ tG7|{-7.̘?@\T(o_ħў-kh5Ҭ@6o#ԁ.@'1hS˕0|W'D? =Mm3ބ usR9>3P~}QDRV z X!q)bAO:,F jm>~O _~YT0<?Y&MS:PQVX^|E a)nd78l>_n8 \/>DO[:}.8aqdee?/_G(}%[NӧO7=e8q Fw}YQ[=`bc3)hksaj 0)-|2gC-SR+o69sT'A}wV0qT< nsڴiйsgK!@Qk;;_\%涵ᦶڕU%5;v;C2uШU Zj $f# Ӗʗ/od3\N^LԌ`;{cZJ5Y{ ESlװ2th\:6mlTCI{Zܺȩ_ʩׯX0n~#G ߏP` zꩧ8J={)!  &SpO]NW-OZDA׌F5AZMk%XDPW_}ʖYn8 0>m> bd\ȧbB*Fc S%ZFO3 _\rs]Pܨ`01IfHkTWUqZ  +VmS$ndB5v0j2=Ѩ&^A P#2;_a#pxUo8]w`Bpd!ڥ,dӡ6B&Y"raLtSzC;FFK": :^c999z XY~&!@͛7& Y pU0FpYi#:`RpiupCop ލL9EGQ<i8E2-hܹsPlY[sL 0YVZ%`Jw8'CP3:===|ۣ/wj[x1;FQ0&MtR;@BL;` g,[,gBqjѭTbL A 99fϞ '򺌎SZphgϞmڴ+HzR'OB׮]9H 0 3g… j@ǿ)* ZK"oi>&ȅ 0&`oU/8~,B;wfCiwZf͚U%_~ZFK%{rWXࣷ!\ yMu !9z( gX(&&~z@_/>|… R8J%B!ZH;隆H\`jطorŵڒnz0C`*.ģ%5TlKBkbwU п 0&`Zgcm`wEd]7.n0} :uP_`B-3f̀!1Z_@?3ʰ@}EnS)9莳k׮o V3&|&='$$\$éWeUOƉ8 -2EƤ$ @ Lrk@vv6Z0e{}~*ERYf3ljwZ5k"dYDeLH;#GlFq|pҌQP/]VZmGȓOg:vP7: pE]n/T0x=3p@T~)x]vl.L Mv4͝;n*z :kENYuIg4?@Gh@"O)Ɂuf#@apJ 68^Ճ@OolIP82\W@udEåZ\_fx~WH9@p% 59eҤIaL B/UV"Yr$KVZzx%H(QK7on }YI&'6,1FY ho&2mB[1lْ}#/]kA%4N,}.b\ ,rqW@>#XA;uC( 0^6:;Eݏpߒ8&Aǥ8 0doe>u\t x:&!@ϟ֭rٯSRms] Ùm49STc(̙3о}{@TU5Y/&!@}][/ ,1)u3ؿN/.:RcAͅ 0&`de;w4 ]]ND]zdܵkm ܄cR֡/_ǏC8Pi{&<&@Y(ϡC8p됒s]W:,.EWȏj>0s%h3QQQPn]Ed0뵔vv.Ʊc3u<8[YA$ ;q Ҏ A>ТE 5> ؛nPd%K}qa6)S(mPFd%xȁcOav%@{Ν k֬+9h T|WN倍?6΅  Q`e։ 0Ph3gRUjQR8Ƶ0Hnݺ}YnǏ=Ж%!!AU~}(W-L '@ =(p8lcl߾=We7ʎNvlܸ0j"4lqߙ)k{ァ ANw_nA?>ĥ_h́E_r\E L@}8# 9Qr.pwOG$@N(~_ΣK>hv.L ؀իW~_W1h)8b3Oq& q*ϦMA*=DF%P 7..N%|%gEaݾT"㵶4؄>cg;NOi*p֭04jԈ biJsp"݁c_0Etm Np?MұA ,_𒀍:UUMϚ5KsV^YD\ԩ^9rd(v?.JP&cB׮]}/eL@J{ǐ"# ?U$s:@("oHxT**RHSg[l/"2$PlXax4@(B !$\L wwg޽g{Ϸߞe e|'q99L$XAA1'O>ƔAoLG$J|r5Z1R{|x{c:ܿJiF2fop ;S}]$]$-Z® Z -3s!ũ5Nk(|A5~x%@N& ֮]VZdEPn [pFO4>:>DjR+?Jծ];Y 8@iiJKKSEEEN) C8Epˡ 遇0&@VD>5ZFL o-Z0 <dlzdKB롴yZCכW% ,(SFǸ85zh5dpcVi|~K(_n8i4'+y^C:`2D ,ݮq?߿BH@ڵe ē(<u5/`=cQM(O2$C=DC@T_ٟ$ H<^H{$%ƚUnȺbpn-0Fz(lx'CUG3f̸7}ծ(&8vXէOЮwޭV^jW{?jY#M00n<|kV>|8p ͚VWWm۶< >5e>cʴ5/ n䄈\X-(^"e1bj֬vSaT[lQk֬QOvUC%K?L"@$u:uj0~X;~xSGZn\!, PVV6nܨ6mڤ$7_صkgϟujDd5 q`<`{ȑ#UC+W@덐̇.ikh\Caf< ɘ۷o dx7onr,' O~Æ ѣg 㯀?%##cuȅz C_%`c( c.lVIIIwޜ=`c݋y,dn8o|{ o z,VgcUնm[# @ȍ%pYot,ŀۅ,:V\fq *L<t!/B Pwy:fқ<Ν;˲<7|tҥGPa RSSOf구R:Dc`~(F>2oϞ=`uW@@FWTT<ɴl{!w/y6W}ub g@ IApӏ߷oNJsXZO,[,Y!=&r0MC s=BDzk0U{޽;vPΝzUC!M@d]5NF`w+p={4 >>JYN:e彾:YM K^|9%uhX6SRRb1(&t!3[F ..1pMZV 6n#צaGLSh8/`(!Z[G@@w0 dce]ַ|UlBVVVqR 0Y͞=~09(oYek HXbY@< ]v[nR.sKKKաC'{qgn"7s%? 5Ν]^^{xmu;#Ccǎk9ܮ' #?$%bQYx2b1 ́ة8QꊄB{;G@-ܹ|A6m%1w_settǎ3A 5&%@>iiiEe6K l kz )~]L# Zj$DzӃI/sNO>lnMT5ps“@x]+3p[q۠)B a, %'$QsaLLV臲TF;! |E%7EÓtulWR^ww^-[%2iD\x{i 7 7,tO/ 4g娜!k <~(( .s. {IO.oM T߃[U$@"cRf.]Ħ>}ܻjm?HIlr# + )DJ֗MmrQ!ۨ"Њ@ZiQeixaSSS#19Rԣ:$@!pD~>oU4"r1S'Do@REa$@&is0jPh%ύZ'x-*j-ը ajO1o0J%aoWX]Q'K H4?_&Kՠ~H^o#}{E'A$`*mS_hj,̑pdX#{KnYf8šX* .I 2+c:7('T+3^D6"x[ ߭-g0wT$!pWcNpr4,"@xdo6)/ ;߁`?9H[5H|R e$@$'2t/c)7srr.;PDd36wCu-_87KI$p߾+\AN}I@&_aD2PpW1' CrDKwH8L k'2AAa'GHjJ.o233wH5`A0c`r,/IE ^@ǿ#\^r2cd /BQМ~!###_sT?4Ŭ483Z -GW6[*d%"@Sed " >o~ 9_d*JEl\T!OTHJP .^IDAT7򠬈%O=| T2o17J Lv`eq2,.~]L$Äe=bSRՖu^2Vȑ#0pԘPt ߐ"QP{<̘15??c`*DZ.jܯ}7.:PFD3=gT]]-+NBx4^[g]}V iX8^x}AF߄{p%fBx/& +& /q D?FH%.:̷,aBm"?J@{\}M $ CB{{y_{ҥrS [+m'M4&&fĩZA+{N\]~)/7jN*I jG}DjP~~y (1ׂx0/^}O-wkV|L~ K]lIKHh8(4JNNh"D7 t\?qSV%tL{ߋzt)[%ae$q4s# x<b+ 0 [*HH0 ?# ^yJ?s޽tǗO)c*Hh8( K LVd h}$L_D' 3{5`q\;UFΣU.X>:h"ÍHHHHH RDIENDB`ic12[PNG  IHDR@@iq AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs%%IR$ IDATx[ TV #Dpcܗ$=du۷om:+`QxfBڌ:dT"+vyC`Wk,]-''(C<h2_9³211NC0cƌ𚚚d'U؜L4g;SNAaDA>F kժuܙ-Z RDӣGrss֭[)Qz- |gޮ];4hۗ5kf$N>M'N۷<(ϾS6nX(@RȰP8у"##SNWҁ(##ێDz+%%C̙3dzd?' C cǎzlu]~`͛dm۶=F]`.8wwwÇ C޽{Z*# LMWin:t4~ߟ(""xW d8eSϞ=)++K1;TVVv<:I},#C .$VOiYIܮnjHdӦMi CQ.\8# W -2ٳ ksi~ӷ6Pu3=,Լ37H`EEaSffu— IJI#l<93ze v:syO؍8w{a`O37jI^#zWhܲϨwAͣ-[ Ѧp؃9D@C뼶ր++8 G5 qsԑ*'Oٞ(si62 ڸq}uCW]K_J#~}|*kt رce9"l]G%Q,Qu4i o( "߼HX+|\_(JYyl?bw-Xۑ>D:"o%QB+*&$v)!QQQ2Z{x;) Yg6m|(Kpe|=,8V;0Y KNPfNLhh(mVHr<`q 2D6WP=j6'3 d$.0 }D\|2@8_(E s0+~ :eʔ6.eee"j{#+QgVXM|^L5߄Tiq%F|o P]"t& WU#<)kҐ<ī(A] Hte2ˀ"r CPgo B !l_T<^ k?x,q\X^Lg`ȀBY-,lWebJRӳvE[lE. 6*-0YZdÆ ΗluH6$MXлCÄ4_:Q+!{FىiHe(xF(k=k蕰6=W)uU+mA1t4yʜ={%Wپ+{yOwW oL};~N>E$SM& #(I՛ܰu,4t,>a1)hŮStVF&#*j3@>d^x{{^Zn<1??_z&TDtgq)fE<hᲈȷFȅ4p.庹}6ݻ'`6-1ާM#FySͲ Q~,ǡ #y&71v)bDKl0]%SF:MaIhětq}M0q~7|_ Wi;/G|O.-MDeA"ǎmp]|1O3(2N<)O& i͆ݻͫL#둝y{ԑݜ'ps玈ı g @8j?DE. ^6)ma[[iFa:["nxW6 ;=(֨Z9fŋ[UE;?dJP߰JL U)W _96%+)<599Y6gQQ/x\6jA9wE4fdN/I~k6+fAQ=斈w/ 5SeӓUrq.AQ-p Zb0x l]wZpEC5d3:K,!.hٲe'q|adRROu hyܹT \q8+/_K&&A֭=4/hr"-FК>ub&ຬ_]&G|?5#ӫ0 bS3IyqDCxT]._Vܺ PԖ-[;X xx!={ Ѹf:UV?8",3(EZDDlϳTOtP_ trϗ ۰al؈/С+$]x؈Gh8Ղ8,#388L uŭBZ#di `W"ÅG &xgj5ţ *,N)s [=HR$_S֪a7sY r1ߐ `:x.f. uө !؊NIpQejUaGPaq!=qT:)@E FE>S|&1~7L֢MQZ:BPb31b(|doKE}AAl*@KlDe5<*=_Xl§EB!`àP[=G{Rc:>s,&3{7 _IENDB`ic07%PNG  IHDR>a AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs  DATx]xUŶ^ 0PP*E)O_AlxOgCE (]PPP@@E" HT$ 瞜:>';}̬Y3f͚5kfGI^zNwڵ(^\=q;^w#rիW9uɻ~-_Tn"֓>}B4EĽo+3go[Yqe\5I0`@s Oqs+QFN4TSQᇁJN@)&.2BfE2@JJJ}(w:*v#DL2eaN>GgϞE7~WLN)BaOi8x |W^筶Y(C5kV見dY^?Ku߰#$Æp k03qElZWxq)W)SFlٲRH)T,XP04/JFFqB9t>|X'aΜ9xb;#(cK,&JYCb4~o %K\jԨ \cɓ!rmӦM;hb:-!;Z͜8߿rw= KǏ7f !omտ 1ڵRhg G؂1R`0rc,͚5ٶm[(SxPC%Kh;-1l!S:4XL'6mzNҥ2?_Z a!e簗 ׯx3Q6lXK64Xr"̠A<'%uQU!3|6iP-^7Dqu 疪C 4:~{ +u21r7x`R; wF~-.]qulLf8~N6$kU O*h@Cp Swp6LpA5l2WE|bEK*lhpO FI7Iu0OT߹1daG` Э&#Θ򵄘,D…5y'Ò {eR2e귛LiTAb 賐(V,Y 6}`Jb% @_}J3;p@G|޴çGwJw>_+(?l7^(/_ި3:Xtױ"[J%R+:*pOJ݃|yev:-ϔgNgZVaaZ!=8\C͸^m !On 5U:pf1M{/2=ߺ4~r6`kBw﹵Kc:p:~AzlZx: CYvb/#A Ԑ!>-+|zfo^uC'-*g{ @q@ ȯA9WG|j^B2gxg)T߽,Mh{xjI? A$+`-܀W嵄 cjS+R?IҸ .Mk,t5@Hu"eb %+j<.wp(Cֆ5~`)/_g!(+v1 R%T/_tC߿@30JuDw#i19x;m&en,bXتcmQR71ӱ1ҲqLL׀6n^ r- a;gy_ H?^~uLr-XK,Mޭi=k\+6l2\lWG;ק )kd\fX$mvϠ!H\'9d_χxK~ƥ࡝A tY%HKEps7~%Ox9n9ы娆vi"pllM0$D2AoGF,*QN'K mF%pt;cUE|8ͩ6h8bN,p3,H^("񛹺F0ܜw17w8{ CFꕝ0~Fvm(ÆXaቘ}ƍ;f0^>[}" s`I?f0gn#8`L[ܒz=YϯtxEt@W WJ 2xV^I]bh"d?_C,c9 X_U !C6mæ_8\9@ 8n]^ mq_]E: ";V4$Nquh|"P WD&Y6}`&P ?n÷oCCq[;Ɏ<`.ܞ@yAÀRCŪgs迳QgpXBK`֬YlotJ|r>6eO l lY#k„ .e`=aT/;vѠ#5kzu׎'>;7ah'ecße!5 >S@Ka矵֔|Vàn凷KB2|"Ln[[M0( !EbEN-[\'IciF jty1)R1U6%>蕉:;Eua Kѣ9ОM7d٤IT|cwNp~֤ɸƗ^wՃV>ٶln~SC DS:A%2?ek^U{nRY8h΍vBHçU8agK5w3\aO6?0]H_J YÇy"Z,V/~};>Ϻ;}._#FF6,I(u 1ӯ2tPế&dBn2ի fC/<11Q(s,bCfFCcZƢfP[32j#Gb@dDmkР-xi܏9b(5jDe4MؼmUp&wE.B|R5Zqh]tVF,dž *N>:4´7Ξ=ۊ3z|嗖ʏIn*;i(V/hå 5@1cp$>EYI0qFFm_KwJ/ʟ5%^6>vmyz :ߔb"2`z =;h-0GXGoX܆աG04b(8 ;aw^zjSNئ85{#L"|Qk1B\&*!p_S'p|[Րɰj) @0Lg<%4)^QdVI(w͘1z6lUH533)lԆ4뺺sD:xk( ;<bCy|n 2_µ,,?vԿN/V/bV;[I4$t`n3 3=ǎ'$$H#;\9oahQ<$``_1:%ed5՟t88.Wl8n=4P 20A*$wZ%RЄ٤xRB-$YY'~m"|RrK, xCPxi/HNN坷_}Ӂ>|ܸ|#L.)d&DrCy"ݻw/"<40 gRjUϬw^믅[8P+ >Alg8>h|Cƴ|AH.ᆨ{Gx{XD 8+\e#+J-3kmXlgRz _~6`^`Rh>px y*?I{O栈o2zp 'ΰ230`@A=0y~5  F 7+ {&O0>y =6;g~fX8d2?{||k~WWll?6.VAl`X0Z-:H1{Ne=cB5n#:OG$Í{E*qv{ʕ|sAhЗ#JW rwry itmw;;10ӧO+< Wv9e R|-G &vĵW<ơo8}M4qJzIIIi`ÕEZ0L-gгZ?yxS8bٳ e **}?z'xp(DX\ n#:=Y04-k6M . jcɳ Wd  =EX^ ]J%2{QXIBB$A^7 H z*Xu*w`(ƪ .PCcw34Sx>4kLa-Ta]IENDB`il32=c``_`_`fc`abf`[^` xժx`ca`ևՀ`^^`cb`_`kօj`b_``cֆ/΁d``a`̀``_]`H``a]_``ֈ yՁ`_``yMLx``__````_`Չ sԃ`_r8`LҀ` Қ6l`3`_``Ձ``_`*``_a``v6w``ՀrӀ`U`}d|``ab`}I3Ctӆ`^_``bՏb``jՍj`j`bb`Za`{Ї`bj` wӨv`a`aU]_`bU_``_`a`^Uc``_`_`fc`abf`[^` xժx`ca`ևՀ`^^`cb`_`kօZj`b_``cֆCCfہd``a`ڀCH``_]`CCUCCx``a]_``ֈ CCCCYՁ`_``yCC|CC{x``__``CCCCE``_`Չ CCDCCԃ`CCCCl`{CCCCPހ` ޵kCC`iC`_``ՁGC``_`bCH``_a``vYCkw``ՀCӀ`U`}DCD``ab`yit߆`^_``bՏb``jՍj`j`bb`Za`{Ї`bj` wӨv`a`aU]_`bU_``_`a`^Uc``_`_`fc`abf`[^` xժx`ca`ևՀ`^^`cb`_`kօj`b_``cֆ񊊠d``a`耊ˁ``_]`ኊ``a]_``ֈ يŠՁ`_``yъx``__``NJ``_`Չ ԃ``芊` 棊ļ`㡅`_``Ձ܌``_```_a``vw``ՀՅӀ`U`}Å``ab`ë`^_``bՏb``jՍj`j`bb`Za`{Ї`bj` wӨv`a`aU]_`bU_``_`a`^Ul8mkbզe,/ ,29&+ ) S[ÊGP kz " gl { >ԱAic11‰PNG  IHDR szz AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs%%IR$IDATX  LSWʷ.Df쓑mFA#!O64s6,aɖ:MaC$_i Rvm㵔Rv9{Ͻ377ID AQ5M 1m(jb5W%4ÀGOW~]Bdطor^"8(̬$(..f3Jƻ̠l?i}cAAA\aSSSnܸ1+p % pњ{ cVAvS8mmJCnn.M4N KfTFvZ?o4PTռmb_Akh?3??_ŘX‹(T$::ګu 2bO X^J`*\w:%$h 9raxlR4yBL]}]>d'E">s(傴9c`((6U@zu{v@@!tE#X!!!z'@eO:=|!`OG'޿px`sJ { 62>6ۯeš/I[&z0]kfYKI_r;(؝~frbR ]Πk~հdpS)=22"6.ΡfXd@%F7%ʍ::웇4NL͟a)W're[FDŽv?^J.c&,VYvU[ZZZ0zJ!;;[Q+^Sog]N7'"iZc2)|'^b-QX,T*c4ed <8~@=ym:nzkiiv2DZg/]VV СCZ3 iYOzⴈ_$ɓ'v=4J !VɟeU16^Ti?[#ܹs&dW|A˨Wa =9{ёJ~y/%pAtTѝDs?L裄Pw.?L8pݝ0MaըjG:[`ĥ`bbB*O 05z3b7#ҷ)ƒ{R\2nJ\.ߌ OY8ݵcqymjjb3p/>IENDB`is32b`_^ ma`ff`_`m`a a`m_aƁ ra`_` c+`^_f RFp fa` ?U``+c _`NT q`a7``f$ f_c`]+/_``aƅ aaUaaa` U_`ff`ab_`_b`_^ ma`ff`_`m`a a`m_aƁ a`_` Gc`^_f wJfa` qC``cJ_`}M`alC``f^C΀ f_c`cf_``aƅ aaUaaa` U_`ff`ab_`_b`_^ ma`ff`_`m`a a`m_aƁ Ⱦa`_` `^_f fa` ``Ύ_`Ȯ`aā``f f_c`с_``aƅ aaUaaa` U_`ff`ab_`_s8mk/ږ1+.҇$("Ʌ#ic14PNG  IHDRx AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs%%IR$@IDATxTE͙]r9AT@2"pAO 0w;&ԩS333[8PZOwtA~ޅ3͛w?w\SKN> t= Ab-dl~78؉uǬ[g`@>b - 0nܸ8t{g|A?QI//ז/&C `[ I&UC'8h{.CU`n]˜9sQ5a`sf|[p0ިRMb'pO=d//`><>S lяE)a/,77>S_|F_ |A5pv#pa@exu|>FTx|pAiL@lHU,hiFiWҮ@iE#r# _å]3 C/. 8}I/3oqb D94?NoށO7L 8#oRi+!&zcѢEǘ/_[y:uWb'&<%p/XG.{>kɵ|.`n3Xml|3l8&FdddL'jn_'2& ]hlRv/p֍f0 12c_8:)UqaLt;K w o.on I 7Ai`OSN ~6SJ8bL: .E`A8?Ru]-3OnYO.sE {'ږ|w/㫡'L Cv  _t8`~(QqbI-ВǼ/S v*1& ,\{ +% f6LC-nCƍ+[!{* AV2#22YfeQ!!'67]&?+{_eu+aL@g( `N*S_L42 ?6)V 0$ߣp~6s =fL &@>;{Tק7*`@,Ad=ia%_1&`_gP'p86`R{{m߬"Ё*t ~h޼yuvba h|`L6Qԩf̵6R E;{cpի`؅S1NsV"YMj)5fR `@ gcΜ943El(Љp~_qߙ`z@C85gU'c6c[{oUCu+bL 8'p>ҜGe  T?3p$8WL N. ;B S0Ș1c^k` z9--e˖~!6D 7e.xb|s> 0&`4~Ν{ΰFb ^#Wh܋{r ,Wy⊙`&m={{v)mSc|uR> 0&`&EaaarN3{q3}shSƄ#ZCDD}?YKDLQA>geeztk❓*Z; t]JJ {9gddh?'KIjxI ;D5T(WTPUbłQQQ@\ 99!..N{ώիW5cBdX6}> #P7>|T@e˖5j@͚5zcbb'wEM$IHHH(0.\gΜsin qEw " (&zZR TVMсj׮ Ѽ !n%2N:?N>]>& hGmFb@#Z,>bQ O9PjUhР4j֭ {4cp%8y$;v ?/^^RK-X`%pP 5LRc띭[=~Æ WӦM5F9ԝs.)^6ZԆ1 '}&Mzzʧ|b=@4;pA8r/X-%JwF`.O/ %xKm<}Z_gD7o͚5VZl3ynK4@4.BR%BJg|W="")ioӦWSBa ۷(aPgxٱnadɓ~/cK & ::tCƍ :?*D;`ΝZ@#ԓVUC$iPp6L4?䦹| cCǎE308ql߾l٢E94%M{ѯ6N(&-Nc@VH<#| < ~֭{?;T 8|0lڴ vز1.$Ql8?M379 4߹sghٲ%{]fh|nݪˀyp`*v… Wת=[~L}WOS{Qr.L[i۶m?ٳg@<3z~)_.6%yKA^\Ηx@i]O>^}j> N[6o A[> |n|kk% I&UéjkP\-8߫W/w)?֮]*0Q7[y:pUM( oN_~P^=}+ژ(Gի a6#M0ްlX1:w:Jsն.$$D[׿[|!1@Y OgL 8 }ĉv& :r-[,W{ױf K ?ի開.a}OzmY:u;>S S 0@G\,h9 |Z*cYDk5wlD^ad@'pzyvmZ*(Cʕ+СCʡXI{ ب^)?>V×#Jsww;_|qPIW*MKlx .x.%OCjz L@I4#|rUR?倻q9+U9628/}һ*"Pzu۵l|0[ C>Xz6~A#v_qz+2?=|=ߵkW ;ػw/|jطBOF#`oj6<#Gc*Fe|j>h1b~-( 4C 99/'~ߕGxۓ|.r)&Y7ؽn^§-|S?L21=tMZ'N\<"@۴igϞm]iӓpԩS?_O/*aRJ޻ Q0&3J2dظq#Q-K1죏>έmv.:|p!+V$c#bQ98 .Q^.nHGi ^ƹ: 'ݍ<<_ !⧩~8L L ;;;oq)@27PW}5;FYyD6L}&MQ0fLDgϞéSLlUΦw=ZzOA95_j6 1i8O$yZ!.?A`)z L/য়~҂eeeY#<^@#'M'8Ig8?y.ָqcRJ.L XE0o<\J$F@4ld OpڿNQ^o~IkoȐ!Ϧ-5khIrss)aowhhhٳgZм0M~`o/zRyS$nݺq"lO& شp@[8%P5''Ͱa>[vmc.>MC[8El~{S6X}hq9'I^vс 1r }222bW=TLƍOkɟǴ.Hddzw6kK^ @/x8}lEEԹ-n\b큶0NS;+q~xwg= UfD~ڶm p hN?yHO0p+l;BϴO>$t l&,YI5SJK þKj>6lhҤ5x޽ˁ.M0\p: J% tjy{ٶ#<;w-V<\Ivú‡מ4;4 u1dZji!w vO-FMhlA#i`BC=bȍ2)iܸsO?\b/%As4ojY"|оQeo.k &@9fΜ W^5%᫿[ϝ;)R$FUw3M;2=SPJtc$0۽ziYгe ^1FEEANȑ#iDZѩ{Ϟ=m۶-OKRij|+Yeum֬ɏbsaf{x1}AUzصkW8~8߀<51<"T6ZU@PȩoM& @zPEҥ ;w.]CV ?@^6VΧ|A,>GWKo6E;_l8 X"е|vo.^.\&/E~a :HJ@zӟ-3Exܿ'Jea)c24߀ WSᦶ (@+idP(q(xm2F@C߬}Æ ?:x$7BnE{0ڡbᏼf8˺e+Aʶ@k+ 9pmukbeR8? Y=c"/a8p 쇶ZX[8t*t):_KɄ? WӠo(O)̌n-Z@VVht{"֏=1rOhQd>C+PAy 4.K{C |k_o>nhE+ـeBv娢_&p$ 8nr7703 40oGU=l0>|;  x6@5y(V K >Y.~ RR#GZ _t![\|.uyS |ahQԫM|(@NN] w;pk1^tNw@kkNv_zOرcy|n( 9uӛS54߀>kB0xQk(Yrr2:uJ:eb<hd"3_HSp{b\J#vMKKm\N^Ltlh7œUA>Đħn+D~AW^cU"LJe \[S qO2]Nӗᖧ]/} 'd?^`z7N&"4]krfڴid\_H,$Xk, e.nG)E ccyx'I!w|ӦMGͅ Fyf 3:9 =g C13fk!f*otb@Em"=Me$ B߀U[O r2R|z]r ҽs,ZxR:2I? g3 87_zը\ ,ڊw~ǡ| gD~8n۶k6(AD5m/44x # EL#[ـ?/7HU[6N华~œ~O+Nѓx%TY apt%-3GK,tU)P Blz֭[s[*wkٲ{5&e;0qĚt{7o.,} \IL^ j $F;tKJȁ=U .if!N}oT_"pJ23JcC laVLyyypQ+x6g1W.tr-ājhs@& ,YDuK?|f@r'QB3pj_תpzMbwEe<7)Sp?#rHN6n*e;_J)nigw,6P,kMO |I7+Wz!#TIVfZN>̟ @A~a;z}z*26\H7> Hb2? 8v40ůy60>2w?M qBo&j]@ٲh; & #|\40O4>;-=zIP.}8}}*O7!1Jݻ5UQH^ >P5*xas_ E}={,n1QR*"! tG(o큕_XH$ Wn9-|ȳ<(6(岳@DDttn)9& 34?o S@; 00P@ūq& |̳x#yG?(7iӠbŊU51dYԲR>/6p{=@rGUad#F-ZL#kZ[6D3#7 GYf0lqw(ܬ9k4iR5IŖ 3p 6 e½"H>\#0x`/n laX")>b~)! m@jzQo֜(R vogƎJG |oʏJq3%ZT"@ޤsV[-~@LL L0dN8K-3eT\Hz4zN:]-,'.}D Hϲn tP_ݰC+߱J`ZKI(Arq-oǝ2b?]<Ҿ}{h޼8$Ld4::hLNmڴT:2*0!@hh(mb]O.ai, *0wcU JjF6*x5Fmj(t^8ނZ]߭  3vkX~}c4\z7zS8s%Y9ݼUd`t-WnL<6U2nMFYf&+)gteeÃ2ZW/[ݴFoʖ-+ށt7߰}T1Y ".1ͪɊޥW3`3:'UV@wBW ?Rh۟_8't38JLd~.׮24X3^*ҳgOVM=cy-gjXlDÆ S6ִ1WL6'? +<$~i8 \ϕRy*$ OMkmEk s=\8~!Q: eË" /_C@rr2|x*N:u5k},'9f`f,ݑ;w@9 pltzFZ(,|P?|p+,W(mIsS prTL:u.u3pE׵ ;N:ЪU+0*, R5~e`_# V9K}Yp)\fIV7ڮ];[b~tVV.΀hОFOVv-g$ &>$)t=eR#Y)p ظGשpr֭A*R6=pםN,(h. J˅ 0 %ɵ̙vA<̖ATL1w@FWADJ4iDqX& 2 +mP:%,_%z>?0f%E( (okrc W;J 2^i~OFVl<&o;5"##zqOP!XN{F^E$@fe2 3tUN/}n=YpLUV-e}2bccP& A_^&Oz;xI=/~ tr-\>(霒GTL@ǎn/XV'e'T)2i7C8T|}!duۓOE3s6x3Au|FkwZeC8zt @ U cdž#<%˩8%ЍͪB@F'@= B˟ԯTR^ >R:9Sf-LҎyeOFRF/L]2: s:U(?= rA$P}&LPtWNiC߯_?QEc0R@NV>E#\dHѯ;5#(Vʥ*GcOxGrPzI+& K7U~i8>m/mWFgԯ_h P3d6E=UE lܽEuZe k9}Fدx aqfѣc ai=6T  0 P(`ي\>sxbRkҖ^@?th,Pb.<^gD ΥJ1۱)c",4 >0S]o/-.G?ZF_ vRPz (EVE4R_f;/S 1NРGGO]IiVl,l6jUB cbHH0Px0&2_ X050O3[lԶ(, 𔔔^JJe1[K/1J שV+μCY ||O{衒Nnnk)71]ʮL# [K?0%"wٹҭRнz)nm#22R~f5x gg?_H ťxC1iLz;62iتsxU]Y H!{*4ӷA'?VI~-p;ܿ@3;w,%xUا$@hBIYD& /(Hгcԋ32۽9 hݺJYa- ))VN"C(AdY6& xS1]HjUVV6zӝn2W VDž 0h.Ti G FWV:Zp/[. ͚5@R EZ xkK+Cg% RlRewK5Л0;Z`۷WiX'A 'mκթBPg_I{lO$i+*yp/~z__4zed #Z h ',zX?0^ZWSEJɓ'+IR FSR2|G{<6U/8 YF PT  A]ʅI9g;+b$+1,pN#X+"С` #DbY韲VDZ v~z Jv[%/>* !KJNZIB\𜀌;HK gQ֓JL U |/qD ;;J5r| 0Rj ڧQ'LMђ14M4e TDA8t YF%eH3rPz@YU)SEP(ÑK4@Ʌ 0$H4 鍲?>\%/xwcIp*cGI. 0ߩt_XD 8A\tmSUP%,0.])))mư>wg*URJ>2{1;)<$b 3;Wj\ qȑN-bZ W6g1 FXr"zª)Y챑aaaMuK7Tqq| ACWʄ>r6HԪSG@PŋS, ȸ `߯ X!i?VhҤ*9dN @ #Qp8q,x>߾qX&fnSJđq5@Ĕ 7[8}ǒCbVDwt֬G<޼ysON\LteH yP#PD! N$ij1Plt| |ZZCJݰaC@ł2Zˬ1MתUKfXv&`9,ͻn jV wkjԭ[@ظhi"O@0&-Qa/iP=:U8XRvmǟ2n`@zΣAeXv$ș@]ۤ^U <:ʾ S fl':$K*Yf9%( 626ϑpȕ.+2>} c  i :;H},@h @i  [$/gϞWXb~YSZ*.&' c ZVL:<(PVM`"""rκPc= rJQa)'  `zfsaEgoߋؑ7 [ǪI1gy$p5YNpy 2a0}3fLYU3ȅ 0 Kjȴ~jeq Ucɵ^zg0=K@5)3&|' :jE3%B*"}$d/o7$DQ`L xG@L4 KQ(! n䤱 .DL8Cec2 0}8 :=OgYXKʴrlRfM&%_BȝeV P&`Ӂ+_w{RoE2 y E:G<<L$5NsC i:KVyȬ蕢@dzg@bYE&$g7Y g뎈| %[͊!϶K*$EƘqG&cw7YV0| }+V#G#Pi0%c m3)gJz[kUTJ6^-i92 "&`,?G}> PKgKi'u!(mstbQ% tEY&P Z l6,z(!x|`ۄ+eHȆ<GEr\~xaF)9:&2׶9Cgx6,+#(g"aӖr{CCJlh eΝ;Wޱ/u:gnu_ @|g]Wk Gs>RDƜJ+PB9 =BJ@D@Qo΅/ |K{Z=PlXI滲eJ+aBCCeraL@<9G.[ _b O,戈~srf̧@38lB ^V>x@U-/bY9=2U}m _ LF<p#d+Ly{+/v>}($dI06",g@땾3ܿ ڳҺzcO3@ d .A+}ZL{G}+'I%P=J,`cC;+gv#>%x*Jʤum1@k\Pfw0ev$ `wB$o@X\` Փ1Zieމ&@IDATv /\:Ǥ_5lRkr6#™Oy*0 GZ{KuGn ޶~cO_JJJƑd?],)ni6XX4mTsjVBO!2wEd>d}'}@ XK ͿG@D(q{`s]Td%o@@F^WYLd@c.L%Pe݄|1Г3Fp0K}G)QнEu)Ih>P c 1:`B\=p3Ʈ;A JRgnͫE u?|6@\5| @unA K@8ʛ=)Z ݄Hw7T-'m(yքcRO{]|6`u`ݪ9==1J'w wJ-tY` j3X6 37Mo= i#2 ؓCqAщQ{efHJw+c[%Tvk`j6F Ny]jEb]CKRk@'uK4% ?uK ?}[ԫ ߿2f=|3?(㇞R tqw(< kT@S}N%կfSP ͝tCM|x̡8r1C jT-anpЧuMCʻ6M:7P?.P@:[au -"pąea<769ԯT+lƞ42M`eX talb ]bZ:FgfO@ӶA֯j 1HuorO}'zb33:"o5)0d]@'DEqERBƥ|A3!9aL#* zը Ekc "k ztjR6u/rf, E4sѢL~b-di TjC$8x*~L9#KjF6Dnњy^mT-(aEdf4XZ_{>ﶟǮc2)|Nua0 z]ri˖-˛0aBv9u;m/Vz#!9=[X鸬dZAMNlThh7xD(ʽIr+15 NTVpQ~JhTb$4ٞum".%f2@Z+ Jvs~u=Mˮ&=P;M{}!Mڸ% ւ[;ՖS|Kk[G@'yDh9Tbi!@O?8wv#8kB ߧ08u=i> +2| ' ؃ɀ qDNM30#`R %t mU 5|;G/d-H 0cϟ3fӫ=:A/C{}b/lXEU& S0HIURpO쁷WDž 8#KΨ1 > (iGn}X?9..-:`'@XsKP`Ip* ^V h) WSե`{ +'!`^pHƜ, +o16jo\*TLLL -qFc˟ls}'{%֐:;/Y+odzBK{ ~pEODnT~~na}51Gr)c4lP+6'N]ޒW ;$ CA[` 0mYe X?9  l1;̺ZL@1G33"ԡ٭1x{#oNunxi͟ Ћ/Eqco4pJ":qH"4B|+\Fuڛ?/0_{Ɯ  ƮE:w@r'<:\x\gId_I%8-k,WNi*URw&q6?gbܔݺr} 1-=Cv@FhvMh2"/Qfx/6ux@%8 @teB d+ y ?ssf|oTXr,Q(0)Z|CclXǞZN~çDž A͠m&XsuZv\?z… ?Hd28|VڜR&$3z!pywN³3 +u)=hr7Ox,pt?Q`8B3g &B>sql:r%OFϝ~gS8@emڴR)1Bm( hYjj -ڄz}j.ױw/>:2媼#>q㫼#Klx|} Y XN ˻V(0OAxTTsDemڟKVkhѢ+7 &n:/½_JHaO0Q (=a9c ٳgwh&/\p˵LC+pxg {’` p Osnn.\e9]T{xr-Ii66SۤWc$/ _@?N(| O<)8go0}˂ڋثĉV6KE؊cs%'.$_[kg .4'%x =~ Qe](y)Ϥ}/T0E92c(`\$@730)!&v4dA.JT D(6,S\)='{/f`RL8Tpdxǧ¿?[%|50@T&`}G!y9,@1 b'PKeƙ;xJ|TxDfwQ ]EOo6='Rz<_)HdX1% ظ%gbccUدi>Xȅ @ "e'eɁӧ@^fÎ?N =xB$)XéSdry;M f'zGb>}z1~9sU1k;|\šb':*'|%p cRݩdl=&` )On)d"Lh3Eƒ-ХSR;|0dgs:gCz{;c%P9&L\X2 deeѣ$W3i@N-gx7/7peHY"ZgDdq_p$9. 9D;tbdV0ٯ&g7D4- E * ի: #oazw4c:^ݻW"i' 7 M)#,tTx. )._BfӬ%Wq.P,2(L\\4hAvU1(& dW#/ ڈ&+=/_.#+M($ HabT!ر%?n)ȿХFv;Ɍ[1 4U^LX* ;b]IQ@ڒ*;@8'p?;'GE'=$|H[e/PZ2ڹSF"NL2~ E r;vPBvpp`3*Pg rFX6c uX{n IR q{gwtR8&Ь6CJG{i̐OP@%cimDreDn xBIr2 2V\~Ҕv@KBz`׮] n((bbhStR ǿ={H){իW> :u]\3==8 fbF3Е@u+cDgddHg`8Rw2 :[.E ̹AƐvN@wg2:*Q줸\$P1I?@sӪ@0= oYeuqqX)233ANt紬[S<& mj ! Z -uG ?"V(}t^]9S߈@vuœ*wmP v `y %%fnmB1`иO~gHNNVߣqvEyڴi.7*m[TKO~eTW3 MnՐRSSv?>yH2Dl+]v-72Ej^tL Еׯ׵N +[l2- R aP9]t?~\TTЋ:S_QT˦i`c-{l+ ޴׭+1KireU+ó"tP@TXP gWLs aaaEzk1uەHU RrY=:aF԰b{r pEwgND LN 9eC*O{+nݺ?ic98yhbY"Ov9%QwLҝ&6r? ~/NTF 2 _{ژ^ZDLvmVvA&P K{J@~oW]3K]B^*?Õ0= edG=㺘Î;T!?^ @ن?~:YV$3eh& mQ)t2r@̙.^ @yӨhא3 noM,!' ,AӅcT_xC --M`7 b??V`#4  C۰3ObPQ_/\O>zS$/ivOE6.uB^V] /M~RPD Wiɗ|06O'6.u׮]AvS|1@ahT2b([bbbi هDwq@j#E*MaGڿ}ljP9}g?<"(R`"r|37UxftwcjKsJd/*m?U/u50?0/2 s-P(e\imca4g%W0G#xqBF !uV$`, 1fIP(m$SOm>aŽ[Մϰ_ooO'իugݻڴi hK=\ ˗ڵk"rp{pl<}ryX_ ~1 0ewݰaU[Y+oO 5omH?sȐYe % Gh{\hG eÃU5@zz6sk7tj 鍳'qXEHiJy7.p[ALDoA֓EӇ@ U˅S!X|r\=u}:d @Do$`^.]$Tb;C8="W_̓x]D/^~I)P|nxjWfͅ>LK7}}/4YN:"5ԶP.2D X a ,YA8W .pP_ 3iN$0Zbg1(᭨O?m%LnK o?‚yITJE;E(?E 0gFi{G~,0ϯFeCXZn]0h[%%%0!~L%1B NM]}cl\pO TBK5䣵i&"}ƐRpgRr4+͆c`Q@)C@FU``XUW1 CA:u|!N7t$A%m۶kLlOͫèMąD8v1B穷eˆB_VR  'OC曆f3|x"e1RP)r:բa#Uʺw%*'n@ß)`BC`߾}yfs3hMg10=.71Z!3?z(xz]MqI`K֖w9rޘi]TQbh\ ~'[69Z:΀S#Qƣ=!г$pJC s gW ǤP1]!4 ` Pz). (X.لiovIYZ{mt_)W?m׮6L%ewwTI!:#,h۠J.GIFD  p?^zQ$R0v.qFޓqm롔>9O+^՘wuvT{[oAbbrԸiO$,@lW)_d FȀl}]zOZ0mHj8%CC 5 hLą XI`ʕe+E0mӞI S j,E;v́@r ޢ:<>оtC|6!5.LjW5Oԗ8 p[W3o&=ۧi3g@56=k]4Ѯqyap5Hʱ+  6P|mrLxwN/ tG7|{-7.̘?@\T(o_ħў-kh5Ҭ@6o#ԁ.@'1hS˕0|W'D? =Mm3ބ usR9>3P~}QDRV z X!q)bAO:,F jm>~O _~YT0<?Y&MS:PQVX^|E a)nd78l>_n8 \/>DO[:}.8aqdee?/_G(}%[NӧO7=e8q Fw}YQ[=`bc3)hksaj 0)-|2gC-SR+o69sT'A}wV0qT< nsڴiйsgK!@Qk;;_\%涵ᦶڕU%5;v;C2uШU Zj $f# Ӗʗ/od3\N^LԌ`;{cZJ5Y{ ESlװ2th\:6mlTCI{Zܺȩ_ʩׯX0n~#G ߏP` zꩧ8J={)!  &SpO]NW-OZDA׌F5AZMk%XDPW_}ʖYn8 0>m> bd\ȧbB*Fc S%ZFO3 _\rs]Pܨ`01IfHkTWUqZ  +VmS$ndB5v0j2=Ѩ&^A P#2;_a#pxUo8]w`Bpd!ڥ,dӡ6B&Y"raLtSzC;FFK": :^c999z XY~&!@͛7& Y pU0FpYi#:`RpiupCop ލL9EGQ<i8E2-hܹsPlY[sL 0YVZ%`Jw8'CP3:===|ۣ/wj[x1;FQ0&MtR;@BL;` g,[,gBqjѭTbL A 99fϞ '򺌎SZphgϞmڴ+HzR'OB׮]9H 0 3g… j@ǿ)* ZK"oi>&ȅ 0&`oU/8~,B;wfCiwZf͚U%_~ZFK%{rWXࣷ!\ yMu !9z( gX(&&~z@_/>|… R8J%B!ZH;隆H\`jطorŵڒnz0C`*.ģ%5TlKBkbwU п 0&`Zgcm`wEd]7.n0} :uP_`B-3f̀!1Z_@?3ʰ@}EnS)9莳k׮o V3&|&='$$\$éWeUOƉ8 -2EƤ$ @ Lrk@vv6Z0e{}~*ERYf3ljwZ5k"dYDeLH;#GlFq|pҌQP/]VZmGȓOg:vP7: pE]n/T0x=3p@T~)x]vl.L Mv4͝;n*z :kENYuIg4?@Gh@"O)Ɂuf#@apJ 68^Ճ@OolIP82\W@udEåZ\_fx~WH9@p% 59eҤIaL B/UV"Yr$KVZzx%H(QK7on }YI&'6,1FY ho&2mB[1lْ}#/]kA%4N,}.b\ ,rqW@>#XA;uC( 0^6:;Eݏpߒ8&Aǥ8 0doe>u\t x:&!@ϟ֭rٯSRms] Ùm49STc(̙3о}{@TU5Y/&!@}][/ ,1)u3ؿN/.:RcAͅ 0&`de;w4 ]]ND]zdܵkm ܄cR֡/_ǏC8Pi{&<&@Y(ϡC8p됒s]W:,.EWȏj>0s%h3QQQPn]Ed0뵔vv.Ʊc3u<8[YA$ ;q Ҏ A>ТE 5> ؛nPd%K}qa6)S(mPFd%xȁcOav%@{Ν k֬+9h T|WN倍?6΅  Q`e։ 0Ph3gRUjQR8Ƶ0Hnݺ}YnǏ=Ж%!!AU~}(W-L '@ =(p8lcl߾=We7ʎNvlܸ0j"4lqߙ)k{ァ ANw_nA?>ĥ_h́E_r\E L@}8# 9Qr.pwOG$@N(~_ΣK>hv.L ؀իW~_W1h)8b3Oq& q*ϦMA*=DF%P 7..N%|%gEaݾT"㵶4؄>cg;NOi*p֭04jԈ biJsp"݁c_0Etm Np?MұA ,_𒀍:UUMϚ5KsV^YD\ԩ^9rd(v?.JP&cB׮]}/eL@J{ǐ"# ?U$s:@("oHxT**RHSg[l/"2$PlXax4@(B !$\L wwg޽g{Ϸߞe e|'q99L$XAA1'O>ƔAoLG$J|r5Z1R{|x{c:ܿJiF2fop ;S}]$]$-Z® Z -3s!ũ5Nk(|A5~x%@N& ֮]VZdEPn [pFO4>:>DjR+?Jծ];Y 8@iiJKKSEEEN) C8Epˡ 遇0&@VD>5ZFL o-Z0 <dlzdKB롴yZCכW% ,(SFǸ85zh5dpcVi|~K(_n8i4'+y^C:`2D ,ݮq?߿BH@ڵe ē(<u5/`=cQM(O2$C=DC@T_ٟ$ H<^H{$%ƚUnȺbpn-0Fz(lx'CUG3f̸7}ծ(&8vXէOЮwޭV^jW{?jY#M00n<|kV>|8p ͚VWWm۶< >5e>cʴ5/ n䄈\X-(^"e1bj֬vSaT[lQk֬QOvUC%K?L"@$u:uj0~X;~xSGZn\!, PVV6nܨ6mڤ$7_صkgϟujDd5 q`<`{ȑ#UC+W@덐̇.ikh\Caf< ɘ۷o dx7onr,' O~Æ ѣg 㯀?%##cuȅz C_%`c( c.lVIIIwޜ=`c݋y,dn8o|{ o z,VgcUնm[# @ȍ%pYot,ŀۅ,:V\fq *L<t!/B Pwy:fқ<Ν;˲<7|tҥGPa RSSOf구R:Dc`~(F>2oϞ=`uW@@FWTT<ɴl{!w/y6W}ub g@ IApӏ߷oNJsXZO,[,Y!=&r0MC s=BDzk0U{޽;vPΝzUC!M@d]5NF`w+p={4 >>JYN:e彾:YM K^|9%uhX6SRRb1(&t!3[F ..1pMZV 6n#צaGLSh8/`(!Z[G@@w0 dce]ַ|UlBVVVqR 0Y͞=~09(oYek HXbY@< ]v[nR.sKKKաC'{qgn"7s%? 5Ν]^^{xmu;#Ccǎk9ܮ' #?$%bQYx2b1 ́ة8QꊄB{;G@-ܹ|A6m%1w_settǎ3A 5&%@>iiiEe6K l kz )~]L# Zj$DzӃI/sNO>lnMT5ps“@x]+3p[q۠)B a, %'$QsaLLV臲TF;! |E%7EÓtulWR^ww^-[%2iD\x{i 7 7,tO/ 4g娜!k <~(( .s. {IO.oM T߃[U$@"cRf.]Ħ>}ܻjm?HIlr# + )DJ֗MmrQ!ۨ"Њ@ZiQeixaSSS#19Rԣ:$@!pD~>oU4"r1S'Do@REa$@&is0jPh%ύZ'x-*j-ը ajO1o0J%aoWX]Q'K H4?_&Kՠ~H^o#}{E'A$`*mS_hj,̑pdX#{KnYf8šX* .I 2+c:7('T+3^D6"x[ ߭-g0wT$!pWcNpr4,"@xdo6)/ ;߁`?9H[5H|R e$@$'2t/c)7srr.;PDd36wCu-_87KI$p߾+\AN}I@&_aD2PpW1' CrDKwH8L k'2AAa'GHjJ.o233wH5`A0c`r,/IE ^@ǿ#\^r2cd /BQМ~!###_sT?4Ŭ483Z -GW6[*d%"@Sed " >o~ 9_d*JEl\T!OTHJP .^IDAT7򠬈%O=| T2o17J Lv`eq2,.~]L$Äe=bSRՖu^2Vȑ#0pԘPt ߐ"QP{<̘15??c`*DZ.jܯ}7.:PFD3=gT]]-+NBx4^[g]}V iX8^x}AF߄{p%fBx/& +& /q D?FH%.:̷,aBm"?J@{\}M $ CB{{y_{ҥrS [+m'M4&&fĩZA+{N\]~)/7jN*I jG}DjP~~y (1ׂx0/^}O-wkV|L~ K]lIKHh8(4JNNh"D7 t\?qSV%tL{ߋzt)[%ae$q4s# x<b+ 0 [*HH0 ?# ^yJ?s޽tǗO)c*Hh8( K LVd h}$L_D' 3{5`q\;UFΣU.X>:h"ÍHHHHH RDIENDB`cantata-2.2.0/mac/createicon.sh000077500000000000000000000010001316350454000163310ustar00rootroot00000000000000if [ "`uname`" != "Darwin" ] ; then echo "ERROR: Run under OSX" exit fi dir=cantata.iconset mkdir $dir for s in 16 32 128 256 512 ; do cp ../icons/cantata$s.png $dir/icon_"$s"x"$s".png done cp ../icons/cantata32.png $dir/icon_16x16@2x.png cp ../icons/cantata64.png $dir/icon_32x32@2x.png cp ../icons/cantata256.png $dir/icon_128x128@2x.png cp ../icons/cantata512.png $dir/icon_256x256@2x.png cp ../icons/cantata1024.png $dir/icon_512x512@2x.png iconutil -c icns $dir rm $dir/icon_*.png rmdir $dir cantata-2.2.0/mac/dmg/000077500000000000000000000000001316350454000144365ustar00rootroot00000000000000cantata-2.2.0/mac/dmg/DS_Store.in000066400000000000000000000360041316350454000164530ustar00rootroot00000000000000Bud1    obbplist  @ @ @ @ .bwspblobbplist00  ]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds\SidebarWidth[ShowSidebar  _{{-1156, 376}, {644, 406}} '3?Kbo|.icvpblobbplist00 _backgroundColorBlue]labelOnBottomXiconSize_showIconPreviewXtextSize_backgroundColorRed^backgroundType_backgroundColorGreen[gridOffsetX[gridOffsetY\showItemInfo_viewOptionsVersionYarrangeBy_backgroundImageAlias[gridSpacing#? #@N #@(#?#?## TnoneOCantataGPH+background.pngGn  .backgroundG@G^#Cantata:.background:background.pngbackground.pngCantata/.background/background.png/Volumes/Cantata\\ Macintosh HD(H+ cantata.dmgGPdevrddsk cdrummon(G@ e)Macintosh HD:Users:cdrummon:cantata.dmg cantata.dmg Macintosh HDUsers/cdrummon/cantata.dmg/#@Y)?MVhq&'09;DMVWY^'.pBB0blobpngPP@  8     D@ d.pBBkblobbook0VolumesCantata .backgroundbackground.png$85hxAь^0Aь@$9587CC25-9B4F-39A9-91F9-1601ADC89EEFaUserscdrummon cantata.dmg pe   Macintosh HD):A)$BAF1DD81-C9ED-3D28-ABFC-F300942D002A/4bebb2a8723dc0118a4f55958bb970d6838b34b4;00000000;00000000;0000000000000020;com.apple.app-sandbox.read-write;00000001;01000004;00000000001fe289;/users/cdrummon/cantata.dmg@   L , < x0  /Volumes/CantataTd   L , < x95162b11c3637b024996fae77269e793860c11cd;00000000;00000000;0000000000000020;com.apple.app-sandbox.read-write;00000001;01000010;0000000000000018;/volumes/cantata/.background/background..vSrnlong .backgroundbwspblobbplist00  ]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds\SidebarWidth[ShowSidebar  _{{-287, 130}, {979, 630}} '3?Kbo| .backgroundvSrnlong ApplicationsIlocblob+ Cantata.appIlocblobAY E DSDB `(0@ @ @rs/cdrummon/cantata.dmg/#@Y)?MVhq&'09;DMVWY^'.pBB0blobpngPP@  8     D@ d.pBBkblobbook0VolumesCantata .backgroundbackground.png$85hxAь^0Aь@$9587CC25-9B4F-39A9-91F9-1601ADC89EEFacantata-2.2.0/mac/dmg/background.png000066400000000000000000001756131316350454000173000ustar00rootroot00000000000000PNG  IHDR_WUbKGD pHYs  tIME  :il IDATxy?s}r(rD.Dp Q'vYJII#AN 蛓 ?? Q'np\gSo:A'86[>}>b:v Q')Jր>rQo:A'ի⼼qAN n/.**zb Q'F[3;;{~qq#AN ..gNNsS:A';Ҽ'JKKo!u @f)--z H dA֭[Ju¬JKK/D n7JJJ(7N3+++ G D Nֽ^/ M JU Q'=++ ) nWrss(++DJ$Atgggleee7R:A(왙(..nOEs|܍ H Lax<|) (+ H H!$##Cv0Es32AI.⋊`LCJLAtL]S>WTTxnn%!H :F9a Ñ;++ݻwF=NAYc;\cw:px^ݻw8ANI9FQQ9-A'33=z=6ANay_Ҕrl6wffz ]D t ySzٳ=:AD {4w@SZ({8 u ۈծ͏$5zv PSQP`>c qR.q\|8v~iayZQ? H8$ 8vNacp*IMO;Bnd\a9hll|p߾}"N6l.%%%gxWNX^JPA"777's9x<  vnQWW1+E'ݻww\n "$`mِT]q:cvtuv:// #DݨpEQYFFXƘ}J:qBSTTE=AtnnFss3TU56;1ظAN<PO)X< Leddr?#Hԉ^ ҍ(5%{*xp\dddx hq Hs\KJc$ap8|x<]1dddd$AQ6j2]X셅z}W޽ҕ#H $Ɂ2,F;Bnbkd>}NW Q'sx<deeēfCII {0Ƙo߾Ri'BeРA\&-ʘ~\.^/<԰ɔWСCsŋ~7@W Q'N Qgr!ռf豾1ѺT VٶzВh9Ng\x9r) {cc#~ѓk0PU%[NŲKYOϢ m<,c[<̖yQQ;&~ffKJJ~'ѕ(>wDOBrY[,FLɈZ2mM{ɶlY2o}%/wH)lFFƍ}}@! u,t2Di[XMw(+"dee$n޽{?(J6=uxucpafےwx:<f&{nF8PJddd{ m6$$YVXa&k)Yo)Wh:rKMξ[no3 Q'HS(\{:3쀧-Tc1Sx<)999Wvi^o&= uΘMEL蟉i-=pTmH("x<9Gff>ռbz]ZvbfIwr`'|N;v2k{=?amÒ%Kзo__2+ѲK,AϞ='V#Q9EQz M/wN`0sDBaD/Ț5ksMh:fFFN6lӧرcx %KI&999I]/#lM:Xtirb:c&ƪ51@Qՙv-:ѥ=VOSo_DdJ0eCd7p>I`~JS0dkĉ裏 ]x4ج=Nnn.8hh0ghk~y~ٽ{\'곎iד#_''+˖-k' {?G¨[afcV.RDv7(MݿOwu!jzb8".ZUU&ޗH))$I'X|5k ظq#qTVV<~?8xq#//. ~-~0fÑC'g}{%K.ѿs}ѻwodeeϢ%1GII BիW/̘1 @  YpYg^GԩS {Fff&***zj,X---q2331uT 6 ŨѣGbŊ1׸q0n8/\}YgGAA<Z5+**­ފCs[b٨qgcp~V޽*GiѵPUE1 t[Qh ='ا"yyyXp!|uaȑ4i Y\r ݋9sfaر`0~}x7pt]_Իcǎv^~z`̘1 ݎ{,e9GYY`D]|8~8-[wy?~<8E#33@pAlܸQ `Oh3g|r>|٘o߾/9rdjccd:Vy,'_- )$ꄥ{f"jԩߎ-[17bٸ[#n޽{xb0ưd,[ W]u,Y8 |qK_رcO1o>?ϱm6ɓ^Çcʕ]!-[Xh }tM7bƌطo3f瞋իW"0Lϱ=sرӢ<"ra quDRs+61bjkkuVMP[[#Gn{0Į]Э[7uaȐ!4i233M{ff&EΝ;QPP`Bc?x`|8p@ c^/l6vڥs#GbغukJk BꫯPZZvnwFmm-򐗗/зoߔXs|deezW=xSc\̥ybȿxbH >wR'NX"AII 6oUU_~%ࣾ>L|AL2'Oƭފ+VgjEG~~>~΃톪֞fݣSAAcZX1sL{(`G aYKJJr+^QQ{'e!//1֚ dsqqYYÇظcǺqDޓ#Up:j'Q'L U.v 0 L`c8p :{5ΏԄٳgO~L<EEEo~[DAK/aΝP7th ѣ|}݇AaѢEصkE 7܀e*++uh}cUUUxꩧ}RNc6t.nݺ-8zuuuiV1 LZF?r<؋z ~[x ; hl" A 2999X~:U|؈sbǎw^SS2߿?ك{Wƿo}[áG-lp\1iӦM߿?:,Sס_~سgϟիWO>Cڵ G>}mEqq{۶mC=PYYׇۻwoM5ݬV{~~~J۶:>ł^I AqpOq&/]'KhyK._Z߫Ϛ7gTWWgϞ0`o߮׻vZ\y/~}wxPUU[sŽދ]vgϞa]#FUW]e˖?OsAQQz}ܧØ1c ˖-Ám۶s=ܣGaeϟ?=,YߏѣG'֭w]̚5 G^pBTW[`{ӟ{xعs+ռfqrrr9z/ddd :cV=<ø/sɲOO$>& 2|31t:n ґ${݆TbE[ǍO=T1f0eSiWWW˙ԃ1477響cǾ'e"sIS$1Z-dYFI sa-ɶ3w]:t|+P]]UVlK*.pvFټ<0-Z\߼Esz$%(FXBDCFƘ'B>bPQWU%JGt]ѶK(bYPF*eh2m]kfn~ xp1lذ/BTmzuT%jqn Dt]~Z]]4 6O"DK4W\(ڿP at(ɺ"ٲOT6sۛl['K>OI  ٳgz:uEsm W磝SВ6”edV6bfŲ:3emU}-fltwYv{߾}~ zķ%AIՂΤ0~ :YwE:Ūn$DXi<;C(k `u@ M`*я$;;[_򖂰(C;w8vUUs;iN =ZcYfQLȃKZI׍Awj{G=;C(Rm[*bi7UftDy9Rv͖/ѿgUUm]!M:Y"DJAW"gŮj-}K4YISHjZX;ڽxf5eL tw|`VSmpt/))Gaa91Ƅk;$JXHδڕkeJZr7}ImHcEԵOjO@JA^{cPYe[%V Ƭ@E\r>_XXxA5s!Mȣ6u5Zsmi8G <{IFW~y^Jj6ѸN]#H*74S=S9ΜnYkW>n%Lю?EeXE}@YJ]XwaוqIv NdM%|V,YL79M`?yyyqرUچ!v|b VW v!͢s΃HyRhV9hV ET̓1*BrKR̈yѓIFI6s8sEU^^&c,9wh"ܕl6MHfH ^SpڵԘ>I,u1BnSP؍<_}+[?lj@wwƴ{\E*vB~[dGn8^C+e̜Oן~Gv3Ԏ\ykwOgV?>FL8s [:ckWO>|xrrr0}t 6 >;w.>C@aa!N 6 c٨~}"//. NJ+oXn'+c}_[NN\s՚+jnT`5AghVV@1& k.x+" IY9I&M02Q"'qyWZ IDATYmkMX[x|.g@qeŭ;^pU{˨@ ?`L ۭ72JOiLK6,z+k4v( ^o:;v /^ ?~<{9xz׋/_5͛EQ0vXu]x7;\أ+V̵[tx<(**BeeH~YNQQѸ 1V&]QU5(JȈPjjizk i+<27N K2xFh-x!)l1x Ko_S`S=rx P4R]e5m F\3n=kp䓷ڵ߷}:oޮ)S;[n-7|3q޽{tRo%K?|ʹmxbFX$*aO'f322*jjjߠDQˎ=<5kYтDV~.D9Wd(u(%d1,xyeSuߟ>Dw3Փ<Yt72F^ g} plGma?xmT.E/#x|aJqP0b  9r$v܉۷GÇ۶m۲e 0bĈOPwgajeYhk:ltV(2ok<`cx`dggcÆ DLN{+7E·l疌9Aaa!l6:(JaaaKYYY?x 1sLp,|\df'$]8(cXۑh\6( uJ!}oh};#U ͐DN !hnwN_m7o>^WOęgjAmP:lb6ΣD ~ ~-؆Hv^z׼^gL ?ބ&*M_,s .c=˗cѣGrJIX4lFl!GInԣ,-d۴bâ խ͵cb)]Xc-$u]A%6Ո]{ggmk*)QLYq|4r ͆TUU! $X]]}ۑ#G^Պm!m hB*bUD3ι*k=~9ꕦmD W6ACQj;hD@웫ě z q [FԎV>mUTlOV^W3eǏw?P^cE|I 1G@^gg`MJmN]>W!8zt ]K@ܻ X(.n@̅b_ܭ5)7(G4$I.ffX! D._Jt k*AtjG^HԖxPUUcOEGnVV֟Tjc!Ƙ"o%e3z"r^}u `1UU3R6pj<%3-PD[0^PƩCŅ J#<ڈ!쑑|D8IlT-<m[a'ڠ#hwOKGZF1gn:Fy<#,;iD3,ͬW"V{])&BO2!f<7sb&!ErȦp'_|7tsۈSr7+)V DpZ֎ܭ;:[ vQXXhzn1~vCʹHuȊ k>ݿo#T(ֻ.5q.ӾDfTEcr6HPNjOgtytWX1hHurL f+r &$M~ϔUܑSf< ~;j RTVVT@-Mކms`9=^*k+/9Pt>U][q&\Je+׽!rjvע%%W,ڼ8ɬyխXV{g͚I&~yΜ9?񏣖4aJ]it p/FCCjjjL`:p ,.#wUUlr ؈<6Mu^eM E?ܯ۩jNUUO  B&O>w%v)뤹D~qPԉѓ&M?o+ޛ2e b~ɓ'U"B|k>zYT/G%VBSojjz277K*Eĵp(EJc<> +Rjmq4/wTU=)sܮ([3vE]hpjc%'NrK1bYyy9 ~X/BXbu]>|88 1Ūtɖ3{ͬl>tVA ) z0vÇG RZoz頶Ml n"b3GAI זՉz6N$1cG4EPHyɸ!\vBLNTAm~ c"2 #aOa&سg믿{?яke<&XӦMC^^L͛7֭[y[oC=ҥK~}v̟?_o;#Zӧ#337pm/1n8mBw9`0+YmIgd{UWW~@꜊= /9ʦ 'mr-|hR=P8犼15.<bCCEm4 z_vx~B.(?lv ~ve+** E<=#ʹ%]m鎐G]]]JsΧnٲeSr?6QvGyXrBЃs<(jgx9͉ $OȻn<j/}k^"EzɌMs8{Si#6[ ]-9q[іYݺuZvݺu0a:,lٲ1 6 999XbE̶ī6m~cϞ=IZ>@+{me|(WWW6kijjY\\|ի7%ci)J5W>޷1\D®BP&YH/xDV^ *Z;xTUmR%@Ѕ<5}*,tamAa =Z-O@V pإ uSmQ4#A?=.SO?W]u^|۷/~iTVV駟e]zĉQUUsƬ>Cs=oзo_<34Ƨ~^x!x ,_GAaa!oSw2$ۖ`'hhhHcظիWu6b]#:W^GpaURԻ-QO"Ւ F<E);cnMcM~ڎeD lMA0 wz(` >P"TXm6xs"|J@~ ;IۡAN>qem'$w\.\veZ\TVV 'Nį~+\wu-nzMQ祗^ ۍ}!'''L=9 &`̙袋PZZj|btM kX̬`oʺrݺu|FAcd>8$S6uTXՖt$,jhhHyeKvv6lFa-y4\º=Z"kDB#/yiqys%v:9#uιK4ܒ1o#t'_ HfduGcccJxǡCv`CS59ZIBmIX (K@o')Z۴^ QER$*)D~vh]ڒ2t+z'1vt麿nk)~Ư`0xXL& 60z"bGleQ)Ҭ4᜷HKmDTg,ȐX.ui9CXƣ֞&.I)YЙOS"W;lZž\򼈈쎉k9 1EċJI O>a7cv|RVj-Ze[hmz(--5T鴤]+T[[kܽ{ePwB y2D\$:"I*ҤDZfy6Ƙs6EX)&OQ'O;#n)Z-VvSNū?˗/ǨQ &VZiӦ%7oz)X\W\*L:5Kӝ5~JpqV&WTTLPMжy4lZ>F\MZ8҄' huH{M9{\1rq[ж\"܁=; 6m |[맀D݆}LS~ՊI{*w9` 0a~iC[)L -˼xף ͢}7*))i71slcJ$sH{UU])&guuԺsk -Ӛ8DV2|l/N!Dk]fڦE؋4E1SZ&K< "h\X~fKy%48qoж<\]2G3 "*vZ|ט4iz̙3;}1p@#33ǎ޽{`YF Ю?5ku73l0ފ1cƠO>hjj͛_ +郼<૯SO=۷;΄ 0a9s0g׿1b^6333fѣQXX={^êU2oqg9998vv؁GyG:++ ӧO\sݸcg]vehhh@QQf͚ƍ^XH޽1sLqX~}>wy'sM6G ۠箻!C}aL<@ -{,D}}=ZZZ :g1#F2o޼#"AS*•m҄IxZs&\x|NE,SЖ4=HR(MC;yo]x:6Lu.[Լ%-<")OVL1n8oE]_ꫯ~II JJJpyGų> ꪫb{rFq:Xp!xPRR:1ddddPTTaÆa(//7՟xQVV7tP :}SO=kþWZZR 2^{-ZZZ |۝0)_|Kfa…:9&Mۼ[n;w.doW.77G(ŋp80qD7|_=ߏ^z ~YYY,pK>6)*_7yMh$k"fpm9 ׇkIĎh'Xiv=Є4 #=6Lk[;$"KdE^ o,6܈3͉$QD:Bv;/qogU\?8z7ݻ7{z(O{{лwo̘1]Dz뭺\/FCCNg;@cc#88qw.Ë/Vo] `1c n +V}Vs='b(,,ĥ^~;;`@nnnlٲN\L6 [l-ٳg[n#<n8NL<,\W+뭷bʔ)555~ .^v׮]xM4SٳMMM]1?~|1c&yMLY"ެd+"錰[LSb&"tiޤ K##$Z$3*+" Zz]-/ $®;294PBEIK'~-]D4' ʕ+uK7{(..n߿~.FjWog}992^tEG{źuc]8l3f ~aaҊ؈Ç~ю9k<Xf x ->|򵵵ػw/|I=z诿1x`ٳW6|dZlݺUoͨǂ!C`ؿCb׮]E~~>=gyds'{*=WN^paC,T%q1p-<(J1`XP+ӡF6B0UҖ҉)&¢4|}6pk 5#j3m}?ԼGpf hя Ktɟ}ٳ'\KEUW]sƬO?_w=`Y cZkq+u _ɯՅE[~3,X/BR-:yfWU;wĐ!Cbꫯinry b\s`0 6Ti5:(^ɺ횅,ź㔺;L܃z|!-H/yDqI$r&m )\ -3|-eN60sQup˓"cYD]7`xYNQQ^^N; yn_|.</^*Dvv:t6/>|pR¶w^p 1b&OQFa̙8r{、ȑ#0`@XOqM:9ɱc cELxxUUߟ;=zrJq26Od1$3Bd:4ڒ9y lOԅ-R$M)`EݤD ,=##& . ļ٣ с_xwq9^zᬳ Gرc1k,?<r3gG^zl@YYf̘o߾[Bnl@-GǼ зo_~ = "Z|Ѹ;cnذ!6i$9xWO?6l@nn. CL8~:^z饘b L6 xG{Qϙ3_|1JKK1ydLvi" XHol-`0p8RpY~${Dȍ>L떏Ԓ/Q^^χ}{_~voU0o޼vm۷V [ѣk׮mqwq@_}233܌zQTT`0Z:t7nBQ\~(--E]]]iL4 3gĨQPPP{7 d'"۷JXlB<߯7ވǏ׭G}T_G… QZZGXt)jjjӟӦMØ1cPRRFܹ7n jp 1c.B;vH@3Q:nG-X4f̘|δl"JW5#^ZtKԞŢq.l% ZҊfk6-t!Y nx>חkVG_\S.=hY1"үѓ&pe":A%uBq )+_~eO2TmUw*cEXM̖Mlmm>bFsrr9{rWӎAME2͚5ͣX"a~cKPx2>WuV_;[;kGx@\ YuE6,UXRUUU%-,ԛ%/~Zz{Odt5 2|G-[oYjhh0$茱P(5k~Hhy,e*Yj²M[s7Kd0aƥ~Σ(9>U5OF0BY X^εH;Ю:P,031:8 V5jgٞV]]uvTUEccᠸhugffBUw֬Ysduq%C{g-8J)mB[@G['ܢ <%/R)+qc KqږNUU]9.}&;]#j}0hk; fa'juqFDqsu{WtZіvq'S&]hp[U6]8~xJ233չsN2PNѢ?P8HOLKVV8FXZeےL%snDn[RWL nGmm~r ljT8DmІh k0v-V PQQ}>_yy܉)A񉴴r8]AH@V53ag-~E d"*ޖJ>&\N[9a75#V xX!TjTO>~G(By晿\tiA_КTcКFɝPEl3j 1YxD@Mo)PڒiMvnזpdjvUH-KN`._uўiNUc?LP;ò'F,t5Meёl@$e ?`0hzTjkkr-3>^G&JgMCm1Z?ͽTU"?l(lA6#˿B b+HgaC aK;CDQ qpƠ@PrRWlUvj̏ƚgs}΂˽4{=ל_3OjIޘ fo n^IwQ0{USlgѫ'9ol M6czgH.\K?9*w- pmXrT o$[ׁ}0ԏ K]\\ww#%'=7g0ϝs9dNHSAК;#Pӹ܉d߯uX,\8/RBcjܡ>w"Oz@p\cUUHӴ5 >vNfK$9V~(l{lGuH@ wܲ+ަ!++?b79975'g7B𖒎aMd8~|@fh A@ϝyι}xs#ԄCv8Kr6%:CǽӣB$i JX.!ѯnn|8rUExHqYkw _b[ 6=M0ck_W:G=HBԷaRs z:mۭ7Iqږ?^{./[Z?9ցNebw3"_ayUN> &?:UCBpR=;#DF=؏U>6}pufoC^[БeO?2:XhfWǽ".R7k{?~2rW{'800-$/.R-L*Db2$S;%nNk9WQ;xr/ޒL|hЅa9 ,̉ў` _F7P_5;?H`:mY}fTC?JNeY?_ ȸqh1hQcP|;wu/s9xh>w5QZ e9!,R :1,D%Kh:JxKl.I_ޗt]؜9 34e۲gh +D"|?f4  uqhg\$:o֕$ںmˎpVʒ^}_3I՟c (,a$2$INg~oOd V!b UhJnr p3@=5`K?TkPO;'G`)IlI$@>czrZAѡi w|˾e`%9GhA=ezܪH%Qx4 C\O- !\flFxp)Q9:{ky<2e:1d0PgV@5Yô ح-vShyD1Eg{2I2ulݷ,~._s;Ae]7ziQ'-IrZȇ{-J;/|&QH $MBc5َumڑU Sx9PU-Ļ+ӡڇn "˲{3}EKAvcg$(SԌtz=E'Swx^ ,kd)-qE [mtDI:*}lHX:'@̡mu[]\Ys߱m IDAT f㛮I$(&[a 8nb?zjpZ(^o]?Lĭfte0nГ$)˲ſW_AOx\3k+qJsnv̜Ab6>h|>r"ʮ=w$Xkj"h_M [B:0n O{?R"Cʱ]eO.//_&IracF6ڳ4FVFMT>D٩8]rVvʩ]1ы5`.jsA8b,z UW3Vl?5Ld>ѣ峴M}/B?bb.3Ȣz<`#Bs#B4ʤp>\-Fw`!eV]k }^[L4f7Nӵ|$\̾/T2l N.gUX)ٟkPafCߐ7Sg>tq ڪp-Cp~}8Mӯu(_礵,B|텘?]ɺF]wWdcdiorNd'k2vJjc0U&ADƸ^ dYRcek Rh'Ƶ]첨)>=ccz(_`՘T~]j3bu$eY~BxesPa(G;{QaFpʎB:NgIv.˲ !__8tffD֩}& X'*Ȝ,dwq:$#,*uQ8 p{!}s! YR09@s2T^^ с.Zl?ԇ!0>DS࣭h64zތkcPC.:´}[;:#f;WeiafNxeݘz j$ )qD7DGCmgh֣oGS@&Mg'w34o:"Z/N&/E? jwI1ȼ !,D/A?\C0k\@,!I-BGvUErr,k=\fXa=9DzRO^6D(N:Ս"Iri(> M6]M=BA5e+q (n3M$53>m3L&n8}YIө<4$Ir5'$ X Df=Ap;!^1ЩNloF.#Vg:aٖrm=?]E!W)\!}}h\e([ EcUϲ YZ͊@ft:t:xff=,cVg9wa ̝SWRؑ;aJ#~]~?еs^dϷ$*TT}XK(sf ͽ%tuؐxƽ~F{u4=9^ F+ hogI%*,m)0XYO$x/V(m*ϵa6,3A=EmuCQ(<.n2Gu*)Hٶpm?6`7gI?150/ zT+ZE"N##=2{(u2V5g۳Y`ڭo:;cBRbE>=k 3sޯ9J=Ii,teʙc778tpvvV]oo\MogYf6 7LSx$ĨM4{/EN@x(Yf;rQWƇb[PQK:SHD Jg51}W͕Ł,znm}'Ĩf=x,-Pv˖EP.N]d2I cILyHȍ!i9sA`#:`:pK(OQw,$Y/4Mԛ!\[xf2 );ҲJMj !d!ǖLP _8q #13f'.}k .y~y72|,100n?yuu2g=WDXcgYwrBd!18M4^ܦ8#ר{hdԀK^lGm+JI\jĤg櫴ËpK=4BIi[7YW ?l}ytay$EK-%_+9)̡{ V :v.6=tgf ƙ}mbPaNR>)%#!4:*0DǗIT=f"\8!3;D 0j_$\QAeQ HpcvtVP;!~2F /ZPkY,";חl{?KӴMv"Fx\ZM6Ba[!gG*Q+.*L6A"0lRK%N^JsyC&B])+'mdٍd<ٔdqJNu^jD>Ё*+ ͩjhׂO\JÃl{>qrrb`QH0Js-m Y,UUUF|;|Ǐ8=q]zͧXo$֦F  VhY#1Nl(0 0 Vuu:v~xW^̳g]cdTÂY]Gdxe햣9RNSK[B$Gi{Ն`|Η8#o*Ѭ|F $Sy^VjHʔ#-۲|jȆ]VQ"Ep35;ińR9 sԻ=܀s`;)HYA-:Mi}3Gwrrz0wRmF\#f ReK} B 3}+FA}!w3 LHj˖xH,1WYW:_Ͼ{ 0ّ]lx\`=woa&+zw$!jmv5X}&<d{AG3Rw}&*Xzι\~6ƅ;^dQ ٚuf{DzywQZ 46"26D4f ˫eM;s}}T, XeXpLvZhZc1]dٗk]& n^w?73xAZEDc8`;ACݮs8c@#{‚Df㹻iԃ9{ :.˲]1pөWM۬DZMolť\ϰzr{&8NM7"ְ>⦴ޡ5F#4 [ds'F~X'4J481fιcJ8X_,H|-[߉}l.>$58wdƗ:##J:<Էg̷n/iE+*&V5aem>esԥ'v ;jx\ECT.vBy<”RN6XP~.2x2u'šd*Y>\m—܃3tD 6ς'(aFȾQBx ]>ouks YV SwkMtw$S"M64M__mԭԜgQ[&kݱ`jf|ݺ vl؏ XH.ɠUYjMlc?v=r=}5i P>òAfYvT+Wɶ3QC(S&Qn޲PHqi$IV-hlS:{f袰|ii߻{Qٯگ.p&HGtZM=5Wa6vL5p2bc[č451j;&[܏~P{ߒ0 kS+Xkq #Aj}?*mϷ&IUqZǒ{fa%#q$ RhHsZXݦV#k+qs.6̑PD ./:e*,vW'p$umq>5M- iVB*gT}_ȏH{zǞXFU:S=aB484~mjFdz,CY u9f5fO#'ӤQC^7CƳ !fur\}2: >Z\VWWf:ygmx9t`1,{_1~3|oDz 8:C>gh~%&CT `0H:c{n"QIqOL^feArus'OV:8# e IDAT-sE(V-lt^QWgQɲ?_4[]b2IstZz.=*vq9t,t Lk,wVүVZ\rVYӟP'|=?OQQ cE}e\^Mд}dbΜs/zGkxÅPnYxM2Q"!ġsG@g(L%TJ^2njsݻRrUio\̜L'"]sgc9dvS,r.zn!y0(AwXZ$M0S89;;ܲiUVIWYw]|O=b]4ř83BGj3XCߥU*ZLmv%E.\u:u|L,ܗ߱f቉hhp">m|z\YBۨ.t=>3BWA)-xqjc)S*Έ ^GdK p} ޗPK*u/P^,Y4\=CSYe6't5XFLdOqW> ?X V <1|{Bx+]XK (-`$.8欑F^-?ovK FoʻpXr5%xYJ9* ݚ@!ЭyI.wy:sBMFm7'y9lVK ' ,hŹ%} =H,(-k:[,E]0(#1(IkWԍ^Pi8 Aw}h.7lp@!dc[JF^8NΎ9*J]re])9'tg7DSF >rW"PW&rm|us©kDv=%O Y䎃,0'±3#W8Ng LA/!*h]k~}-_'&![XSD>u>CMV/n%49;BA*n:H$cɂx ^,w;4l}x%>:YKE^\-9x&TsuVz0Ēt|64 ո4=lqc-[Q R:9rCNBpvQM`?v5HtE~~=7opΦqQNӕDAkCɰkچDV(JB\$}䜛*irWo:Ԝ:6e5-֔Ǧ}Ih$ mV78lģ':Jcb=Ep,Hl۠CĴ6dsgdM&@2i=Pjߖ}n\9q, $H_oĩ= 8/,yd=-׵,aDH[T3Bq.$9a(hɩcXǏohzLLv/#`=2`N ߣprԤ.?y5(M2I{O$#gV6` %zM> ‚* no>\/mI+`z(;$8(/ɩB@'CZ*IUm}n#yPvA!$6n˦HId5-Ĝ1 _ ’ιE,*4{w{-zuz aY a;ȨQsggg9 {Ѥ.TUE//G_׿ K8ngQB7NN\wty*^lB@S>*/JŤ\ |"JeΖL/?tٗFzmg)j2*4,kdVfgV,?n?yXp" J4=֌-%j⧖TyQ.CQf]9G]o-Y] b|׉ݬsrl8{Bsh=u m ZrK5 ub˳I$M۲ٴKԵh%g9VDx9rڮ{{X6dـ|l*vN.oͦiX,teY,0WnC~XW?=9 \g/DQײӸzmm$lQ 9\ܿ:>q=s=*&D~j.b;m *=[9 OaT*zQ|(>63>~+{&)͞}a K76nنm&KdUݦΔ5ike*RXBXͶ$ڧv/d1WD|xA@eۇgjlp;@mC n}%IRZMIm2.[M&\3o{9euhxҶn6)uLAq;5Ūc N"ظ-@vkCU“'Olnzt:+1:zfeYb>#ϗl6?˿E)_nZ_"#S;&ܓ̀e7C J7 [)PkNr1[iUЕ8#䏜گZ9$|>sd52yev8==_?~~闾+q֒GE烁BM^;GEM5_m{&4Z[joh)o윛(š gj榨%(n +{N+s_aX!ذpr5>`=\͡϶XT)^L6&ޝGbu[]on/k{pqD9F8X7,#ADyg41*+m 2aiQ/ݮR7UZ1Jks{$Ic!Sܩ/i<af-L;3gYbxJcn(8o R2ӹwm jBL;CGhLŹWخa_gD"H_RfkӇޱy&%I*wdDo[lAc s݉wb~W<*h&ѨC#lj#s`G&649r_Iݏ`p!E/MS"=y^A˶;-g~D{{!g519182I^gァ %’hb#8 = X?ds~Zps49K:YU'  ~b񌁠=c=TE>|,SlkOcA.Pip,C*:T!T1GFH$qrԊT'59wՠ'`F"43[=ESfhf 'dRF1j- Mg2Fd= et:nKIGj}3x9~$Ѻ Ug Ðva6`pN'lxz~~KX,}ֵ'Ha,)-85ܕ(C$BH3vJvhɄflI6.7K2}U$dt( PIJۑl9jA&D/̓-k=P CQD6ٯ Λ B.D<IĠK-:zKEd3['wX(7S= 5@ jdBI=|)~-'%!5Ԡ'ً4[/ڜ7hGjoA C+EվussCS/u@urrl%+$c12?GBt i(vܝpC:uls[]0L 9t>ѣGq3pι|~OO&}]5jaoh-{ vIN[JbAl uP xHsYv!7''m(U ]Sgy *hb 4}1Q$G妀2If%5ֽĞߵ✂k tRM~;5v36pΑ$z.uϳ&&tN@pze-,"~jLx@bI-[m2 ڗWh!h1ʈ^o[ueeѪ1K2q~;dGc^IfAo[cWὫxzz!97~?=Lm@hKl&V['W1 6y)֓fXBdVδb$t=*sev-It4&QtD/PٳgNxC ]lCԨ;\ϮٵEqˮc qJ4ӣWU5MT T] 4uRϱJ(F-}*ԞBwT/R'UXo~luyn/\^^=lKZִ:ydJ?JBԹWvU'1AC}q,nk$%'JrvrhJf6eg*dx/oya7G?w YmGLN^ו*λМMF3lb3~"mfi$XD_SEjbdž,gd§RWڴ1-e1Ջ:XR܎H8ԫaL8(ze!jb " +I ,[N[ŮU'?`43|w>}/:Oi8˲!˗7n0CR##g/?G$=C49d)%ж9y u̢'u,kނ[̖S<r m\9/%Or`؂=P4:~`Dc/Dpo(Եe۝n }͹طHK>>z'khх0tsYb)ŒCҲIu3kmnYיlHU"Iqi 'CT$'ic9^QdRJNShip,fmC$X:S&@Uf_b O,t>eXoͻz;|z=f4#[jQCF`1GAr*N01++vD6 !>%d)|'ȟ"Ȭ{r{aMn%Ⱥ^{>R7ReR&H*+S ۉ\' hnk=)z"L?4ND<|X-'+5[bQM rS?3V#IKl Jvy *Ze2V zͶZ *-:bق9ds.7̈́xbFU<!zm*~WCYka& MQNc0Jb%!9wu_s ޴YF}%9Gjߦ>|~ђTl[eD"=1q{.Ͻ<)Sit;$#XYp_bm_P̈fn׈CxG˶÷,0b092)p=Rzk%_uoHz]AL=9<X42ƅ ԒxadžCǒ3Asx,:CIJ QrFS}Fˆ v9ĩ3(e#&kx8 nZSAXn;f(.1 K;6&sf%ӕ-z%9Tj+Iς0s.Nsc71!`_ȿL=wt4Pk&#yQrSOGޏB;߳-Z=0jzĥc[6h<]= cL:B=ګOnIv!BoyqBژ|;'1IkD;Ke-L.7 eZ{1R Bçf/VtI6`0̲+$9-C8ԩ GS#Q4`(qzhOMB/$$my;RaOLJu͕!_%I9vg),_Ίfj a(PuLTfN,y꽿4?WUU}81?l,n]4g~RM\\!'Y$˲c¥v)nMɢW]K`ڷREZg4pF35%H4s>RhzZVCgd^2gڪ梲pSpӳStD狤}HQY|*$;k~ڜBLnSmf"x\T L{1*cp6A=tb~kY3NZ.0㛈ֶ%S#A9 Kb`O{FRdB WC@/*D](0 !p-> >d2q܂>De{騥{ g[Bw:/ĺhrPvΝs<Ϗm3s' "YBVrKC.$DDd90Y'DҔceډAdy҂Ds왝Ԃ.DɁ$->I|a`Z[6S4ΘL֬7KHF 8TѦHHݮ:d4@n <$H4&fx.3{!ϢH &nۓT g0&[bY kM}00hc7a|{zsKWF>t yaT_JA:r΋U,[z)R {Y&{NǛ+,h@=ps.<:ctumԃOƒ*-ZR1TSeawѻ}zk|lDZ/#NbkLhM+9xp{\yk$z9Ht-ۣY?"eDd_Ej[C3NI_,s䃏ؽ&z*jN0۞Rq}9k,:d6KH}7Zt8`Mk qt^l.CBi[Ԡ׎Jdye{, @1K1؊ZAιcFEth ,d24IdIm`A.1jG7ϖd IDAT$jkJ0m-8ʅ"e|d7;m& õt:f3Y5R< ^]ch9T"ID~?KGɺZWS2HN[y9ܚho3R9F ~5X°&[sNDA/qSa%LF!5́XpLw|Ι>1P>0HX9CaWk)ʀ+e+P{Z'|(K~G4*pf8|jfWaX@S11n,`)ׁ$T1'mXP{"#;^ YI,,Y lov?#Sw,"}9m('_"ε/ÓK=e[-"ܕ2/8.ϺL4c-Zz-}$.Q뫤% `jK&WTʸ\9eq蜱5Gkc862 ({9i˶΍s΅,4MpZNܐ.B+2a x֦r+s@E/E\;{u9|l<$c_+TnjoyRҠu%¾I_.9p}zf>iajS@SsQ4P'i\yQD8l~X M v RerVh?"S"6&*$887UD=C$};B=4ѹNvUUϒ$98\{KsoVk3ezTKCg{$)sScfLDb4' s]W{e]ߋZ n8sWIZ HdwOP n},83_9A03쯎طּ9ZˢCDݪ$k*$%ڡm8|T|6ujZlS_ޫ^J2һ\K m %k2WIE*v> ?!hz4=xE0f6StmLC+d)esK4kܸn:xun2p+IvW)cȦk28D8/$MυC#&2iaInmUO Ԝ|AGozTZB9[nt"ĒzO|p})h[ݰ=GhJ-o ׀D6%AYܕ>C͢ݙ<6~u\NjEUSÊPen1g~$NgXv]s0{5,|)PGs]%~!}~N ة?TN`.(uDjI@UUU4Mη˘mϵ:5@#ޢNaFc61ńoFtrWH>gRι29Qi1lEdN6ߜpNe9+)Y*n+š Oa3[ [2K]#^Q;E-euY۵5sOs϶wdM>$ls=!US",y"z?Lj v`Z.BqcZHJ']2,Bib52c-m/^ܗuam=O2ч?AP ˼Dhh1J\ %~$I iM~$aJl[򰺷cb>_ vfHp#C=|&"#9o^xqp<;; ښ:"Z4kaƛvhy\^^^?ײW-0ġ+}~-X{26?s, NWh[\e}a΂`Y;2Iu9J(n%p njWOeRP_{q@$=Esΰw]IMˡH~{6$A1\!ڌXk$^eϖ4`YO5S;O'!i$n_YOgJ qy48t"4qt>h4Թ%KN,sXfAg7+->ېq֚39зe?[ 8 ZJtٲ~61@s>56aBxd3؟cv1~zHȮ2u-Zf]aI&͢e56Q.:qM}|{jUig>;-QHE>GW6!BzR)Kmk1뚃K^n97PxwCk !e}bЭiЦBm.N^mQz3ٻ+ yf1+ ('Խ8om]oqiܗdE%%IZe`ԢZgvbI&(*KfU1}z@:TOuͶ$7G 8 Er>}X^X9ulM.e)gUV}~Z-u_28zl= !vY~[8Dl!#.L-C!oNb&, 2(S !Q 16>S|alES> j,v%Cy~l+;G-Î]si'lj562t}nׄ nuKfƑZʾÇ^#[_zǸeEtIJ^ͩag~_kό3GfOcPMMnV!#%)̭"^2e52X ԃ8dC3}nN^7'u}x5vz['m{jw]͸*ԣF0v^PZYA`ݾc!.Jz(9_v=}F332C އ$,InsV)Vԕ=y|ЗR},c|"Fgt[aOk96 0U<')wBPKR{!HK]I>dZʋι}cKK[!Ah9ƲZJ w'd&؜ 7%$l7wC'< Zd: un*CY72b*ݘ٦^{-kPt3u 3.fDvmB)]Xu $In v&Jٿs q?89H@^ݢ>T+!C{)pDOdX R'P|~j{ 9}|n1fhNr<P(, ~G&6lm F_q] T4}F(iy,#dKIEqQ\DͰ8f>G6xVY0:6q 5Pg48lq:0*GA{2la\qSۡ-rgCK͉ l~NC'z>g$g'("R]#=ޗ mdg0:²=m (lg$nlJhS5G7 F[>܇#h>HwfE]dǶwǨzM>f촩Mx-lMGz1CdLxN,`;D. GB.2xu䵔XoI!GXv<lۙK[,qZXY CsHd+b]ؙR{Uvӎ B= S,3| .+\Xy 2Tєa)ۑ$t1+ `=;} s}7IsAZ9[F;Đ&7\DI"]Qc4M7ٞ L/?Q *Fv$a<)h5] &NK=#Dk$I>:՜N26'׷VwJ+p4&>M)Bf !ι}ʙѱ`#JDŦT ں~b2qg<3f`Jcf2Bxjmlĭl!ΔgoQ 6*'syKxE C'h(6= e*M 5SA3,; (}>5X~l} r\B(=֣9< ;ׯc}s (bv CWQQ># &І$E+|B8uF=@ʻFЌr:=Xd̠XT}KyρE#otX2f(KqgιƮQ8UeN+/ͅˌn)Ns\Ҍ!(nw>QK>r)tҁdž#}#yslERg%sjG( S"!'3DB83X+{C;kjvV, Z6["Ƿ,ݎeOOsײT]W]CKP2qX+`C6VHj;غ]aɌ5KbX/( tBSi`{Z>SBu GhP6rܨ;InCG ',/0= MG { -^Qy&G :o N,VH6CMF_c氬pMmN : =3q&?2hSnh0n3AM5dH]GD?z\dѳ.ܾsޏLc}h""2sqԲg_~÷Nd,XSC~KB(=ݨwyb&\rB ׂù~fEQ̳,Kl: 8aLu"8^rkD^59$꼭%gNfd1rLޭt:sC`PLm+/E樔lB{Ok>_}(P1ʃrTr3•}NlaMط yoZԢQ_UU$W쳽6)2j_.j8MIMgXD{{hA Qms@́Ľm5Bfuɧ١Qp <1hH- ˚}N+ *, {֟3l[w(\gl@v8vlx,0A;c4R%aqЏbsSmAD'g!=L;ĹS9/BsrB0"f]KIm߆S44N4:z xgLIOP8Ϧ]gYdUUU=KӴ2(Ʉ >Rߵ )sΥóuP{{nNءf׫[C8i.nv[w&ohy s3seƻ >B# tkS*c1X2gUَ5NSbwRr912k7rv :a3@aL'ynt` >ι J}n9 Edg;Ϥ&d8}bfZִ)TҚV" ^YMB$$9ć "aL9e看$ҖP =ԣ(¤&JKmGr(a}|JY*Hwc p^"Ƭ]f )/m} g@c?g'jbyks۔Q-o'V1I3-u~^vd$@ A 6fΓW[0͵-.zܶA/t3@DkqdP_W}Ζ61t' 1R`PxUYNdrp2GVLC53駟&_/c}G=[|NpOd@Fk:ܚ-8qDc3د{:²,QZ#zS±aZYMxb/k Q#^k4/qkSp;88xtg:NCM&9})PFhx̌{8=KpmyFP`nfho>UP*SguK>6xG/42>uJѲ:GUu"N>cl"˞2c.y,Ξ- u0_YA($ +ǩmN8Ee}E7h>򖊾l4;RQu5at3 4N4ǖiT7Gܖ b@X2H mHĬOrNϿ$G8]TFiNk#LEc dtˇB)%<_F\m"!}&" O%x%gyY>NBYN6)aŸ0Fd 5fW[¨lW[trÿh9LU:BہS ųb:߰.uYG@ބ@ ww7Vv;Yu(jK(Á82i !Iv,g d[~X}l!-zC ;giO(98c{}|N'&,Q/ڕ56iRVk3q"~R YTEDi'K^fiƔ#tfAs|3N':@#n$z +[DV컚$[MUf.?Yީq}eN7xgR/V,ª&G2~F6WV`EmTk2#H l_}]Heɡ>-v8KԢן<0FfbVb8> %tPl#L|Wu|V f.*>"M-{][ޞ@G r3$F~v =<2󛛛S3G^[NjMgsĉ%qBx#+_&jFVRww (<6 z^9r؃޴53)"tDh-FV6`ol2G9 CN8_ W2#&ٰU}9X1:ΖUd&#u.-_\(PF R Mvњ59AVM] PSd`2{+dO-%޳\5od9$Luϐ5/-n!q};,ذOǿG`,zfv !k ؂Z'Pַlj^$s$Ɩ{}3Hd\ן09&f ܂Oc,˲sCesq+l bq $9G.L4\u^hg\k{?Tz)eLuM4A:Ɖ,K\c㙕 J>`ٜc߃wЄz{Ac;0A+tmb>u$L6 :}h/_z^cm8c@fy=,EFB2VKڊ^57DJ J,3[=%|H,fCx51 JXQ {f;Z=uEMpz&ź3@O-L`[`X?p0@nɊu.֌}ڏ)^u8Nt`ݗ`aL _qSؐ͜-$I9JC<[4EQUъAIo1춵Z.hpo_RD2md4_< ?CFhy`UW͐` 8}kp]VɄ# p5;+}@TJ] :{H(!8cc(N=2g"u5U/K\g  Ɋ{eL,]%)k(@fMůoKfFsC +"hxN`86fι& H;1(np)\CJ4' \Y.f3DW$0e@t`MY{99ZsYe(oZD keҭqeHD9g05v;2s:'lmX]`X$Kj{UU6JֈNB]$<|_h77؟w^ma`UO&Fee-cv`EО”cAsg{Sic$7eqpGX۲R0޲fUӐSyj"g[jeDhdALd-H#z\̪uMԇ\ҁH I4OCk]c0֬&1p CT7zox%F6Wo}NMRN-k$ne\gr5~TW8yRAV=¶V8=S-WY1xv52~sta$I`W3W m gz'<6] =b({W[R!4Sd ߁y –=xn`暉QQ fxAEr0#08ddFnjr*0|8'eD..՟qf`)x:{r&YXlZ엄8 )+#zI&Uc{ {cE&~ ]!1񁣏fdE &$4M_iӨ`LJ^ !J$ݩZ-Ohtی7@'6gOk Y-'We+2߭ [YCqF#Ef |MԖH',MerEF "&$RhSWnoI^ZP/s87C\Wʲ̢("З-PihZ!OGs`H朻r~B[kqnf>$/_%iNJY EA)!'Њ~c;_*|iE=yWmMsVɰz%SW[:DE]ZU:ìwb}HPk,{ܖu%&+z%sklwG̾K>PA!v* " Ć4. Dgz]4E'琍y@S@5*3h)XU a-`VRh1?d4\bCg][kSV%? M;F*;Xr 2X={} "u Y:z~w=/hYk LJ>g@XULDzjyHPMD2HT])a}XmcY# -IkH+9׻$*z1kZ&@ -o=ީ L~7 tfR[&Qam.C*40O^Wl9#>?ژq%䵕Hm +43A@{XKP}MY@&P}P"5m_*ѯ8V c@*T(>TgTr_!Ѣ.reYp K,񶬤UJцEL>N0T|eC1WGLHxp֐AwM]y ~#f9-w5;y3[-|@ն6%~'62ؙԼ\/Сsw'(cT},%V̊Q̾ ~bIlު735[n!Ӗu8]Ǻ$V-JS*%Q;Юt؂#JKĊV H(㠌'v򌜯ΘF%d7]N#{ȗ ѩ.Vtt|=:NF5?XJUMM. ȃG'!)wL۹ρPgY֊hR}Yv$oLJ-]_ Z1{3{e-2'۷tB1e^G="r;::3m7tT$1ZG4-=[TAI0`/ΨjABUf` k\w iXj>|t Zx/<-jж:2r [oͩH 3B`X6 $K{xFNik [ THKV<$H]նB|éY% )R&=riuT' d$Μ 9iv4'`<8%urd0,ۀT3c+CydSaIkS-E:џs}u*;ZCIHR+~nrrb, #J"}bZ8Ů>'1:PemH8pt4{fHTxsw\ t/M,WX'[v 3WDb 0{ ̹nV?gO7ݢFj,USOÊq$Av/ՄPǚ%T,ز:;Lrr+-XZ-R߶hv;= p&9>ϞCnAqGP6 "Q'9]ZA%Q>V9S`0,Țdߑ-AulpЖaYN_֯qf]~o%9r+( N0elqЌL5)q뜦6&l}E Ɲ{[`g~Z(I1Y(LGHږk|#kK0J~3%ixEyC>kOnZn͹gob,os[D>0s[+{4\<`"1XCЋŗyRM5kWp6܊eAbfpwtCz#!S_f֧.{9YHyGW8R=EܐRJ(Z1^wNZRh{{{نO=N$MKB7j3iaͧkur!8ιVt8D%N1`W;7-s)CQdٚ I)hSxe{oy+CvH[jTF9إfVhGkchWA#[=bA2;5V%(Çm=?&>,u|F},ȉ~Ur|||)/ʊ/l93k=CaСC1x<='1rƏR'ύ{ܷ$JWRr*EGT!! Xlg^6db-+KCY:x'f'I{j걙^ )P:Tȁ 1&VS>E$\a $UD> K yu= _V( )dB[ VU.-HT`=rk N .@n%Z37 Q& 7>lS. \{IX<0l9N*8UZAKg j;.'K%,'?̪7;AHײ k"NX'Nj3+)LarfMg+rU%'&5ԲC0.HZ|Nuux*Y꙳/`:v ԏ'uؚs<ּ(&x(Fǐ7 Ii&}sG5 L4HbKJW([EEMf٤h [z}r.Gipt"gc['&Q~$\~vf\ Nϩ: ,} <;??V̎1sg>g,e"B3+ND. a؁\^܄#|op;o~LoӒ d%s{wGƾ\۪:M4GS炮TlE$HFf$,;BĮRTܑ^Gꂄ*.LFbI0UE$RV'ł<(Pԩ7]SЇgMsqWWW訅 v@;G:rbuAj3 {i8 4=Z{43l}d"CW9ܭ3%.ͤ:]*΅IdaU-aQIlz 9f2{#0}հĐ1")Lf@祥죎 iȪ1|8 ̊ky[E*@:V(7o_8'Ra9Ch]8N3Qo{5w[M j$;'WRa@+=}&Ա׭iKaޚUD;R[ rY4\!}oG:uVH:ͪ7KCY,~.NmY"u &e{ajm0cݬ WAִ\0'RaK[k ؈cIlY*wngyׂ>[/HJܷ\EbKS\ }{\Q:s*AlIDATG[>ш*e408E v'k7h)'~ۖ@ ds-߅:mK;*9 #cdr&nnoKg{my!nޱ 00qR+ksýs߱lֳ]Uk͂d6sYq֒̊Χc%SF2+ ,WJCհ({CUɤ1y/W-w~W9`8"#mA48i 4T$lɤkr#$KB t,'!Bҏ ԚI9K̆7Q5k ramYihaJ$$ιl칈pX\kHebS[FYG ':xPIsԤzbj0q.TChl1APtH,zBh4ztoyer!!dAH]ۗ@Ɓ?ӫ^#"-)Dͱ^.ptzh}y&E@t&Y@QGxrЯm$a\afO7r#M(㬬Lz9紖>܊ VhU$KJraYaIrL|mt5UКf”a6l;m3\geRhR]){'ιe'jrCNE@Mc:~zC<'0L3{~"d`%T3F(Gs~^zDKWH" E4 DGg2QxCmJd@,﬩ rrvvyQͼg@~Bpw$[n2)Ъ3v^2Cy #ȳX o΄OsiA&NެZag jyH^J2ٷ$|X[bpoSMP&dŪKw]*a$|@o1snQ=PG@I0e}PRXHKJ D(̊I]$xȽ9¶gEÌVʹŰ _2pbi*mUަ&gG,k6>u'6Y0ÝزtS^^V4WrS'%HDϜ=n:"Kܙ="-WKn{&silv&)S+7 uŗexMMCKH}ثS7eġU!`uajPBM&ed]cq$y3٩I=pk6 ^pci?`_K [t}T0@>SUA-4:=1%R 2!+{G%bEHOjVD~lY!3D- K)'myHgS֏B,N ^h 21y3!#N֘`*̬[ YK)@g Ba`}ppԚ֌ru{#ygEbg4DǺ0#I 1}@&%;9mynEcxpƧšH:P,YH,mUq]9S"6 ȜfS%ԝs>MӋ(X㢚[7ԄK>jWV.5y"cûMPb udL|~%:J; (2GJ0j,F ;<<̞A):qmyvB/&Ywdfe97GDv s9,҅NXzoĬ]X X.-60_nnn^0(TZmw,ec{H8Dc 6M!΅ׁݷ?Es݅cX䞜 w-+jW] hc˩@EγTeU"212Z?ι3r>y~Ž)?l9$6?'ǯ'2ڳb*[j37[3T zc Mc,DU[y1#Ctw,% IdykxϞoY#Sbg8_n,ŊYnEr4,Fdž>c\0-B:OAsXqΗe4"RT>#uZQ+?9{I@bkZgOj9۲"{VCwlHPc,`%%s:R!Woy};X1nd'}<Pi~029$rj-'n@e"q_sg_}tp_X1$Ҋk۾[+:8̢(eYv~W0ZܠOZMM3djE K2VkB%C"BRWNpn_?ilLd%APMd9 3 d OuQ[@Z~{MiY, YQz&2'7 f Rkۖ')Ý,'Ofs5zB!e 9G>_-'eP2|eYƾ8L p]*ie,rّdK~βl5إM,{ /8 BBs#YOb_Yrrb[UG>;>u:2ڊF>1痲h4~Htl{1M1-8gdןgo\O@n+,pTSa)>@֛p} Ɵ^cɊ?37}+FJ:3 fҥ]+Hs)P:%nLdӳ.ʭ,SARxﯬ` 5N:DG6(|F;IPF {l&Gڑ,,g_!É3p8/J{FHƁc^lܣG5:*^y0>FS]nѬ$gF:b.ƞv|3@©qd _{ޙ+9uq7X>a 5رu7~&r6o9tj: `Ѧ̚ J6L!Ѐv.a&遒 zvQȞq4>+{$ܴzqP ĩ렉 ӑZm+x/,IQm G깗$0T('0쿙̊-g3\KGP>SGiqmlL%C?~Ο_dC*׾$ |}5.Ak~U嵱sc1`|of`P~ĭډbc|gfߡ;p卽wjKb6[GOlL]Zfs]ܱһ;Lv e Yޕ9l_=1Nn^bx5ˉ@ ±:"QKJy}iӹ-.mpny,gQ kt02gUN:9Uaǰc}{߆am){n3&Aڋ_V^J|"x<7k~- U潿c FaYb$^*'Dlsy$7Ȉ؆7I,iRC)Q0)u3W95Y5¯p]8Cxff~/ʹ*dAp7  vY9dsLKH qK@l*w%g߿⑙~jm0^pf2qeCt}r]鷝hW i[5 R[1>\Jd3bx{Od$#}<|R #Qՠ܆C[Lffc #CDؕ6LxdY`,lu!4}+JƖ~F6mI{>v0}sr7t F/_{>9kڗ (xӔϢyc0Yl"4]ᔴU$3-!(%FfY(܍=T| KE +JZɂ3Fq%L/E+l~Y":t}˻v}BӤNQeWq_^-$/-oyr# DX0pP{N5Y_[!ߛaYG->cUZ%z$ڬr0_S;'%!t`Eι$X!iXx(3[\ W#Q01_7šmT?">6Iz+yro;q'iRQɾ 1B2u q{{;ep4sxoƿ[Ό?bݶݙY9;bV2)bɸMY˜RN>1wܳ~a 9=/߮m@YLoMFQcqp-_S1 k[-bک zRb;3U gC OћM[.xkڶ>I/)6{/[Dg tv[pgh[F |>PY1/ ͦV\8s~`KD,IVpOW0~GVu+>d=3~E7p=3g h$Ax]ryS,/acq#MF6MQVdFq)Z\df: b?EuN}PZ1%^9b2B:uYT熖C ﭘ },~eܯ*cտMiX/a +jT  )Նds+Z3&!ሂMP: c f=p 2Yb|=Ԋ_Wjb Yݕoizӊh` [+Fa\[2F'IENDB`cantata-2.2.0/mac/dmg/background.xcf000066400000000000000000006551631316350454000172770ustar00rootroot00000000000000gimp xcf file_{BBgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 1.000000) (yspacing 1.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) y0U6;%?0BeMUPasted Layer #1U     ~Lyy ,9FdXP]bqswwwy B!;%9%%$)+8+&+;+!+<+,+<+ ,+<+(++, +,355 678788 9: +-577 89: ; <=+ +-577 89: ;< +-57 7 89: ;< +-567 7 89: ;< +-466 7 89: ;< +-466 7 89: ;< +-466 7 89: ;<+ +-466 7 89: ;<+ +-45566 7 89:; +-3556 7 89:; +,34556 7 89:; +,34456 7 89:; B!;%9%%$)+8+&+;+!+<+,+<+ ,+<+(++, +,355 678788 9: +-577 89: ; <=+ +-577 89: ;< +-57 7 89: ;< +-567 7 89: ;< +-466 7 89: ;< +-466 7 89: ;< +-466 7 89: ;<+ +-466 7 89: ;<+ +-45566 7 89:; +-3556 7 89:; +,34556 7 89:; +,34456 7 89:; B!;%9%%$)+8+&+;+!+<+,+<+ ,+<+(++, +,355 678788 9: +-577 89: ; <=+ +-577 89: ;< +-57 7 89: ;< +-567 7 89: ;< +-466 7 89: ;< +-466 7 89: ;< +-466 7 89: ;<+ +-466 7 89: ;<+ +-45566 7 89:; +-3556 7 89:; +,34556 7 89:; +,34456 7 89:; -Ton7nF9); ?=>? @ ABCDCDDEED<==>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABC < =>? @ ABC;< <=>? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB ?%+?,: ; <= > ?=>? @ ABCDCDDEED<==>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABC < =>? @ ABC;< <=>? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB ?%+?,: ; <= > ?=>? @ ABCDCDDEED<==>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABCD<=>? @ ABC < =>? @ ABC;< <=>? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB ?n@ % $3 ),,+,(2*++,#0 )++,$ - ++,%&.)+ +&-(+ +$,(+ +,#*(+ +,#)(+ +,%((++&*(++' '(++( &(++)%(++(#(++(%(++,&%"(++,%2!(++,&h(++((+ +*+ +' (++*D++&(++*LC-+ +,&(++*KNA-+ +,' (++*KMN@-+ +( (++*KMMNB-*+ +((++*KMMND.*+ +)(++*KMMD.*+ +)(++*KLMMND.+ +*(++*KLLMMOC.++#(++*KLLMOC.*+ +)(++*KKLLMND/*+ +'(++*KKLMNE0*+ +,'(++*KKLME.+ +((++*JKKLMG-+ +)(++*JKKLMH,+ +)(++*JKKLMH.+ +) (++*JJKLF/+ +)% %!)++*JJKLME1+ +*++*IJJKLME0*+ +*++*IJJ KLMG1*+ +*++*HJJKLH1*+&+*HIJJKLG1*+%+*HIIJJKMG0+%+*HIIJKMG0*+#+,JIIJKMG1*+!+*3JIIJKLG3)+ +*AIIJKG4*++*9HHI IJKH2++*3FHH IJKJ0++,3>GHH IJK1++?@A@AABBAGHH IJK3++EFEFFGGHGGH IJKH4++D EFGH IJKLH4*++DD EFG H IJKLH4)+DD EFGH IJKLI6)DD EFGH IJKI6DD EFGH IJKIDD EFGH IJK D EFGH IJC D EFG H IJC D EFGH IJC D EFGH IJBCC D EFGH IJ % $3 ),,+,(2*++,#0 )++,$ - ++,%&.)+ +&-(+ +$,(+ +,#*(+ +,#)(+ +,%((++&*(++' '(++( &(++)%(++(#(++(%(++,&%"(++,%2!(++,&h(++((+ +*+ +' (++*D++&(++*LC-+ +,&(++*KNA-+ +,' (++*KMN@-+ +( (++*KMMNB-*+ +((++*KMMND.*+ +)(++*KMMD.*+ +)(++*KLMMND.+ +*(++*KLLMMOC.++#(++*KLLMOC.*+ +)(++*KKLLMND/*+ +'(++*KKLMNE0*+ +,'(++*KKLME.+ +((++*JKKLMG-+ +)(++*JKKLMH,+ +)(++*JKKLMH.+ +) (++*JJKLF/+ +)% %!)++*JJKLME1+ +*++*IJJKLME0*+ +*++*IJJ KLMG1*+ +*++*HJJKLH1*+&+*HIJJKLG1*+%+*HIIJJKMG0+%+*HIIJKMG0*+#+,JIIJKMG1*+!+*3JIIJKLG3)+ +*AIIJKG4*++*9HHI IJKH2++*3FHH IJKJ0++,3>GHH IJK1++?@A@AABBAGHH IJK3++EFEFFGGHGGH IJKH4++D EFGH IJKLH4*++DD EFG H IJKLH4)+DD EFGH IJKLI6)DD EFGH IJKI6DD EFGH IJKIDD EFGH IJK D EFGH IJC D EFG H IJC D EFGH IJC D EFGH IJBCC D EFGH IJ % $3 ),,+,(2*++,#0 )++,$ - ++,%&.)+ +&-(+ +$,(+ +,#*(+ +,#)(+ +,%((++&*(++' '(++( &(++)%(++(#(++(%(++,&%"(++,%2!(++,&h(++((+ +*+ +' (++*D++&(++*LC-+ +,&(++*KNA-+ +,' (++*KMN@-+ +( (++*KMMNB-*+ +((++*KMMND.*+ +)(++*KMMD.*+ +)(++*KLMMND.+ +*(++*KLLMMOC.++#(++*KLLMOC.*+ +)(++*KKLLMND/*+ +'(++*KKLMNE0*+ +,'(++*KKLME.+ +((++*JKKLMG-+ +)(++*JKKLMH,+ +)(++*JKKLMH.+ +) (++*JJKLF/+ +)% %!)++*JJKLME1+ +*++*IJJKLME0*+ +*++*IJJ KLMG1*+ +*++*HJJKLH1*+&+*HIJJKLG1*+%+*HIIJJKMG0+%+*HIIJKMG0*+#+,JIIJKMG1*+!+*3JIIJKLG3)+ +*AIIJKG4*++*9HHI IJKH2++*3FHH IJKJ0++,3>GHH IJK1++?@A@AABBAGHH IJK3++EFEFFGGHGGH IJKH4++D EFGH IJKLH4*++DD EFG H IJKLH4)+DD EFGH IJKLI6)DD EFGH IJKI6DD EFGH IJKIDD EFGH IJK D EFGH IJC D EFG H IJC D EFGH IJC D EFGH IJBCC D EFGH IJ:ߣ"5^3m(2A//60 4. '- , $+3*;)8(2'4&:%@$B#H"U![ TE>JY][ Y"_#d#e$h%r&'(w*h+gn nl~,v==b 8857*4+,3+,P1+2+*/+)!.+)!-+*",+*#++* * +*) +( +!' +#&+!'*+ +%5+ +,"I4+ +, !KI4*+ +!#JKJ6*+ +*#  JKI8*+ +*$JH7++%JI6++& b 8857*4+,3+,P1+2+*/+)!.+)!-+*",+*#++* * +*) +( +!' +#&+!'*+ +%5+ +,"I4+ +, !KI4*+ +!#JKJ6*+ +*#  JKI8*+ +*$JH7++%JI6++& b 8857*4+,3+,P1+2+*/+)!.+)!-+*",+*#++* * +*) +( +!' +#&+!'*+ +%5+ +,"I4+ +, !KI4*+ +!#JKJ6*+ +*#  JKI8*+ +*$JH7++%JI6++& 766542 100/- , + * ) ('&% $#"!  +,34456 7 89:; +,34456 7 89:;+ +,34456 7 89: +,24456 7 89: +,24 456 7 89:+ +,2334 456 7 89 +,233 456 7 89 +,233 456 7 89 +,233 456 7 89+ +,233 456 7 89+ +,233 456 7 8 +,13 3 456 78 +,1223 3 456 78 +,122 3 456 78 +,122 3 456 78 +,122 3 456 78+ +,122 3 456 7 +,0122 3 456 7 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567+ +,/0112 3 456 +,/00112 3 456 +,//0012 3 456 +,//012 3 456+ +,//012 3 45 +,//012 3 45 +,//0 12 3 45+ +,//01 2 3 4 +,/ /012 34 +. /012 34 +. /012 34 +. /012 34 +. /012 34 +-.. /012 34+ +-.. /012 3 +-.. /0123 +-. . /0123 +- . /0123 +,-- . /0 123 +,-- . /0123 +,-- . /0123+ +,-- . /012 +,-- . /0 12 +,- . /012 +,- . /012+,- . /012+,- . /01+,- . /01+,- . /01+,- . /01 +*++,- . /01 +*+,- . /01+ +*+,- . /0 +*+,- . /0 +*+,- . /0+ +*+,- . / + *+,- ./ +) *+,- ./ +) * +,- ./ +) *+,- ./ +) *+,- ./ +,34456 7 89:; +,34456 7 89:;+ +,34456 7 89: +,24456 7 89: +,24 456 7 89:+ +,2334 456 7 89 +,233 456 7 89 +,233 456 7 89 +,233 456 7 89+ +,233 456 7 89+ +,233 456 7 8 +,13 3 456 78 +,1223 3 456 78 +,122 3 456 78 +,122 3 456 78 +,122 3 456 78+ +,122 3 456 7 +,0122 3 456 7 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567+ +,/0112 3 456 +,/00112 3 456 +,//0012 3 456 +,//012 3 456+ +,//012 3 45 +,//012 3 45 +,//0 12 3 45+ +,//01 2 3 4 +,/ /012 34 +. /012 34 +. /012 34 +. /012 34 +. /012 34 +-.. /012 34+ +-.. /012 3 +-.. /0123 +-. . /0123 +- . /0123 +,-- . /0 123 +,-- . /0123 +,-- . /0123+ +,-- . /012 +,-- . /0 12 +,- . /012 +,- . /012+,- . /012+,- . /01+,- . /01+,- . /01+,- . /01 +*++,- . /01 +*+,- . /01+ +*+,- . /0 +*+,- . /0 +*+,- . /0+ +*+,- . / + *+,- ./ +) *+,- ./ +) * +,- ./ +) *+,- ./ +) *+,- ./ +,34456 7 89:; +,34456 7 89:;+ +,34456 7 89: +,24456 7 89: +,24 456 7 89:+ +,2334 456 7 89 +,233 456 7 89 +,233 456 7 89 +,233 456 7 89+ +,233 456 7 89+ +,233 456 7 8 +,13 3 456 78 +,1223 3 456 78 +,122 3 456 78 +,122 3 456 78 +,122 3 456 78+ +,122 3 456 7 +,0122 3 456 7 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567 +,0112 3 4567+ +,/0112 3 456 +,/00112 3 456 +,//0012 3 456 +,//012 3 456+ +,//012 3 45 +,//012 3 45 +,//0 12 3 45+ +,//01 2 3 4 +,/ /012 34 +. /012 34 +. /012 34 +. /012 34 +. /012 34 +-.. /012 34+ +-.. /012 3 +-.. /0123 +-. . /0123 +- . /0123 +,-- . /0 123 +,-- . /0123 +,-- . /0123+ +,-- . /012 +,-- . /0 12 +,- . /012 +,- . /012+,- . /012+,- . /01+,- . /01+,- . /01+,- . /01 +*++,- . /01 +*+,- . /01+ +*+,- . /0 +*+,- . /0 +*+,- . /0+ +*+,- . / + *+,- ./ +) *+,- ./ +) * +,- ./ +) *+,- ./ +) *+,- ./; <=>? @ AB; <=>? @ A ; <=> ? @ A:; ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; <=>? @A9: ; < =>? @A9: ; <=>? @9: ; <=>? @899: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=> ?@889: ; <=>? 89: ; <=>?78 89: ; <=>?7 89: ; <=>?7 89: ; < =>?7 89: ; <=>?77 89: ; <=>7 89: ; <=> 7 89: ; < =67 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=56 7 89: ; <=556 7 89: ; <56 7 89: ; <456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<4456 7 89: ; 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;33 456 7 89: 3 456 7 89:2 3 456 7 89:22 3 456 7 892 3 456 7 892 3 456 7 892 3 456 7 89122 3 456 7 812 3 456 7812 3 456 781 2 3 456 7812 3 456 780112 3 456 78012 3 456 780012 3 456 7/0012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567; <=>? @ AB; <=>? @ A ; <=> ? @ A:; ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; <=>? @A9: ; < =>? @A9: ; <=>? @9: ; <=>? @899: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=> ?@889: ; <=>? 89: ; <=>?78 89: ; <=>?7 89: ; <=>?7 89: ; < =>?7 89: ; <=>?77 89: ; <=>7 89: ; <=> 7 89: ; < =67 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=56 7 89: ; <=556 7 89: ; <56 7 89: ; <456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<4456 7 89: ; 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;33 456 7 89: 3 456 7 89:2 3 456 7 89:22 3 456 7 892 3 456 7 892 3 456 7 892 3 456 7 89122 3 456 7 812 3 456 7812 3 456 781 2 3 456 7812 3 456 780112 3 456 78012 3 456 780012 3 456 7/0012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567; <=>? @ AB; <=>? @ A ; <=> ? @ A:; ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; <=>? @A9: ; < =>? @A9: ; <=>? @9: ; <=>? @899: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@89: ; <=> ?@889: ; <=>? 89: ; <=>?78 89: ; <=>?7 89: ; <=>?7 89: ; < =>?7 89: ; <=>?77 89: ; <=>7 89: ; <=> 7 89: ; < =67 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=56 7 89: ; <=556 7 89: ; <56 7 89: ; <456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<4456 7 89: ; 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;3 456 7 89:;33 456 7 89: 3 456 7 89:2 3 456 7 89:22 3 456 7 892 3 456 7 892 3 456 7 892 3 456 7 89122 3 456 7 812 3 456 7812 3 456 781 2 3 456 7812 3 456 780112 3 456 78012 3 456 780012 3 456 7/0012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567/012 3 4567BC D EFGH IBC D EFGH IABBC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHI ABC D EFGH ABC D EFGH@A ABC D EFGH@ ABC D EFGH@ ABC D EFGH@ ABC D EFG@ ABC D EFG@ ABC D EFG @ ABC D EFG?@ @ ABC D EFG? @ ABC D EFG?? @ ABC D EF? @ ABC D EF? @ ABC D EF>?? @ ABC D E>? @ ABC D E>? @ ABC DE=>>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC D=>? @ ABCD<==>? @ ABCD<=>? @ ABCD<=> ? @ ABCD<=>? @ ABCD<=>? @ ABCD<< =>? @ ABC <=>? @ ABC;< <=> ? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB; <=>? @ AB;; <=>? @ A ; <=>? @A: ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; < =>? @A9: ; <=>? @A99: ; <=>? @9: ; <=>?@89: ; <=>?@89: ; < =>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@889: ; <=>? 89: ; <=>?7 89: ; < =>?7 89: ; <=>?7 89: ; <=>?7 89: ; <=>7 89: ; <=> 7 89: ; <=>BC D EFGH IBC D EFGH IABBC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHI ABC D EFGH ABC D EFGH@A ABC D EFGH@ ABC D EFGH@ ABC D EFGH@ ABC D EFG@ ABC D EFG@ ABC D EFG @ ABC D EFG?@ @ ABC D EFG? @ ABC D EFG?? @ ABC D EF? @ ABC D EF? @ ABC D EF>?? @ ABC D E>? @ ABC D E>? @ ABC DE=>>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC D=>? @ ABCD<==>? @ ABCD<=>? @ ABCD<=> ? @ ABCD<=>? @ ABCD<=>? @ ABCD<< =>? @ ABC <=>? @ ABC;< <=> ? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB; <=>? @ AB;; <=>? @ A ; <=>? @A: ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; < =>? @A9: ; <=>? @A99: ; <=>? @9: ; <=>?@89: ; <=>?@89: ; < =>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@889: ; <=>? 89: ; <=>?7 89: ; < =>?7 89: ; <=>?7 89: ; <=>?7 89: ; <=>7 89: ; <=> 7 89: ; <=>BC D EFGH IBC D EFGH IABBC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHIABC D EFGHI ABC D EFGH ABC D EFGH@A ABC D EFGH@ ABC D EFGH@ ABC D EFGH@ ABC D EFG@ ABC D EFG@ ABC D EFG @ ABC D EFG?@ @ ABC D EFG? @ ABC D EFG?? @ ABC D EF? @ ABC D EF? @ ABC D EF>?? @ ABC D E>? @ ABC D E>? @ ABC DE=>>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC DE=>? @ ABC D=>? @ ABCD<==>? @ ABCD<=>? @ ABCD<=> ? @ ABCD<=>? @ ABCD<=>? @ ABCD<< =>? @ ABC <=>? @ ABC;< <=> ? @ ABC; <=>? @ ABC; <=>? @ ABC;; <=>? @ AB; <=>? @ AB;; <=>? @ A ; <=>? @A: ; <=>? @A: ; <=>? @A9:: ; <=>? @A9: ; < =>? @A9: ; <=>? @A99: ; <=>? @9: ; <=>?@89: ; <=>?@89: ; < =>?@89: ; <=>?@89: ; <=>?@89: ; <=>?@889: ; <=>? 89: ; <=>?7 89: ; < =>?7 89: ; <=>?7 89: ; <=>?7 89: ; <=>7 89: ; <=> 7 89: ; <=>JK4++%IJJK6++# IJK7++! IJI8+ +," IJI9*+ +,$ IJI:)+ +%DIJ;)+ +$ IJ;*+ +,"HIIJ:+ +,"H IJ9+ +,$H IJ9++%H IJ<++& H I<++'  H I<++' GH IJ:++(GHIK:++& GHIK:,+ +% GHIJ<-+ +,$# FGHI=,+ +,%5 FGHI=*+ +'FGHI?*+ +' FGHI@*+ +&EFFGHI?,+ +,%EFGHI=-+ +,&EFGHIJ=-+ +,( EFGHIJ>,*+ +(EFGHIJ@,*+ +) EFGHI@-+ +)  EFGHI?-+ +* DE EFGHIJ?-++DDE EFGHK?-*+ +)DD EFGHJ@.*+ +'DD EFGHI@.*+ +,# DD EFGH@-+ +DD EFGHA,+ +,DD EFGHB,+ +,CD D EFGH>*+ +,!CC D EFGH<+ +,CC D EFG<,+ +( CC D EFG;,+ +BCC D EFGH9,+ +%BBC D EFH8,+ +'BBC D EFG8,+ +(ABBC D EF9++& AABC DE;++'ABC DE:*+ +&ABC DE8*+ +%ABC DE7+ +,# ABC D7+ +,! ABCD8+ +,!@A ABCD8*+ +$ @ ABCD7*+ +& @ ABCB6*+ +& @ ABCB6++$ @ ABC5++#  @ ABC4++$  @ AB3++%? @A3++%? @A@4++%? @A@5*+ +*$?@A@4*+ +*#?@?2*+ +">??@?2+ +, >?@?3+ +,JK4++%IJJK6++# IJK7++! IJI8+ +," IJI9*+ +,$ IJI:)+ +%DIJ;)+ +$ IJ;*+ +,"HIIJ:+ +,"H IJ9+ +,$H IJ9++%H IJ<++& H I<++'  H I<++' GH IJ:++(GHIK:++& GHIK:,+ +% GHIJ<-+ +,$# FGHI=,+ +,%5 FGHI=*+ +'FGHI?*+ +' FGHI@*+ +&EFFGHI?,+ +,%EFGHI=-+ +,&EFGHIJ=-+ +,( EFGHIJ>,*+ +(EFGHIJ@,*+ +) EFGHI@-+ +)  EFGHI?-+ +* DE EFGHIJ?-++DDE EFGHK?-*+ +)DD EFGHJ@.*+ +'DD EFGHI@.*+ +,# DD EFGH@-+ +DD EFGHA,+ +,DD EFGHB,+ +,CD D EFGH>*+ +,!CC D EFGH<+ +,CC D EFG<,+ +( CC D EFG;,+ +BCC D EFGH9,+ +%BBC D EFH8,+ +'BBC D EFG8,+ +(ABBC D EF9++& AABC DE;++'ABC DE:*+ +&ABC DE8*+ +%ABC DE7+ +,# ABC D7+ +,! ABCD8+ +,!@A ABCD8*+ +$ @ ABCD7*+ +& @ ABCB6*+ +& @ ABCB6++$ @ ABC5++#  @ ABC4++$  @ AB3++%? @A3++%? @A@4++%? @A@5*+ +*$?@A@4*+ +*#?@?2*+ +">??@?2+ +, >?@?3+ +,JK4++%IJJK6++# IJK7++! IJI8+ +," IJI9*+ +,$ IJI:)+ +%DIJ;)+ +$ IJ;*+ +,"HIIJ:+ +,"H IJ9+ +,$H IJ9++%H IJ<++& H I<++'  H I<++' GH IJ:++(GHIK:++& GHIK:,+ +% GHIJ<-+ +,$# FGHI=,+ +,%5 FGHI=*+ +'FGHI?*+ +' FGHI@*+ +&EFFGHI?,+ +,%EFGHI=-+ +,&EFGHIJ=-+ +,( EFGHIJ>,*+ +(EFGHIJ@,*+ +) EFGHI@-+ +)  EFGHI?-+ +* DE EFGHIJ?-++DDE EFGHK?-*+ +)DD EFGHJ@.*+ +'DD EFGHI@.*+ +,# DD EFGH@-+ +DD EFGHA,+ +,DD EFGHB,+ +,CD D EFGH>*+ +,!CC D EFGH<+ +,CC D EFG<,+ +( CC D EFG;,+ +BCC D EFGH9,+ +%BBC D EFH8,+ +'BBC D EFG8,+ +(ABBC D EF9++& AABC DE;++'ABC DE:*+ +&ABC DE8*+ +%ABC DE7+ +,# ABC D7+ +,! ABCD8+ +,!@A ABCD8*+ +$ @ ABCD7*+ +& @ ABCB6*+ +& @ ABCB6++$ @ ABC5++#  @ ABC4++$  @ AB3++%? @A3++%? @A@4++%? @A@5*+ +*$?@A@4*+ +*#?@?2*+ +">??@?2+ +, >?@?3+ +, %,+  (!1"/#)$)%/&5'8(= )G *O +J ,; -3.</K0R1O2L4Q4W5Y5A67g77776*65%453,3#2#1.05//.#-," ++ *, )( ($ '"&%$ #"!  +) *+,- ./+ + ) *+,- . + ) * +,-. +*(()) *+,-. +( ) *+,-. +( ) *+,-. +('(( ) *+,-. +(''( ) *+,-.+ +*''( ) *+,- +*''( ) *+,- +*'&''( ) *+,- +*'&&''( ) *+,- +*'&&'( ) *+,-+ +*'&&'( ) *+, +*'&&'( ) * +, +*'&&'( ) *+,+ +*& &'( ) *+*&%&&'( ) *+*&%%& &'( ) *+*&%% &'( ) * +*('' ( )*B+*+=+",+<+,+<+*+<+!=+ !*+:+!$&(8("#7# | +) *+,- ./+ + ) *+,- . + ) * +,-. +*(()) *+,-. +( ) *+,-. +( ) *+,-. +('(( ) *+,-. +(''( ) *+,-.+ +*''( ) *+,- +*''( ) *+,- +*'&''( ) *+,- +*'&&''( ) *+,- +*'&&'( ) *+,-+ +*'&&'( ) *+, +*'&&'( ) * +, +*'&&'( ) *+,+ +*& &'( ) *+*&%&&'( ) *+*&%%& &'( ) *+*&%% &'( ) * +*('' ( )*B+*+=+",+<+,+<+*+<+!=+ !*+:+!$&(8("#7# | +) *+,- ./+ + ) *+,- . + ) * +,-. +*(()) *+,-. +( ) *+,-. +( ) *+,-. +('(( ) *+,-. +(''( ) *+,-.+ +*''( ) *+,- +*''( ) *+,- +*'&''( ) *+,- +*'&&''( ) *+,- +*'&&'( ) *+,-+ +*'&&'( ) *+, +*'&&'( ) * +, +*'&&'( ) *+,+ +*& &'( ) *+*&%&&'( ) *+*&%%& &'( ) *+*&%% &'( ) * +*('' ( )*B+*+=+",+<+,+<+*+<+!=+ !*+:+!$&(8("#7# |={='=<:8)AM8M /012 3 456./ /012 3 456. /012 3 456. /012 3 456.. /012 3 45. /012 3 45. /012 3 45.. /012 3 4-. . /012 34- . /012 34- . /012 34- . /012 34- . /0 12 34,-- . /012 34,,-- . /012 3,- . /0123,- . /0 123+,- . /0123+,- . /01 23+,- . /0123+, - . /+?(?# /012 3 456./ /012 3 456. /012 3 456. /012 3 456.. /012 3 45. /012 3 45. /012 3 45.. /012 3 4-. . /012 34- . /012 34- . /012 34- . /012 34- . /0 12 34,-- . /012 34,,-- . /012 3,- . /0123,- . /0 123+,- . /0123+,- . /01 23+,- . /0123+, - . /+?(?# /012 3 456./ /012 3 456. /012 3 456. /012 3 456.. /012 3 45. /012 3 45. /012 3 45.. /012 3 4-. . /012 34- . /012 34- . /012 34- . /012 34- . /0 12 34,-- . /012 34,,-- . /012 3,- . /0123,- . /0 123+,- . /0123+,- . /01 23+,- . /0123+, - . /+?(?#??M 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=566 7 89: ; <=56 7 89: ; <=556 7 89: ; <4556 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ; 456 7 89: ;34 456 7 89:;933 456 7 89:;:/33 456 7 89:;9/*33 456 7 89:;8.*+33 456 7 89:7.++3 456 7 898.++ / 02465566 7 898-++.556 7 898,++-4556 787,++-4556 786-++*2556 786.+!+/44556 786.*+!+,4456785-*+#+456784-+%+45674,+&+45675,+'+4564-+(+ 45673,+ +)( ( +3 4564,+ +)# #)+ +3 452,+ +,)(+ +3 42,+ +,((+ +343,+ +( (+ +342++)(+ + 342++)(+ + 341,+ +,((+ +231,+ +,'x(+ +230,+ +'(+ +2341,+ +)(+ +12240,+ +*(+ +1220++) (+ +1220++) (+ +120++( (+ +011/++((+ +011.+ +,' (+ +01.+ +,&(+ +0/+ +,&(+ +/++&(++' (++'(++,&- (++,%!(++&"(++'%(++(#(++( %(++( &(++' '(++&*(+ +,$((+ +,$ 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=566 7 89: ; <=56 7 89: ; <=556 7 89: ; <4556 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ; 456 7 89: ;34 456 7 89:;933 456 7 89:;:/33 456 7 89:;9/*33 456 7 89:;8.*+33 456 7 89:7.++3 456 7 898.++ / 02465566 7 898-++.556 7 898,++-4556 787,++-4556 786-++*2556 786.+!+/44556 786.*+!+,4456785-*+#+456784-+%+45674,+&+45675,+'+4564-+(+ 45673,+ +)( ( +3 4564,+ +)# #)+ +3 452,+ +,)(+ +3 42,+ +,((+ +343,+ +( (+ +342++)(+ + 342++)(+ + 341,+ +,((+ +231,+ +,'x(+ +230,+ +'(+ +2341,+ +)(+ +12240,+ +*(+ +1220++) (+ +1220++) (+ +120++( (+ +011/++((+ +011.+ +,' (+ +01.+ +,&(+ +0/+ +,&(+ +/++&(++' (++'(++,&- (++,%!(++&"(++'%(++(#(++( %(++( &(++' '(++&*(+ +,$((+ +,$ 7 89: ; <=6 7 89: ; <=6 7 89: ; <=6 7 89: ; <=566 7 89: ; <=56 7 89: ; <=556 7 89: ; <4556 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ;<456 7 89: ; 456 7 89: ;34 456 7 89:;933 456 7 89:;:/33 456 7 89:;9/*33 456 7 89:;8.*+33 456 7 89:7.++3 456 7 898.++ / 02465566 7 898-++.556 7 898,++-4556 787,++-4556 786-++*2556 786.+!+/44556 786.*+!+,4456785-*+#+456784-+%+45674,+&+45675,+'+4564-+(+ 45673,+ +)( ( +3 4564,+ +)# #)+ +3 452,+ +,)(+ +3 42,+ +,((+ +343,+ +( (+ +342++)(+ + 342++)(+ + 341,+ +,((+ +231,+ +,'x(+ +230,+ +'(+ +2341,+ +)(+ +12240,+ +*(+ +1220++) (+ +1220++) (+ +120++( (+ +011/++((+ +011.+ +,' (+ +01.+ +,&(+ +0/+ +,&(+ +/++&(++' (++'(++,&- (++,%!(++&"(++'%(++(#(++( %(++( &(++' '(++&*(+ +,$((+ +,$ ,sM MKa,g +X )Z(i't&r%d$\#Z"U"N KOUPB7>LS M!C >#:$4%.&-'5(8)0* ">?@3*+ +,=>>?>3*+ +"=>?>1*+ +#=>?=1++#=>=1+ +*!=0++! =/+ +*" <=.++#<=<0++#<;1*+ +*!<:0*+ +*!<;/*+ +*  =:/*+ + #9/+ +,9"/+ +,'#*+ +% +*( +*!& +* ' +) ( +* )+* *+* ++*,+)-+).+*2+00+,1+3*49 6 6>?@3*+ +,=>>?>3*+ +"=>?>1*+ +#=>?=1++#=>=1+ +*!=0++! =/+ +*" <=.++#<=<0++#<;1*+ +*!<:0*+ +*!<;/*+ +*  =:/*+ + #9/+ +,9"/+ +,'#*+ +% +*( +*!& +* ' +) ( +* )+* *+* ++*,+)-+).+*2+00+,1+3*49 6 6>?@3*+ +,=>>?>3*+ +"=>?>1*+ +#=>?=1++#=>=1+ +*!=0++! =/+ +*" <=.++#<=<0++#<;1*+ +*!<:0*+ +*!<;/*+ +*  =:/*+ + #9/+ +,9"/+ +,'#*+ +% +*( +*!& +* ' +) ( +* )+* *+* ++*,+)-+).+*2+00+,1+3*49 6 6   !! " #$&& ' ( ) ~* z+ ,./01{1z2t4m5o6tx(+ +,"*(+ +$,'+ +%p+*++,%".++,$ 0*++,#/',,)1   {%(+ +,"*(+ +$,'+ +%p+*++,%".++,$ 0*++,#/',,)1   {%(+ +,"*(+ +$,'+ +%p+*++,%".++,$ 0*++,#/',,)1   {%  , &. 2/|50..0(3Z567&}d>2' Pasted Layer     n zm'z0%010=0I'{18)^~ڥPs3GW`jr}1Pʓ/(> l`,<(Uk4} H!1Awߢ-J!<,,,,,,---%-5-E-U.O////00 ?  *! ,! /" /$*&+*!!" ! ?  *! ,! /" /$*&+*!!" ! ?  *! ,! /" /$*&+*!!" ! T!                                               '$&  !$""           '$&  !$""           '$&  !$"" #                                                      ?963b            ?963b            ?963b            |7 4                                                       :            :            :                                                                                3 * (  (  ( 3 - 16631 " 4r878   3 * (  (  ( 3 - 16631 " 4r878   3 * (  (  ( 3 - 16631 " 4r878   1)$!                                              ., * '%$" ! ! %'$% - ,% ., * '%$" ! ! %'$% - ,% ., * '%$" ! ! %'$% - ,% gV=<                          v2.*&}!! !   !!#""##$$ " $"+v2.*&}!! !   !!#""##$$ " $"+v2.*&}!! !   !!#""##$$ " $"+@;741-- ) ) &$ "    g#% #   0   1 @;741-- ) ) &$ "    g#% #   0   1 @;741-- ) ) &$ "    g#% #   0   1 00   )'9<"4 "227+$ ' ! '  /#00   )'9<"4 "227+$ ' ! '  /#00   )'9<"4 "227+$ ' ! '  /#;651 0.., ***'&&'%$$$$#            ;651 0.., ***'&&'%$$$$#            ;651 0.., ***'&&'%$$$$#             l2-( b +-+(--+,- l2-( b +-+(--+,- l2-( b +-+(--+,- ><;::;<78><<;98876        r       <<;98876        r       <<;98876        r       <;987665                                                  !    !   3 * (  %  ! -.,01 ) 1    " 4   4 4 2 #  #  " 3 * (  %  ! -.,01 ) 1    " 4   4 4 2 #  #  " 3 * (  %  ! -.,01 ) 1    " 4   4 4 2 #  #  " 0, & !                                                          "    ,&"}     + !.%/,*  +  %%    ,&"}     + !.%/,*  +  %%    ,&"}     + !.%/,*  +  %%   _)$  !                                  /)& ! !  !$  %!     - SD /)& ! !  !$  %!     - SD /)& ! !  !$  %!     - SD 0*$                                                 Z&#"    g                 ! Z&#"    g                 ! Z&#"    g                 ! Z)$"                                                               "!"%* 040.+            Ij           "!"%* 040.+            Ij           "!"%* 040.+            Ij                       !    %    +   04  /  -   +                                                                                                                                         "            "                          "         !     + !    #5  782.  $ "h   / 3  5 78&$:<  "!    "   &&  !     + !    #5  782.  $ "h   / 3  5 78&$:<  "!    "   &&  !     + !    #5  782.  $ "h   / 3  5 78&$:<  "!    "   &&        !     !                                                                                                                                                                                                                                                                                           ! "  "   !                                                                                               (  !!!  ) - ,# - :; +      -* <65 $  ( $A `.)<<-          (  !!!  ) - ,# - :; +      -* <65 $  ( $A `.)<<-          (  !!!  ) - ,# - :; +      -* <65 $  ( $A `.)<<-                                                                 !                                                                                                                                                                                                                                                                                                    "          "           !             !  #             5        )""#$        & ( ) 6! ) "  "    5        )""#$        & ( ) 6! ) "  "    5        )""#$        & ( ) 6! ) "  "                                                                                                                                                                                                                                       ( !   "     " #   $                                                           ! " !                   !       !"       "     !   # 03 57:>:81.*       (6  40" $ ,#&"  "# ! " &$#" !)%:! <"8 < 03 57:>:81.*       (6  40" $ ,#&"  "# ! " &$#" !)%:! <"8 < 03 57:>:81.*       (6  40" $ ,#&"  "# ! " &$#" !)%:! <"8 < 0 3 5 7 :=; 7   2   /   +     '                                                                                                                #   #                "   &   #  #  !                                                                               !                         "                                          !             .             -/ 11      !!!  . %;7:9 -. * " 5         .             -/ 11      !!!  . %;7:9 -. * " 5         .             -/ 11      !!!  . %;7:9 -. * " 5                                                                                                                 .  /  0   0            -32+ 10  /                       !                                                         "!   "     " " !           # ) 71$$8+' 34 2 2 ,  '#! "(- + *)(&%$# % &: +$.0  ) / 4 & # * &-"  # ) 71$$8+' 34 2 2 ,  '#! "(- + *)(&%$# % &: +$.0  ) / 4 & # * &-"  # ) 71$$8+' 34 2 2 ,  '#! "(- + *)(&%$# % &: +$.0  ) / 4 & # * &-"41  579# <%  7"88 (760& )   ) #""!$  # % 0&*19458 !%0 +  !## %  579# <%  7"88 (760& )   ) #""!$  # % 0&*19458 !%0 +  !## %  579# <%  7"88 (760& )   ) #""!$  # % 0&*19458 !%0 +  !## %     5667764 1 /-,       ;7. 2"Ij               5667764 1 /-,       ;7. 2"Ij               5667764 1 /-,       ;7. 2"Ij                        * !     + )*" !h  ! !      2$:"         5 32*.*              * !     + )*" !h  ! !      2$:"         5 32*.*              * !     + )*" !h  ! !      2$:"         5 32*.*,  )(*")  *     1 % $ 5 5#5 9+                $2 / 36- )7 '7%" 2 0*""%,  )(*")  *     1 % $ 5 5#5 9+                $2 / 36- )7 '7%" 2 0*""%,  )(*")  *     1 % $ 5 5#5 9+                $2 / 36- )7 '7%" 2 0*""%<<< ;:: 8 7 6 5 4 2 2  1   1  2    ( 3 3  * *  )( ' &   %   #                     !                                                                 !             &    (      (    $               )#4         32 4 5 567,&" $" &(  (=!)"&  5  4                )#4         32 4 5 567,&" $" &(  (=!)"&  5  4                )#4         32 4 5 567,&" $" &(  (=!)"&  5  4                                                                                                             $         !                                                         2 3 3 4  5   56 7   %  !     $   !                                                                 !                 ! !        !        "          #!!#    #        "$  !      ! $'+)(&%#   2 .  (:  36 0( 2 ) /%   " &$#"  1 5%:! <"*8< @!$'+)(&%#   2 .  (:  36 0( 2 ) /%   " &$#"  1 5%:! <"*8< @!$'+)(&%#   2 .  (:  36 0( 2 ) /%   " &$#"  1 5%:! <"*8< @!%  (  +  )  (  %  $   #                                                                                                    "                                                              "   &   #  #   !                                                                                                                                                                       !          .           ! " 4 69<6 2 0,(   " ! ) %  !  ! ) 8 %;7:9    5      .           ! " 4 69<6 2 0,(   " ! ) %  !  ! ) 8 %;7:9    5      .           ! " 4 69<6 2 0,(   " ! ) %  !  ! ) 8 %;7:9    5                                                                                                  ! $    3  5 8<7  2  / +  (                                                                                                                                                           !       !   ! !            !    !         #           "   " j+=;/, 35 4 3  ."  ,)    + *)(&            7)  )   4 &  2 . *   j+=;/, 35 4 3  ."  ,)    + *)(&            7)  )   4 &  2 . *   j+=;/, 35 4 3  ."  ,)    + *)(&            7)  )   4 &  2 . *                                                             '   &    $                                                         +   )   (    '   %                                                                                                                                                          !      !        !         !          !!         " !        #" !         !$        !$! ! # '5- (  , ) 1 2 ( . 0 &)+ '"!!"'#   !#$ %$%% %&"" !%.355789< # '5- (  , ) 1 2 ( . 0 &)+ '"!!"'#   !#$ %$%% %&"" !%.355789< # '5- (  , ) 1 2 ( . 0 &)+ '"!!"'#   !#$ %$%% %&"" !%.355789<                                                                                                                                                     "  "    '  &   $  !                                                                                         %  / 3  4  5   7 "8!9;   !!"#"&&)) ./ I   !!"#"&&)) ./ I   !!"#"&&)) ./ I#             # "   %   !!      $   $      ! "       ! !  $   !  '! " !  #" %  " #& %  "%$ &   "%"!%!  ) ((   &*$&& %'&"( - '%')&&$#1   J&$#" " !#'/ &$#" " !#'/ &$#" " !#'/  $    !  &     !&%#    %       !"!  ! "#""   "%#"&'#"%!    #             ' !"   #%! "  ##&'$#(  /     +,/ / 2 69     +,/ / 2 69     +,/ / 2 69   #       "!    !$  $"  !   #   !   #  # !%!  !  $'   % # %    "!" !  %    ! $   +"  %-     ". #   ( /' '#2$'!&#"5   ! !#$ % &'))***+ + +, ,---..///001 1 2 2 58! !#$ % &'))***+ + +, ,---..///001 1 2 2 58! !#$ % &'))***+ + +, ,---..///001 1 2 2 58      $""$      !$%!"     ! "     $! !%    #"!      ##"     $"   !&    &      (&   ("   )%   )&   )"    *#   +#   *#   + &  + #   -(    -* ,$ -) -(  .+  . (  .&  %/ )  )/)  $&0 '#  .(1. %/*2, #0%2) %.+4')'#8:="!  !  #   !!""!#"$#$%&&(()+-./0 3 459:="!  !  #   !!""!#"$#$%&&(()+-./0 3 459:="!  !  #   !!""!#"$#$%&&(()+-./0 3 459%       !      !       !     ! "   "  !       % %   %  %!   $!%"#   #!  $ "  "%&$     ""  #    $##!'"!  !$"   '$ !  '$#!%"$!!!$#%   "!    &"!     #" !    !"$$       !% "    $ $%$   " " !  '  $!     %!  !  %   !#!%  $$(   $   &  !"%"! ! "  ) #$ "  ) "   (  &!!# !  &"! #  )# !#    * "!!     % "$#  #! $##% %% #&$ $ ! #%% %#$ %% &#$!% !+%)! "%)!"    %"'!##"  *',%#  ")"-)   ',..  %,,  ) -")   *. $'  '"/&! *2 '(,! 3 -%%(&!5 # #8)$% "$&# +"S :!<":$4/  3 1! !" &%!"%* 0      )$% "$&# +"S :!<":$4/  3 1! !" &%!"%* 0      )$% "$&# +"S :!<":$4/  3 1! !" &%!"%* 0            !            #         #       $#   "   !!   $ #   !  $       &&$%$%##!  ""    '' !&%"  '"      #%   $&   #(     #&   "#% &#     "   &'"#!%     '&        (         & *       #*%      "  (*         $ $)       ! !$       !"       %'%    !% %      & # &    )# +    & $ +      & ' *%  ($ ' "-   +" % -%     $,$ $ ".!    !'& !$ *)"   ($ (  +'"   &)  '*!  ('%  *%" , '$ &)-% !+() -  +  "+.,./**-+1,*$ $*)%    ))# -#  !1# $-' (,- ! &..'  $(2&%%-/(% $(-0)$) ',+-2-++""0   $/37423%(2093( ./) %2**'&6,  .# -."+-'0+  (/ -*#,('.(  5 0' #.$ !/'  6 1(  1# /* /#.( "2#*1  *--+,00&#7 (- *, 2+ ,* 8$ )+&1 7(  !& " %                + !    "3 3 3 - )$h  !& " %                + !    "3 3 3 - )$h  !& " %                + !    "3 3 3 - )$h : 8  $5##% 48 #    $#$$ !%$#""   " "# !#    ! %   !"$ #  $ #   $ '%%!  & %  &#($ ! "  '%'((   !)!$%& "  &&""'# %   ( !! )" "  #( )*!(  ') *"'"! #) *&&##(" !('')%!& %(  (&#&" %#)($  '&&!$$&"$+$ (  *+'( +%!#'$! $ )&!$&*#!&,'##%%# " ()&"$*+-++'' ( (  &+ &   * ($&'*   ) %#  %-    & ! (!%   !" ( *%    !$ +  + '    "" )"' +    "  &%,   % '%   ( )"!    & ,&    $! (.    $# &  -"    %" (#$)    - /,    - ..    "* .1#    && . -#  !  0 /($     0  +  %( "   ." .'0       . , '2$!  +& #(1!  "2 00     5  -   1%     -( -  %*23//51144*  #. $/ $2.*!.4+.41-#     1  5",5) !1!+/2)     5%  3.2&  0'"..- $2$  +' .0(    (1 +0*  %-   )-2,  !!',81+'# #+1( -/  +4&  *4341-.56/01511+$$1,! $4-  .6  (05(#3%"*,3/."+.)*1   ,/  ,//  3#(/41(!%0471  4+ '0+ 3##*53/$585# -6% 0*! 4)*17155*"*3",*4'  )5!+7:71*4.23! " ,4&$-474("'42%(57+)82-''+/0231-%"3985,-+  #8;;:4//-& &+/046//$2&'*) ,,.0 /24352/b            (!!  ) - ,# -&'*) ,,.0 /24352/b            (!!  ) - ,# -&'*) ,,.0 /24352/b            (!!  ) - ,# -@#  !,  !-%     /%  $0#  #& 2& $#4# 65 ""& !1 %*++)!!/ && %"# $& $*"!'%&'+    ","*#   +,  + - -,  +' ',$  !( + %"+  (" $ *  )(   *#$  '! . #'   -( %'  $$+  $ ( +,  ( ) ("#   * !) .+#  ( $& .(  $ )  $--  %" -  *"' & /   "$ (+  % *$    -,* % (%     (#/# ! ) *"    0 .  . ,%   *"2  / $-   -  *+ * 1  "" 0,* ) /  ! 30! - .  ! (* /  0 1  !  )( .+ +%   ""  )/"&' !-  "   $3* $& .     +.!, /     &4!7 0"  $$   !72 4 #3  "   )0+*  2  #"!  ! )4% !1 (.  !"$% # $&136  3 #$ " """$$0$ ,+ (0   "$ !"$$ -(6  4'  " $#%  /2  ,*8  !! %  *6#.7/# 2  *5  #5"41'"320) 10 #63 /. 8 3+)8 !!"#"&&)) ./;:                  %            !!"#"&&)) ./;:                  %            !!"#"&&)) ./;:                  %           H  !    $   !    %!   !     !" $  !  #% #  %# $   !%!! $    ' ((     %*#%%$&%!' - &$&'$%##1  <:$% 7*'#*! + (' &*"!!+  $(   $'  ,#  #&  $) *,  $%($ "(  &#-  '!'& #) '+ *  ($ &'! & . !+  (!  )"$ - &+ &"+$! '  '$ ))' $"    -  %! - " "%     !- &) *%  (  "(  ( /'-    (   & ,) 0    - , "/+ /   . ( %. .   ('  #, /+ ,!    /  &% +/ %*   1   .  $.(2    2!  "/ -/2    !0    *! /&),&    +.#!   1 0"//   *1*    -! ."-$3      &30   !  "(('$.))      #12$   0 !.!/1      -3 !  /" 12/"       $     #-.%.% /     ! !     / %-,'0  #" ! !!!  "##  2 3*.#.  !  ""!#     ," .0. 0  " !" !" ,      /# !54 "4  "# !!"  0#  !  %) 53 ,,     ! #!!  /  "!   +& ,6  4%        1  "   &) &6*  7#     4 "  +& 27  7#     +( "$ $   +$ (()1  7#   (* !  .% +" 3) 7' 3   3 *7"60 1!  1! 48 08#5"%$  9 -7 #55   0 ! ! 3 %) 9!  (53$  0"$ $4 )'6-  %05.#  (/#!  (2 5+8&  &0461,& &2&  6$"0 46%  #,2698654336 4" ." 89)  #''&   "          ( 4 /1:;36"  9r<<=  +./2 58762. *  $ ,  (6  40, ( )   "          ( 4 /1:;36"  9r<<=  +./2 58762. *  $ ,  (6  40, ( )   "          ( 4 /1:;36"  9r<<=  +./2 58762. *  $ ,  (6  40, ( )=  "#!$$%    #%!  & ! "$##  "#%$&#&%&%##%%" "#  #  "#%'&%!"!"!!&&%!  #! $ !$  #$   # $!       % "  #  "%     $   !  %    "   "$  $  % # " #!  "'  #'&   '  (%"    &  "%'        +! *$!      &" !+&      & % %)     + ! '    *#%    #$ "!!    %" '      % &     % +   $ )      &,            -*  $"*&*'+))#     %(   %**)/), "  '+'   ))+('$"%"'&+)*'!   +(!   "!   &*"  .+  *,.#    &-$-- ./&-+&,*2"*0//,-,+5 ".#8&.% 6+.! 5 #3 20*  (. %-&  "+2- *(/ "// ' 1#  !  .0 )1)  !  10 '%+*,.,/-402.*%  #!  #4*  ,.00-0)*%*&  !   12)42+! #    "!    )4$4'               &0 ',     # #  !"  -'/   "    $   6 1   "!  !    33   !!!""    70   ##" ""  %24   ! "#"#!$    7 8$    # #"!  4# 55 $   3)+:5      !4+318%    &,05:) //(0+  456970,7:" -$&0/% # "79 "/,12#  /'')*+   ($  " %'$% -  %%    .           246'')*+   ($  " %'$% -  %%    .           246'')*+   ($  " %'$% -  %%    .           246    )  ")  ) $)  !*  #           #"  $ !         " &!      ' '!     $&&(&#"!! '  &"%# %%$  $&!$"" '   $'"      "#  $!  $  $    $ &"   !  '    #&       $  &"   ( '   & (     + )!    * #(      ! + *      *#$ '     + %     & %  )'"&# !"    "'($& ( %     '+ /)' "        )!.(#! '    *&-%  ( ,      ',+ #,*+)-  , )      "," 0  ,/$,%. %#    +( "*)  )+ *    ,! %(#  %%' +    '" *'  , $ )   )   )! $$    * &#& (     #&   #') +   ! ,  -, ,     *" )0, -    1  /3+ !,  ! 1!  %*4*$ #,   3 ((0$) "+ !/! 01-#)   !)0 ,#2 .!) .  0 . /!) $)  .$$+ .#( +  .4'('( *#    /% -2++      !.    ),)*-6/    0  0!/53  )-   2#$,7  4 ,+ 4+/  3# #4'* 0)  0.  4 31%  -/  .(0" 1& /- /) "/ .,!(4'  3$ 7 (/3. -+/& .- &1 2"6& (230&  *15 /0!     }#/          0#)<1$$9+' 34 2 2 ,  '#! "(- + * /0!     }#/          0#)<1$$9+' 34 2 2 ,  '#! "(- + * /0!     }#/          0#)<1$$9+' 34 2 2 ,  '#! "(- + *      #"  #    "$#       "# $!" !      $%"  !    $%$%%$#     %$"&$##%$%%&%%     #&&'#$$ %   !&%"%&%&%%#"     #$%%  ##"$#   !!'"$ !$% %%   $!%! "#!% ###(("   '%   # !("   !)#   ( "   (#   "   $%"  (  %  (&#  ""  &)$     '  %!)%#%(&*    #(   )&$&')#$)   " # *$"!**"    )' )*$%*)* +' ++'&%(% *)  #,,() %) #( '  ')( ++  "* '% %  &'(#+%( +#* '  -% *"!%)&!!,&(  *) ")(&# +,-' &" )($,)*  ")",)-.$+/+ !-&&(.+,* (/+!   ,*''++.%$).,  %)#*. + )*'&!  "-. + ,&    %/)&+  !)+,.)&   )$(-'  #&.+/--('#!    )1(  )*,)$#!    )",0 /-'     *$'%(.(         - *.*#      )% ++,        , +,         0 , 2    (( ,-   !     #- ) 2    "   $) 0.(     - '% )3"      . 042    )& ,,03+ /# ')/$*,  $-# *- $)!),(  (/  &. )" +..            3+ 5 .,&32        !40! "3&+ #22.*   (11'  2&',# &,812+#!",121! $8 /*  !)0.5/3-0,3/413*  .0(/(   " %!!   "01)-+%     -5 "(4+   ,0%  '1+/$  '01-#  -0/3*/%+(22578872() &$14:87342/2:92. ,8$ $45618"+.(  /, #28)4  *6$:$ #)2<,& %! !   & $0  g<}#C6=)  1.+ 662#v?; 7'== (9:0) +  ) ##)2<,& %! !   & $0  g<}#C6=)  1.+ 662#v?; 7'== (9:0) +  ) ##)2<,& %! !   & $0  g<}#C6=)  1.+ 662#v?; 7'== (9:0) +  ) #              #  $          "   "!         !!  #       "  #!   &#      ##"#"$   "       #$&#    ""       $  #   $ !     !  $  "   !     ""'&&!      !&%$! "%$'##&  #    &$&'&#   & "     !$ )# !   $  ##   ! # '   !$#%   # $#      "' &  &#  & #   #&   &   $ ""    "!%  &   $ #   "&%      #' $ "!    ((       %%  #"#   #*    % ,&$   )&    ' %$$ **    !$ ( $# #*!    + $ % *(   + )(! $,!   % +) ! ,)   ' "# ""!  (,"   * ( +%" -(    ## ,  ,(   +,         ) ---   (-$      ' $+  .+     ( "!'  --   #   ( #! #'  %/&   )    !(  &%   /)   ,     ( ( %' !/+   ,   * %  +%%)--    !'    / $  $'0+    )   / (&1#     +"    , +".*    #(.   , !%&+(    * * !     / % &-#       , !'   / & '0'      $& ,    *$,11*#     ) *$  +",4-   "  !   && 1   0+0..  !   0 -& "4/!#.      &+ &+ *14!&.  !    . #4 01(/+'     ,$ /*'  11(,!/        -) '+0  (0+,(,     -. 4 /"   0"-,/$  +2 0+ 1   )+"/&$/          )4  !6 .$ +,*.$.$   -1  21 !44*,,  $2*  -& +) 5& 8-2 /2!  -( 4 &4/81& +4+  ++$5  687$    (7/  ,2 -5  (896*  '//-  1/ #2)*440&:753.'"!(+.46-$  (5% '*&0*'+.0/15863128:6.*(   #4. "-%!"  )23+52$    "48.  +.,&" )*/76( #'/320.-/341-.260*'!  "===<;<;;;::998:::999:9::9:;;<<;====  "===<;<;;;::998:::999:9::9:;;<<;====  "===<;<;;;::998:::999:9::9:;;<<;==== "!!#$ !  !<==!<&;&' ;$+:$&,:"&:!#9$& 9') 9(,8) ,9)'9(%9'%9#)8 +8 -9 + 9 ' 9 "9 (9 -9 / :*:%!;+;%) ;1;4<-<= =+ -,)&%#$$&&((*-03 + -,)&%#$$&&((*-03 + -,)&%#$$&&((*-03   "!    "  !    "   "  ! "         ! #$     !  # #$   # &  % $#  !% #     !$ #" '  !  & "    $"" & !#% ' $$ (  %'   %  &&  # #(!  " $(" "#!"* &  )!$,#(#+$ "!/ !((+*+$%2                 Ij                !!"#"&&)) ./              Ij                !!"#"&&)) ./              Ij                !!"#"&&)) ./!- 1.  &/3  5' .% $6 &/2$#5&  )1 0/  , ,-&4%5 =   ,-6%4%/% 6+ /& 4(!4( 0 "6  6"#42,  1+,5  6" 2+.1 3 < "):<&6  -*$5 #%$%;'1"7#;2. #"%#!544/-  */7 $"%%7 893  3+#7  ")"#2  <;' =3+! *0>4 %74&&###-09$  20  9#)!%+$4$8  5*%9!#'(6&9  > .2 ')#7"9  : .3%&> 6   =1/"&=4) ?61)7,1 & 912#,)(!3->' /85 =+2!)!*7 &;/'$.1:=1& @-8(#8' 258;851   ;*<%**(#.4   <!<&(>" >(A!#')!.4 ,0>%"**(+EB29 *"(%17 ;$ (?,)', #D0:<,**%&'+*39>37)+)'+#&'><4>)%*+,-!*'"A6&=53 -#,*'.4<C1 @+$(=1C'-6': :8<1 >+C/"A.(:E,'=& @# *?'39".8 88!=6&D =4D6<-?5C56= ?9"@7"D :<&?8I!7?+?; A""0E#+=?=("*G/&8D&?-%B:,1G3?$%:??"E=-&J(%8K3*:;;$$E/* AEE:CHF4)!(.0>H3- .7HCHHCCB?8)0  - 2  5 78 &$:<  " !  '  ++*)('" " "'/ - 2  5 78 &$:<  " !  '  ++*)('" " "'/ - 2  5 78 &$:<  " !  '  ++*)('" " "'/   0833.%"%),-/342+  /02 81$' (,-472012,# ,1"%+ 3,$!$%$&"" ,1*1 5% $$!$ # '$ #  014*7$&$$ %!%&$  8:11-#(#&%!"  !<8 /, "$%& " ""! /: 2/ $%"# )#!#  != !9 %&"&%%' 9"7))'$($! #8 ;0()"#!%&+3 90((&7!04"#('03893..%"%* -326&)%$)'$& ,83- "&/-5747466-'& !!),51$?&%"!->0# %(074885645762/9' !*"''3?/   7'!((#*+8;1 9.$)& "2A5- ".6$)$ ,>3$ " !62(%%)'182 ! (:=+"'$ /  !/?:+!''# ,<81" ")(*,# .;93"' -9@;0 %((4B8*'*!&#")'+! 8<6 ")*(,'+$+$#' &(!6=-%'+()("%&** 7@0 ($"##'$**+") >= &)("$,,&',!:3 &&'#+#!&67 %# ?&!=%: ?%5B93 9?#E )C8A=5H0+H>(4@BC9,),) "*'&244<@7:.'&$6C'-B=* $=B5.;91& .;J3/>A:3/)!*/AE>2!)8<::@B81581/;9:HGDC. ' #/621781.."/   , +         "    -6<65 0 (4A a.)<2- '') +,/ / 2 69,  , +         "    -6<65 0 (4A a.)<2- '') +,/ / 2 69,  , +         "    -6<65 0 (4A a.)<2- '') +,/ / 2 69, 2 #86+  .2  !4+154 3"/+ +.  04$ (.  !5.2  &1'  7 8( 09" "34&:79 %;('22) )149:8:470-*! 76=!4/ %278:<750/*((&,'002:432$2+ 0:-3<0)79;:80% '*/86-/8, #1-7 59*!1:,!#,5::4*!)7=:1#8?2>>6*"(058972,($'&%##(49/&,52(2929685.%#)36:8<;<><:9;><2  +5/2)6?/'3<<633;:<77030646;=@?>?>=83/221.2;>:5+%&569<-  ,79!= "?% *;:+"").363245,   "5>4, #66+:$(=8$ ,554& 00!7=)  29- %149+"9 ;*  97' 9!%6/9)8- 10.--7 &:) =#99)*6( %<< 7*#8*"40#6$98,+&! $;2*=".8&#-)'#( 16= '706,')*")**%" #7' =< $9) '&&(##$* "##!&&'  7.2.(7>-#+&)!&)&'&#&" #> ;(46=*>!'(%"/ &*7:@<6(86D 1473#*''*""-0>?C?A1*38.> 71,>*+&(+!*79B=?55&$ ; &?#?"65 #+,.>?@1-  @3 D%?+.,!:> -E.8/:+*+=.*@(38A&"/#)@-&E&54!B!%&*"8B C2;194 "..& 8A%=5 0D7. (E<@, 0H># .!,?6+J< ,7G.G5 :A'&>13D'/9 >8(D);<*@0/E+-+<&<@(.B/0J9/,I,$8H@# 2(CG=EB?&5!-8,  '')*+,)        & () 6! ) "  ' & !"   ""#$%%%& &% ''((())*)*+,  '')*+,)        & () 6! ) "  ' & !"   ""#$%%%& &% ''((())*)*+,  '')*+,)        & () 6! ) "  ' & !"   ""#$%%%& &% ''((())*)*+,7   !6 - 7<7+  : '5 %/%58;80(!#*4$  7$ ,(*1468987798641.*  : !3  #1  1--+'7 4% $5(5!*20*(2) 0--/)6,2%1*9  6+:+429.+.'(:  ,?07$)# -7  />?3  7. 42)'$/4@8) *;<89053(#"&4=9)9736 $84( !%*),39;73-&!096$ 1AB* /<& !-7:5* .71 '>< 19 '6=9*1@:$ +B05(6:2",:.7(= '26+":B& 2/@ %7<3 ,=&*:; $14& #=* (+04  7*-<2'@;39==87/4'7#:8&.!+:;129+:?7*);/+>, D);C>!,A,3B= (?! /;:" 78 2A"C%%@2)@$ 0=(((&*&!B(/D):6(:' !&,,++!&.D+!:?"*D (?1(,-((')+$*@6)A1!<6>:(,+', '$))#+.&8E2!5?#4?1=1!+.,#%-)+)! % &HB-%B1%G4G1%&!$(+*.)@GG8"5A"<%B3 $%-$0@7?>5B,3>;(-/);>5;?F</@E)(,(%:2)7CG>*#-@((#(C;"1F?FA1&B3 /#:=2@2-9G"A< &+.+('#&9?F4"!58%.,%6GD/#!=2).';HA91#"G!%0,$2<.=@# >5&0.&$'!,E*$)E)+&!J$F+ ''#D$$-@++$E$%B )21/.#L% A."-$!) K#%*@-.#C( &C+.*&F! %&9=.)#J+')F.$I1'H, 0B8'1? =7!?( F" @4 /A(D0?/88))H(?&#<#)A13?:1)-@>8 G*B,%C418+)D;>=7.)#  " &$#"   !  )%:! <"*8< @:="!$&9  #   !!""!#"$#%%''(.)#  " &$#"   !  )%:! <"*8< @:="!$&9  #   !!""!#"$#%%''(.)#  " &$#"   !  )%:! <"*8< @:="!$&9  #   !!""!#"$#%%''('7;) #7 )67   (211:;7%  /&'95,#   $38$) $0) $05;-*     +87' 2." 11:64-&#"+0::.#  *3*  "$1-0237465687:/'   00,!,8684=2"  '/:<9/% 2;    #43#79/85.*0-#/7* 28 (/4:/.,:# "+2:''5+ #01;>=32-.-2085<8-*7$08)+0,0)+ :17")6.   8!&(?0 <(#;5$*  #>3#':0& 0=6 !(?+%%  26@98"%)(# $5/9#62 *  /3A  '<*)! >*6  " (=4")**6+8-559 ()2:=A:6'(** <(6( ,691&97?! =2&771" *#"#%$)'$$)(,!C#(: ;%2<5%('()%&%("%*%#*! 85= #7.$5;&!"-++&+)+("3<.623!?7$%"+&%.$'28&A 07&;/)!#( +='71 "6(,;5!&?'%A54">/&*73"=%+8!?18 &@. 6743 5;"A3 65 >.'H!,F:)GA/<')'D1  .D@-  /C'4;  B'+- #HD3 %5AB5)D2 3426*()-+(CC?JA?7+* #,:?E@*2I% E&?)&,,! (,89/1:D?G?C<>:?@ECG8) =@*!=,9##!,'5> (%/-3.-#*AB/=43$/*,'DD#">E'36@*,/+ DED7% "9?4 >,%=$*)3="6B9=0#!$4;C><',?")-"/< %,=A>D7;12.249C>>63   =?.%)+(#?(  "C!>(&),/)>% %:$H)./+;%!)?-<)0'<!':/@,1/&>!,?65(%*'0-"/=!':97&+;4"'>=+ +- /@!#< ?0 )1+:6#!C#>' )+/A#@#>.(1F0$?/>) 3A$ 7/>1 ?* &-C#8. )<#&#D688/ '69:7*;"6   #"!&!! ! 6 . %;7:9 - * " 5  /)   "// 1 3 5779::;<<====6   #"!&!! ! 6 . %;7:9 - * " 5  /)   "// 1 3 5779::;<<====6   #"!&!! ! 6 . %;7:9 - * " 5  /)   "// 1 3 5779::;<<==== !7!5 ":  )6%//855)  '9&#5*(%&.473& 1= 4(  #/:5#;,-3 &2<3 % 8"  .87" 6   )-14-#/; & 055& 4:&+671 !// 9?$'&%   1:7:4:47+.488  )" !  8-9"  ' ,./3'),&'5'!%#!'(714 "7"43  & ) (2#7,  (()!!()' !(#("#5! (?"))% )'&)43:34213052530+3/.> )!& $%$$'*.'%(.3-*8% 56! !!(('&''* +  $,38( .3 ;8 ",%&)%* (*83<688775397@27, 2@%#'"'%0><>600'+#&#'(043'"-+.$#"("!$)972FC86.//02810*D5 2;?)4+B;6.>/6%H# 8:/9*D9?+:.>;@.;:<5<=(&%$#   $0  ) / 4 &  * &-"     !#$$&&((*-03T(&%$#   $0  ) / 4 &  * &-"     !#$$&&((*-03T(&%$#   $0  ) / 4 &  * &-"     !#$$&&((*-03T3  *62/'8  (4!,3%6)3$*5$:  +2#)5#:  /1,0"5!  4,0- 5#   8%5(  $$..32744&(. !:9!$ 1/9662/,)%&#**239+"4+89=8>5640"  "095 3/'5 !$%  3+ 6$#4%1,% =%4  /.'5!&#'&   65  8!7, (#*  = 9(*7 ?&  7(4*%)2,*;0#' $6 @"#%*) )19&7'* . 7)&*)&##:$55:%%')') : 5.'&&#')(*5 A9#&(  +=@'#$# #$7%A4"'  ,?5;$)%&,":' 2<;" 0:B$8/ )%$+ 01#>+> #-47C :$((<42!@+6>'7*>"')**(";)@"9?!;4)B31''%C94;B  /=-%0=0?3(%81 7-%*&C6"7:#A".A8$$95 9,!(&),-)F-+>)&C)4:<$*@* 9('&"7D.3=*?$.?82(7>C )=D,@./<6:8;'*@.C!,!AD0A#8<4'" <; +9),+$ED>4!<1!5B):2*.&#,)$$!CD;0&C$.5@2 :'*&$/%GE=&+E5B;4@$##&#EG: 9:8:E8$&<-,&6BH1 D27350+),#+ <6D+0C   C1'.&,$2>"B.><C1.))$<,<:0B#-A#! 0C0@;>3: @*EF! @*0ECFJ! $G&.DD-D 8='A#'G=,3.< 'K$ 61 /@84: #H/2A 83 :=1E" B* 0I$ ,? # F( +K( );+ $ E*,J-):5% J$.C2)<8& F&-@6,?6 '<82?92A1(.= <>7%:>'*D3!*J<3!8A1,?H7/!4?L?97=: /9EGMLJ?A,! 2 S"!$  #%  0 & * 2 3 3  3 4 !   0& !& (( *+,/.1 1 3 54797}"!$  #%  0 & * 2 3 3  3 4 !   0& !& (( *+,/.1 1 3 54797}"!$  #%  0 & * 2 3 3  3 4 !   0& !& (( *+,/.1 1 3 54797}   ! "$0.9450-* $$/6;10')!&!((*3.60'$ # /:1$    $+,6/ /4( (3/,"  &32   */2% 19  #'+*))$%  &05!"9. &/8:66-1.144:6=52. (2.";- %1=7+"&-1>76/ /-&>! ,;5+   !)0=8(,7&8)  )95)  )95/$!5# =%0=( %242&&0 8* #:/  #13,26 17 *3/ +<78$ 4 &;$  8@9 #,3' 4@ #9%=+ 9) )"$ )9  13&,%'!$  %8 52('()!))(+  A ,7" #(#(#!%'"#%%&%%'8 A%+&("#&'%%#"'+8 3*('$+((-: +$&!*+%*& !0>( $++*'*$%)*:C5 &&"#+"(6:>7)  *(&+# "/56<@C=:-%)*%%(!-/98>;=:60&" (*+*;=C97(" $-+"8?% !&+,>+&!.! 0>)'"(*(';?( ,+!)@5**)E4*/!8C ,1B;!.$@;..@,01>(1 >:23>44%;D%4%BG166CB498&:}                                ,. 0 1 58J,. 0 1 58J,. 0 1 58J,?9%K@D."K-=LD%01G*8L<2,B1,4;IC$ 4 '?B=7!8 J&)+,./0 3 459 &)+,./0 3 459 &)+,./0 3 459 &,G=975) 6HB*>*HI'!;1++F-2A- 6?,B#.8= &=6/<3"B0 2;="+C232E:9=:3 56158                         I$?e Applications     .gimp-text-layer(text "Applications") (font "Ubuntu Bold") (font-size 17.000000) (font-size-unit pixels) (hinting yes) (autohint yes) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 1.000000 1.000000 1.000000)) (justify left) (box-mode dynamic) (box-unit pixels) 2@e2X6e2l5);zw^@hjhDEE6Y1Ryy}ܧ$F%$F%A29:,+trpZfZf<zCnCn0(Ä51L31L3(/+P3"NY1 {{ޯ߿W 11$szwNjh"Z  F(+_*7LޔChd ޔ*wCjBXM-.E*qD'OJ_[n2 < into the     %gimp-text-layer (text "into the") (font "Ubuntu") (font-size 17.000000) (font-size-unit pixels) (hinting yes) (autohint yes) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 1.000000 1.000000 1.000000)) (justify left) (box-mode dynamic) (box-unit pixels) 8<8<8ttt j%u oo nujmiddddznl~;?+oT8HH.9=.mmjv++/folder     #gimp-text-layer(text "folder") (font "Ubuntu") (font-size 17.000000) (font-size-unit pixels) (hinting yes) (autohint yes) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 1.000000 1.000000 1.000000)) (justify left) (box-mode dynamic) (box-unit pixels) </=/=#}}}n! Gx~!  +dd$z~um~;?&+oT8HH.9=;.ghzjv7hicon     !gimp-text-layer(text "icon") (font "Ubuntu") (font-size 17.000000) (font-size-unit pixels) (hinting yes) (autohint yes) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 1.000000 1.000000 1.000000)) (justify left) (box-mode dynamic) (box-unit pixels) AAA*MMM]ju;Unm;jmi8;?79=l~_u\CCantata     Qgimp-text-layer5(markup "Cantata") (font "Ubuntu Bold") (font-size 17.000000) (font-size-unit pixels) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 0.992157 0.674510 0.450980)) (justify left) (box-mode dynamic) (box-unit pixels) (hinting yes) DnCDMCDM:srrprsr rsr srssrss srssrqrrpss rssrrssrfsrrsrsrssrfssrssrrsspsqssssqsssssmrompssrssqpssrssrompssrssrompsssbjrsrrssrssbsssjrsrrssrssjrsrrsrssmsssssssrssrssptssssrss`rssptssrss`rssptsqssrrfprqrsspmsssssq`mUrsspmsssq`mUrsspmsssrrsssssrrsssrrsssrssrrqqrsrsrrssssrssoqrsrsrrsrssoqrsrsr   ȶȶȶкккгζϪζϪζ߀   '\ O a]? |a|(c0H\0H0H     ): : : <_ M>8`)8`)8y%y%O^~Өx-~Өx-~rfsqsrsrssssssssrsЙ|Ө! To install: drag the     1gimp-text-layer(text "To install: drag the") (font "Ubuntu") (font-size 17.000000) (font-size-unit pixels) (hinting yes) (autohint yes) (antialias yes) (language "en-gb") (base-direction ltr) (color (color-rgb 1.000000 1.000000 1.000000)) (justify left) (box-mode dynamic) (box-unit pixels) OOUUORmU!j!uo dddd % nK܌ jmin8ddxo;?u NQ9=mmme +ܸ7! % $o+܌ f $zum~xoj~ddzn &=NQ ;.gg hzme(wm7hܸ4+,Ws68tj6Dݣ&   aul~+oT8HH..jv)G #_Layer     Vc_VZOZ[Zg_WOW_WoWWWWWWWWWXXX/X?XOX_XoXXXXXXXXXYYY/Y?YOY_YoYYYYYYYYYZZZ/Z? @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @kkkk}W>+cantata-2.2.0/mac/dmg/create-dmg.sh.in000077500000000000000000000022041316350454000174100ustar00rootroot00000000000000#!/bin/sh # This script has been copied, and modified from the orignal create-dmg.sh # in Clemtine 1.2.3 - which in turn was taken from Tomahawk. # # author: max@last.fm, muesli@tomahawk-player.org # brief: Produces a compressed DMG from a bundle directory tempFolder="dmg/@MACOSX_BUNDLE_BUNDLE_NAME@" tempFile=dmg/temp.dmg dmgFileName="@MACOSX_BUNDLE_BUNDLE_NAME@-@CANTATA_VERSION_WITH_SPIN@.dmg" rm -rf "$tempFolder" rm -f "$dmgFileName" "$tempFile" # Create folder structure for disk image mkdir -p "$tempFolder/.background" cp @CMAKE_CURRENT_SOURCE_DIR@/mac/dmg/background.png "$tempFolder/.background/background.png" cp @CMAKE_CURRENT_SOURCE_DIR@/mac/dmg/DS_Store.in "$tempFolder/.DS_Store" chmod go-rwx "$tempFolder/.DS_Store" ln -s /Applications "$tempFolder/Applications" cp -R "@CMAKE_INSTALL_PREFIX@/@MACOSX_BUNDLE_BUNDLE_NAME@.app" "$tempFolder" # Create DMG file hdiutil makehybrid -hfs -hfs-volume-name @MACOSX_BUNDLE_BUNDLE_NAME@ -hfs-openfolder "$tempFolder" "$tempFolder" -o "$tempFile" hdiutil convert -format UDZO -imagekey zlib-level=9 "$tempFile" -o "$dmgFileName" # Remove temporary files rm -rf "$tempFolder" rm -f "$tempFile" cantata-2.2.0/mac/dockmenu.cpp000066400000000000000000000030741316350454000162040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dockmenu.h" #include "gui/stdactions.h" #include "mpd-interface/mpdstatus.h" DockMenu::DockMenu(QWidget *p) : QMenu(p) { setAsDockMenu(); addAction(StdActions::self()->prevTrackAction); playPauseAction=addAction(tr("Play")); addAction(StdActions::self()->stopPlaybackAction); addAction(StdActions::self()->stopAfterCurrentTrackAction); addAction(StdActions::self()->nextTrackAction); connect(playPauseAction, SIGNAL(triggered()), StdActions::self()->playPauseTrackAction, SIGNAL(triggered())); } void DockMenu::update(MPDStatus * const status) { playPauseAction->setEnabled(status->playlistLength()>0); playPauseAction->setText(MPDState_Playing==status->state() ? tr("Pause") : tr("Play")); } cantata-2.2.0/mac/dockmenu.h000066400000000000000000000021671316350454000156530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DOCKMENU_H #define DOCKMENU_H #include class QAction; class MPDStatus; class DockMenu : public QMenu { Q_OBJECT public: DockMenu(QWidget *w); virtual ~DockMenu() { } void update(MPDStatus * const status); private: QAction *playPauseAction; }; #endif cantata-2.2.0/mac/macnotify.h000066400000000000000000000020251316350454000160300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MACNOTIFY_H #define MACNOTIFY_H class QString; class QImage; namespace MacNotify { extern void showMessage(const QString &title, const QString &text, const QImage &img); } #endif cantata-2.2.0/mac/macnotify.mm000066400000000000000000000054411316350454000162170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2016 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "macnotify.h" #include "config.h" #include #include #ifdef QT_MAC_EXTRAS_FOUND #include #include #include #endif #include @interface UserNotificationItem : NSObject { } - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification; @end @implementation UserNotificationItem - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { Q_UNUSED(center); Q_UNUSED(notification); return YES; } @end class UserNotificationItemClass { public: UserNotificationItemClass() { item = [UserNotificationItem alloc]; if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:item]; } } ~UserNotificationItemClass() { if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil]; } [item release]; } UserNotificationItem *item; }; void MacNotify::showMessage(const QString &title, const QString &text, const QImage &img) { if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { static UserNotificationItemClass *n=0; if (!n) { n=new UserNotificationItemClass(); } NSUserNotification *userNotification = [[[NSUserNotification alloc] init] autorelease]; userNotification.title = title.toNSString(); userNotification.informativeText = text.toNSString(); #ifdef QT_MAC_EXTRAS_FOUND userNotification.contentImage = QtMac::toNSImage(QPixmap::fromImage(img)); #else Q_UNUSED(img) #endif [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; } } cantata-2.2.0/mac/powermanagement.cpp000066400000000000000000000043501316350454000175660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "powermanagement.h" #include "support/globalstatic.h" #include "mpd-interface/mpdstatus.h" #include #include #include GLOBAL_STATIC(PowerManagement, instance) static void powerEventCallback(void *rootPort, io_service_t, natural_t msg, void *arg) { switch (msg) { case kIOMessageSystemWillSleep: IOAllowPowerChange(*(io_connect_t *) rootPort, (long)arg); break; case kIOMessageSystemHasPoweredOn: PowerManagement::self()->emitResuming(); break; case kIOMessageCanSystemSleep: if (PowerManagement::self()->inhibitSuspend() && MPDState_Playing==MPDStatus::self()->state()) { IOCancelPowerChange(*(io_connect_t *) rootPort, (long)arg); } else { IOAllowPowerChange(*(io_connect_t *) rootPort, (long)arg); } break; default: break; } } PowerManagement::PowerManagement() : inhibitSuspendWhilstPlaying(false) { static io_connect_t rootPort; IONotificationPortRef notificationPort; io_object_t notifier; rootPort = IORegisterForSystemPower(&rootPort, ¬ificationPort, powerEventCallback, ¬ifier); if (rootPort) { CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode); } } void PowerManagement::emitResuming() { emit resuming(); } cantata-2.2.0/mac/powermanagement.h000066400000000000000000000024371316350454000172370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef POWERMANAGEMENT_H #define POWERMANAGEMENT_H #include class PowerManagement : public QObject { Q_OBJECT public: static PowerManagement * self(); PowerManagement(); void setInhibitSuspend(bool i) { inhibitSuspendWhilstPlaying=i; } bool inhibitSuspend() const { return inhibitSuspendWhilstPlaying; } void emitResuming(); Q_SIGNALS: void resuming(); private: bool inhibitSuspendWhilstPlaying; }; #endif cantata-2.2.0/models/000077500000000000000000000000001316350454000144125ustar00rootroot00000000000000cantata-2.2.0/models/actionmodel.cpp000066400000000000000000000026261316350454000174220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "actionmodel.h" #include "gui/stdactions.h" #include "roles.h" QVariant ActionModel::data(const QModelIndex &index, int role) const { QVariant v; Q_UNUSED(index) switch(role) { case Cantata::Role_Actions: v.setValue >(QList() << StdActions::self()->replacePlayQueueAction << StdActions::self()->appendToPlayQueueAction); break; case Cantata::Role_RatingCol: return -1; case Cantata::Role_TitleText: return data(index, Qt::DisplayRole); default: break; } return v; } cantata-2.2.0/models/actionmodel.h000066400000000000000000000025251316350454000170650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ACTIONMODEL_H #define ACTIONMODEL_H #include #include #include "support/action.h" #include "config.h" class ActionModel : public QAbstractItemModel { Q_OBJECT public: ActionModel(QObject *p=0) : QAbstractItemModel(p) { } virtual ~ActionModel() { } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual void resetModel() { beginResetModel(); endResetModel(); } }; Q_DECLARE_METATYPE(QList) #endif cantata-2.2.0/models/browsemodel.cpp000066400000000000000000000227501316350454000174460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "browsemodel.h" #include "roles.h" #include "playqueuemodel.h" #include "widgets/icons.h" #include "gui/settings.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdstats.h" #include void BrowseModel::FolderItem::add(Item *i) { i->setRow(children.count()); children.append(i); } QStringList BrowseModel::FolderItem::allEntries(bool allowPlaylists) const { QStringList entries; if (children.isEmpty()) { entries << MPDConnection::constDirPrefix+path; } else { foreach (Item *i, children) { if (i->isFolder()) { entries+=static_cast(i)->allEntries(allowPlaylists); } else if (allowPlaylists || Song::Playlist!=static_cast(i)->getSong().type) { entries+=static_cast(i)->getSong().file; } } } return entries; } BrowseModel::BrowseModel(QObject *p) : ActionModel(p) , root(new FolderItem("/", 0)) , enabled(false) , dbVersion(0) { connect(this, SIGNAL(listFolder(QString)), MPDConnection::self(), SLOT(listFolder(QString))); folderIndex.insert(root->getPath(), root); } void BrowseModel::clear() { beginResetModel(); root->clear(); folderIndex.clear(); folderIndex.insert(root->getPath(), root); endResetModel(); } void BrowseModel::load() { if (!enabled || (root && (root->getChildCount() || root->isFetching()))) { return; } root->setState(FolderItem::State_Fetching); emit listFolder(root->getPath()); } void BrowseModel::setEnabled(bool e) { if (e==enabled) { return; } enabled=e; if (enabled) { connect(MPDConnection::self(), SIGNAL(folderContents(QString,QStringList,QList)), this, SLOT(folderContents(QString,QStringList,QList))); connect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged())); connect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues))); } else { disconnect(MPDConnection::self(), SIGNAL(folderContents(QString,QStringList,QList)), this, SLOT(folderContents(QString,QStringList,QList))); disconnect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged())); disconnect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues))); clear(); } } Qt::ItemFlags BrowseModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex BrowseModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const FolderItem * p = parent.isValid() ? static_cast(parent.internalPointer()) : root; const Item * c = rowgetChildCount() ? p->getChildren().at(row) : 0; return c ? createIndex(row, column, (void *)c) : QModelIndex(); } QModelIndex BrowseModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } const Item * const item = static_cast(child.internalPointer()); Item * const parentItem = item->getParent(); if (parentItem == root || 0==parentItem) { return QModelIndex(); } return createIndex(parentItem->getRow(), 0, parentItem); } int BrowseModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } const FolderItem *parentItem=parent.isValid() ? static_cast(parent.internalPointer()) : root; return parentItem ? parentItem->getChildCount() : 0; } int BrowseModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } bool BrowseModel::hasChildren(const QModelIndex &index) const { Item *item=toItem(index); return item && item->isFolder(); } bool BrowseModel::canFetchMore(const QModelIndex &index) const { if (index.isValid()) { Item *item = toItem(index); return item && item->isFolder() && static_cast(item)->canFetchMore(); } else { return false; } } void BrowseModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } FolderItem *item = static_cast(toItem(index)); if (!item->isFetching()) { item->setState(FolderItem::State_Fetching); emit listFolder(item->getPath()); } } QVariant BrowseModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } Item *item = static_cast(index.internalPointer()); switch (role) { case Qt::DecorationRole: if (item->isFolder()) { return Icons::self()->folderListIcon; } else { TrackItem *track = static_cast(item); return Song::Playlist==track->getSong().type ? Icons::self()->playlistListIcon : Icons::self()->audioListIcon; } break; case Cantata::Role_BriefMainText: case Cantata::Role_MainText: case Qt::DisplayRole: if (!item->isFolder()) { TrackItem *track = static_cast(item); if (Song::Playlist==track->getSong().type) { return Utils::getFile(track->getSong().file); } } return item->getText(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } if (item->isFolder() || Song::Playlist==static_cast(item)->getSong().type) { return static_cast(item)->getPath(); } return static_cast(item)->getSong().toolTip(); case Cantata::Role_SubText: if (!item->isFolder()) { TrackItem *track = static_cast(item); if (Song::Playlist==track->getSong().type) { return track->getSong().isCueFile() ? tr("Cue Sheet") : tr("Playlist"); } } return item->getSubText(); case Cantata::Role_TitleText: return item->getText(); case Cantata::Role_TitleActions: return item->isFolder(); default: break; } return ActionModel::data(index, role); } QMimeData * BrowseModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QStringList files; foreach (const QModelIndex &idx, indexes) { Item *item=toItem(idx); if (item) { if (item->isFolder()) { files+=static_cast(item)->allEntries(false); } else { files.append(static_cast(item)->getSong().file); } } } PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files); return mimeData; } QList BrowseModel::songs(const QModelIndexList &indexes, bool allowPlaylists) const { QList songList; foreach (const QModelIndex &idx, indexes) { Item *item=toItem(idx); if (item && !item->isFolder() && (allowPlaylists || Song::Standard==static_cast(item)->getSong().type)) { songList.append(static_cast(item)->getSong()); } } return songList; } void BrowseModel::connectionChanged() { clear(); if (!root->isFetching() && MPDConnection::self()->isConnected()) { dbVersion=0; root->setState(FolderItem::State_Fetching); emit listFolder(root->getPath()); } } void BrowseModel::statsUpdated(const MPDStatsValues &stats) { if (stats.dbUpdate!=dbVersion) { if (0!=dbVersion) { connectionChanged(); } dbVersion=stats.dbUpdate; } } void BrowseModel::folderContents(const QString &path, const QStringList &folders, const QList &songs) { QMap::Iterator it=folderIndex.find(path); if (it==folderIndex.end() || 0!=it.value()->getChildCount()) { return; } if (folders.count() + songs.count()) { QModelIndex idx=it.value()==root ? QModelIndex() : createIndex(it.value()->getRow(), 0, it.value()); beginInsertRows(idx, 0, folders.count() + songs.count() - 1); foreach(const QString &folder, folders) { FolderItem *item = new FolderItem(folder.split("/", QString::SkipEmptyParts).last(), folder, it.value()); it.value()->add(item); folderIndex.insert(folder, item); } foreach(const Song &song, songs) { it.value()->add(new TrackItem(song, it.value())); } it.value()->setState(FolderItem::State_Fetched); endInsertRows(); } } cantata-2.2.0/models/browsemodel.h000066400000000000000000000110501316350454000171020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BROWSE_MODEL_H #define BROWSE_MODEL_H #include "actionmodel.h" #include "mpd-interface/song.h" #include "support/utils.h" #include struct MPDStatsValues; class BrowseModel : public ActionModel { Q_OBJECT public: class FolderItem; class Item { public: Item(FolderItem *p=0) : parent(p), row(0) { } virtual ~Item() { } virtual int getChildCount() const { return 0; } virtual bool isFolder() const { return false; } virtual QString getText() const =0; virtual QString getSubText() const =0; FolderItem * getParent() const { return parent; } int getRow() const { return row; } void setRow(int r) { row=r; } private: FolderItem *parent; int row; }; class TrackItem : public Item { public: TrackItem(const Song &s, FolderItem *p=0) : Item(p) , song(s) { } virtual ~TrackItem() { } virtual QString getText() const { return song.trackAndTitleStr(); } virtual QString getSubText() const { return Song::Playlist==song.type || 0==song.time ? QString() : Utils::formatTime(song.time, true); } const Song & getSong() const { return song; } private: Song song; }; class FolderItem : public Item { public: enum State { State_Initial, State_Fetching, State_Fetched }; FolderItem(const QString &n, const QString &pth, FolderItem *p=0) : Item(p), name(n), path(pth), state(State_Initial) { } virtual ~FolderItem() { } void clear() { qDeleteAll(children); children.clear(); state=State_Initial; } const QList getChildren() const { return children; } virtual int getChildCount() const { return children.count();} virtual bool isFolder() const { return true; } void add(Item *i); virtual QString getText() const { return name; } virtual QString getSubText() const { return QString(); } const QString & getPath() const { return path; } bool canFetchMore() const { return State_Initial==state; } bool isFetching() const { return State_Fetching==state; } void setState(State s) { state=s; } QStringList allEntries(bool allowPlaylists) const; private: QString name; QString path; QList children; State state; }; BrowseModel(QObject *p); void clear(); void load(); bool isEnabled() const { return enabled; } void setEnabled(bool e); Qt::ItemFlags flags(const QModelIndex &index) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; bool hasChildren(const QModelIndex &index) const; bool canFetchMore(const QModelIndex &index) const; void fetchMore(const QModelIndex &index); QVariant data(const QModelIndex &index, int role) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QList songs(const QModelIndexList &indexes, bool allowPlaylists) const; Q_SIGNALS: void listFolder(const QString &path); private Q_SLOTS: void connectionChanged(); void statsUpdated(const MPDStatsValues &stats); void folderContents(const QString &path, const QStringList &folders, const QList &songs); private: Item * toItem(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : root; } private: FolderItem *root; QMap folderIndex; bool enabled; time_t dbVersion; }; #endif cantata-2.2.0/models/devicesmodel.cpp000066400000000000000000001026351316350454000175700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "musiclibraryitemalbum.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemsong.h" #include "musiclibraryitemroot.h" #include "devicesmodel.h" #include "playqueuemodel.h" #include "gui/settings.h" #include "roles.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdconnection.h" #include "devices/umsdevice.h" #include "http/httpserver.h" #include "widgets/icons.h" #include "widgets/mirrormenu.h" #include "devices/mountpoints.h" #include "gui/stdactions.h" #include "support/action.h" #include "config.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "devices/audiocddevice.h" #endif #include "support/globalstatic.h" #include #include #include #include "solid-lite/device.h" #include "solid-lite/deviceinterface.h" #include "solid-lite/devicenotifier.h" #include "solid-lite/portablemediaplayer.h" #include "solid-lite/storageaccess.h" #include "solid-lite/storagedrive.h" #include "solid-lite/storagevolume.h" #include "solid-lite/opticaldisc.h" #include static bool debugIsEnabled=false; #define DBUG if (debugIsEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void DevicesModel::enableDebug() { debugIsEnabled=true; } bool DevicesModel::debugEnabled() { return debugIsEnabled; } QString DevicesModel::fixDevicePath(const QString &path) { // Remove MTP IDs, and display storage... if (path.startsWith(QChar('{')) && path.contains(QChar('}'))) { int end=path.indexOf(QChar('}')); QStringList details=path.mid(1, end-1).split(QChar('/')); if (details.length()>3) { return QChar('(')+details.at(3)+QLatin1String(") ")+path.mid(end+1); } return path.mid(end+1); } return path; } GLOBAL_STATIC(DevicesModel, instance) DevicesModel::DevicesModel(QObject *parent) : MusicLibraryModel(parent) , itemMenu(0) , enabled(false) , inhibitMenuUpdate(false) { configureAction = new Action(Icons::self()->configureIcon, tr("Configure Device"), this); refreshAction = new Action(Icons::self()->reloadIcon, tr("Refresh Device"), this); connectAction = new Action(Icons::self()->connectIcon, tr("Connect Device"), this); disconnectAction = new Action(Icons::self()->disconnectIcon, tr("Disconnect Device"), this); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND editAction = new Action(Icons::self()->editIcon, tr("Edit CD Details"), this); #endif updateItemMenu(); connect(this, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool))); } DevicesModel::~DevicesModel() { qDeleteAll(collections); } QModelIndex DevicesModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } if (parent.isValid()) { MusicLibraryItem *p=static_cast(parent.internalPointer()); if (p) { return rowchildCount() ? createIndex(row, column, p->childItem(row)) : QModelIndex(); } } else { return row(index.internalPointer()); MusicLibraryItem *parentItem = childItem->parentItem(); if (parentItem) { return createIndex(parentItem->parentItem() ? parentItem->row() : row(parentItem), 0, parentItem); } else { return QModelIndex(); } } int DevicesModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } return parent.isValid() ? static_cast(parent.internalPointer())->childCount() : collections.count(); } void DevicesModel::getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres) { foreach (MusicLibraryItemRoot *col, collections) { col->getDetails(artists, albumArtists, composers, albums, genres); } } int DevicesModel::indexOf(const QString &id) { int i=0; foreach (MusicLibraryItemRoot *col, collections) { if (col->id()==id) { return i; } i++; } return -1; } QList DevicesModel::songs(const QModelIndexList &indexes, bool playableOnly, bool fullPath) const { QMap > colSongs; QMap > colFiles; foreach(QModelIndex index, indexes) { MusicLibraryItem *item = static_cast(index.internalPointer()); MusicLibraryItem *p=item; while (p->parentItem()) { p=p->parentItem(); } if (!p) { continue; } MusicLibraryItemRoot *parent=static_cast(p); if (playableOnly && !parent->canPlaySongs()) { continue; } switch (item->itemType()) { case MusicLibraryItem::Type_Root: { if (static_cast(parent)->flat()) { foreach (const MusicLibraryItem *song, static_cast(item)->childItems()) { if (MusicLibraryItem::Type_Song==song->itemType() && !colFiles[parent].contains(static_cast(song)->file())) { colSongs[parent] << parent->fixPath(static_cast(song)->song(), fullPath); colFiles[parent] << static_cast(song)->file(); } } } else { // First, sort all artists as they would appear in UI... QList artists=static_cast(item)->childItems(); if (artists.isEmpty()) { break; } qSort(artists.begin(), artists.end(), MusicLibraryItemArtist::lessThan); foreach (MusicLibraryItem *a, artists) { const MusicLibraryItemContainer *artist=static_cast(a); // Now sort all albums as they would appear in UI... QList artistAlbums=artist->childItems(); qSort(artistAlbums.begin(), artistAlbums.end(), MusicLibraryItemAlbum::lessThan); foreach (MusicLibraryItem *i, artistAlbums) { const MusicLibraryItemContainer *album=static_cast(i); foreach (const MusicLibraryItem *song, album->childItems()) { if (MusicLibraryItem::Type_Song==song->itemType() && !colFiles[parent].contains(static_cast(song)->file())) { colSongs[parent] << parent->fixPath(static_cast(song)->song(), fullPath); colFiles[parent] << static_cast(song)->file(); } } } } } break; } case MusicLibraryItem::Type_Artist: { // First, sort all albums as they would appear in UI... QList artistAlbums=static_cast(item)->childItems(); qSort(artistAlbums.begin(), artistAlbums.end(), MusicLibraryItemAlbum::lessThan); foreach (MusicLibraryItem *i, artistAlbums) { const MusicLibraryItemContainer *album=static_cast(i); foreach (const MusicLibraryItem *song, album->childItems()) { if (MusicLibraryItem::Type_Song==song->itemType() && !colFiles[parent].contains(static_cast(song)->file())) { colSongs[parent] << parent->fixPath(static_cast(song)->song(), fullPath); colFiles[parent] << static_cast(song)->file(); } } } break; } case MusicLibraryItem::Type_Album: foreach (const MusicLibraryItem *song, static_cast(item)->childItems()) { if (MusicLibraryItem::Type_Song==song->itemType() && !colFiles[parent].contains(static_cast(song)->file())) { colSongs[parent] << parent->fixPath(static_cast(song)->song(), fullPath); colFiles[parent] << static_cast(song)->file(); } } break; case MusicLibraryItem::Type_Song: if (!colFiles[parent].contains(static_cast(item)->file())) { colSongs[parent] << parent->fixPath(static_cast(item)->song(), fullPath); colFiles[parent] << static_cast(item)->file(); } break; default: break; } } QList songs; QMap >::Iterator it(colSongs.begin()); QMap >::Iterator end(colSongs.end()); for (; it!=end; ++it) { songs.append(it.value()); } return songs; } QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool playableOnly, bool fullPath) const { QList songList=songs(indexes, playableOnly, fullPath); QStringList fnames; foreach (const Song &s, songList) { fnames.append(s.file); } return fnames; } bool DevicesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (Cantata::Role_Image==role) { MusicLibraryItem *item = static_cast(index.internalPointer()); if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (Device::AudioCd==dev->devType()) { static_cast(dev)->scaleCoverPix(value.toInt()); return true; } } } #endif return MusicLibraryModel::setData(index, value, role); } QVariant DevicesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } MusicLibraryItem *item = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: if (MusicLibraryItem::Type_Song==item->itemType()) { MusicLibraryItemSong *song = static_cast(item); if (MusicLibraryItem::Type_Root==song->parentItem()->itemType()) { return song->song().trackAndTitleStr(); } } break; #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND case Cantata::Role_Image: if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (Device::AudioCd==dev->devType()) { return static_cast(dev)->coverPix(); } } break; #endif case Cantata::Role_SubText: if (MusicLibraryItem::Type_Root==item->itemType()) { Device *dev=static_cast(item); if (!dev->statusMessage().isEmpty()) { return dev->statusMessage(); } if (!dev->isConnected()) { QString sub=dev->subText(); return tr("Not Connected")+(sub.isEmpty() ? QString() : (QString(" – ")+sub)); } if (Device::AudioCd==dev->devType()) { return dev->subText(); } } break; case Cantata::Role_Capacity: if (MusicLibraryItem::Type_Root==item->itemType()) { return static_cast(item)->usedCapacity(); } return QVariant(); case Cantata::Role_CapacityText: if (MusicLibraryItem::Type_Root==item->itemType()) { return static_cast(item)->capacityString(); } return QVariant(); case Cantata::Role_Actions: { QVariant v; if (MusicLibraryItem::Type_Root==item->itemType()) { QList actions; if (Device::AudioCd!=static_cast(item)->devType()) { actions << configureAction; } else if (HttpServer::self()->isAlive()) { actions << StdActions::self()->replacePlayQueueAction; } actions << refreshAction; if (static_cast(item)->supportsDisconnect()) { actions << (static_cast(item)->isConnected() ? disconnectAction : connectAction); } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (Device::AudioCd==static_cast(item)->devType()) { actions << editAction; } #endif v.setValue >(actions); } else if (root(item)->canPlaySongs() && HttpServer::self()->isAlive()) { v.setValue >(QList() << StdActions::self()->replacePlayQueueAction << StdActions::self()->appendToPlayQueueAction); } return v; } case Cantata::Role_ListImage: return MusicLibraryItem::Type_Album==item->itemType(); default: break; } return MusicLibraryModel::data(index, role); } void DevicesModel::clear(bool clearConfig) { inhibitMenuUpdate=true; QSet remoteUdis; QSet udis; foreach (MusicLibraryItemRoot *col, collections) { Device *dev=static_cast(col); if (Device::RemoteFs==dev->devType()) { remoteUdis.insert(dev->id()); } else { udis.insert(dev->id()); } } foreach (const QString &u, udis) { deviceRemoved(u); } foreach (const QString &u, remoteUdis) { removeRemoteDevice(u, clearConfig); } collections.clear(); volumes.clear(); inhibitMenuUpdate=false; updateItemMenu(); } void DevicesModel::setEnabled(bool e) { StdActions::self()->copyToDeviceAction->setVisible(e); if (e==enabled) { return; } enabled=e; inhibitMenuUpdate=true; if (enabled) { connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(const QString &)), this, SLOT(deviceAdded(const QString &))); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(const QString &)), this, SLOT(deviceRemoved(const QString &))); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(setCover(const Song &, const QImage &, const QString &))); #endif // Call loadLocal via a timer, so that upon Cantata start-up model is loaded into view before we try and expand items! QTimer::singleShot(0, this, SIGNAL(loadLocal())); connect(MountPoints::self(), SIGNAL(updated()), this, SLOT(mountsChanged())); #ifdef ENABLE_REMOTE_DEVICES loadRemote(); #endif } else { stop(); clear(false); } inhibitMenuUpdate=false; updateItemMenu(); } void DevicesModel::stop() { foreach (MusicLibraryItemRoot *col, collections) { static_cast(col)->stop(); } disconnect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(const QString &)), this, SLOT(deviceAdded(const QString &))); disconnect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(const QString &)), this, SLOT(deviceRemoved(const QString &))); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND disconnect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(setCover(const Song &, const QImage &, const QString &))); #endif disconnect(MountPoints::self(), SIGNAL(updated()), this, SLOT(mountsChanged())); #if defined ENABLE_REMOTE_DEVICES unmountRemote(); #endif } Device * DevicesModel::device(const QString &udi) { int idx=indexOf(udi); return idx<0 ? 0 : static_cast(collections.at(idx)); } void DevicesModel::setCover(const Song &song, const QImage &img, const QString &file) { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND DBUG << "Set CDDA cover" << song.file << img.isNull() << file << song.isCdda(); if (song.isCdda()) { int idx=indexOf(song.title); if (idx>=0) { Device *dev=static_cast(collections.at(idx)); if (Device::AudioCd==dev->devType()) { DBUG << "Set cover of CD"; Covers::self()->updateCover(song, img, file); static_cast(dev)->setCover(song, img, file); } } } #else Q_UNUSED(song) Q_UNUSED(img) Q_UNUSED(file) #endif } void DevicesModel::setCover(const Song &song, const QImage &img) { DBUG << "Set album cover" << song.file << img.isNull(); if (img.isNull()) { return; } Device *dev=qobject_cast(sender()); if (!dev) { return; } int i=collections.indexOf(dev); if (i<0) { return; } MusicLibraryItemArtist *artistItem = dev->artist(song, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(song, false); if (albumItem) { DBUG << "Set cover of album"; Covers::self()->updateCover(song, img, QString()); QModelIndex idx=index(albumItem->row(), 0, index(artistItem->row(), 0, index(i, 0, QModelIndex()))); emit dataChanged(idx, idx); } } } void DevicesModel::deviceUpdating(const QString &udi, bool state) { int idx=indexOf(udi); if (idx>=0) { Device *dev=static_cast(collections.at(idx)); if (state) { QModelIndex modelIndex=createIndex(idx, 0, dev); emit dataChanged(modelIndex, modelIndex); } else { if (dev->haveUpdate()) { dev->applyUpdate(); } QModelIndex modelIndex=createIndex(idx, 0, dev); emit dataChanged(modelIndex, modelIndex); emit updated(modelIndex); } } } Qt::ItemFlags DevicesModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; } return Qt::NoItemFlags; } QStringList DevicesModel::playableUrls(const QModelIndexList &indexes) const { QList songList=songs(indexes, true, true); QStringList urls; foreach (const Song &s, songList) { QByteArray encoded=HttpServer::self()->encodeUrl(s); if (!encoded.isEmpty()) { urls.append(encoded); } } return urls; } void DevicesModel::emitAddToDevice() { QAction *act=qobject_cast(sender()); if (act) { emit addToDevice(act->data().toString()); } } void DevicesModel::deviceAdded(const QString &udi) { if (indexOf(udi)>=0) { return; } Solid::Device device(udi); DBUG << "Solid device added udi:" << device.udi() << "product:" << device.product() << "vendor:" << device.vendor(); Solid::StorageAccess *ssa =0; #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND Solid::OpticalDisc * opt = device.as(); if (opt && (opt->availableContent()&Solid::OpticalDisc::Audio)) { DBUG << "device is audiocd"; } else #endif if ((ssa=device.as())) { if ((!device.parent().as() || Solid::StorageDrive::Usb!=device.parent().as()->bus()) && (!device.as() || Solid::StorageDrive::Usb!=device.as()->bus())) { DBUG << "Found Solid::StorageAccess that is not usb, skipping"; return; } DBUG << "volume is generic storage"; if (!volumes.contains(device.udi())) { connect(ssa, SIGNAL(accessibilityChanged(bool, const QString&)), this, SLOT(accessibilityChanged(bool, const QString&))); volumes.insert(device.udi()); } } else if (device.is()) { DBUG << "device is a Storage drive, still need a volume"; } else if (device.is()) { DBUG << "device is a PMP"; } else { DBUG << "device not handled"; return; } addLocalDevice(device.udi()); } void DevicesModel::addLocalDevice(const QString &udi) { if (device(udi)) { return; } Device *dev=Device::create(this, udi); if (dev) { beginInsertRows(QModelIndex(), collections.count(), collections.count()); collections.append(dev); endInsertRows(); connect(dev, SIGNAL(updating(const QString &, bool)), SLOT(deviceUpdating(const QString &, bool))); connect(dev, SIGNAL(error(const QString &)), SIGNAL(error(const QString &))); connect(dev, SIGNAL(cover(const Song &, const QImage &)), SLOT(setCover(const Song &, const QImage &))); connect(dev, SIGNAL(updatedDetails(QList)), SIGNAL(updatedDetails(QList))); connect(dev, SIGNAL(play(QList)), SLOT(play(QList))); connect(dev, SIGNAL(renamed()), this, SLOT(updateItemMenu())); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (Device::AudioCd==dev->devType()) { connect(static_cast(dev), SIGNAL(matches(const QString &, const QList &)), SIGNAL(matches(const QString &, const QList &))); if (!autoplayCd.isEmpty() && static_cast(dev)->isAudioDevice(autoplayCd)) { autoplayCd=QString(); static_cast(dev)->autoplay(); } } #endif updateItemMenu(); } } void DevicesModel::deviceRemoved(const QString &udi) { int idx=indexOf(udi); DBUG << "Solid device removed udi = " << udi << idx; if (idx>=0) { if (volumes.contains(udi)) { Solid::Device device(udi); Solid::StorageAccess *ssa = device.as(); if (ssa) { disconnect(ssa, SIGNAL(accessibilityChanged(bool, const QString&)), this, SLOT(accessibilityChanged(bool, const QString&))); } volumes.remove(udi); } beginRemoveRows(QModelIndex(), idx, idx); Device *dev=static_cast(collections.takeAt(idx)); dev->deleteLater(); #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND if (Device::AudioCd==dev->devType()) { static_cast(dev)->dequeue(); } #endif endRemoveRows(); updateItemMenu(); } } void DevicesModel::accessibilityChanged(bool accessible, const QString &udi) { Q_UNUSED(accessible) int idx=indexOf(udi); DBUG << "Solid device accesibility changed udi = " << udi << idx << accessible; if (idx>=0) { Device *dev=static_cast(collections.at(idx)); if (dev) { dev->connectionStateChanged(); QModelIndex modelIndex=createIndex(idx, 0, dev); emit dataChanged(modelIndex, modelIndex); } } } void DevicesModel::addRemoteDevice(const DeviceOptions &opts, RemoteFsDevice::Details details) { #ifdef ENABLE_REMOTE_DEVICES Device *dev=RemoteFsDevice::create(this, opts, details); if (dev) { beginInsertRows(QModelIndex(), collections.count(), collections.count()); collections.append(dev); endInsertRows(); connect(dev, SIGNAL(updating(const QString &, bool)), SLOT(deviceUpdating(const QString &, bool))); connect(dev, SIGNAL(error(const QString &)), SIGNAL(error(const QString &))); connect(dev, SIGNAL(cover(const Song &, const QImage &)), SLOT(setCover(const Song &, const QImage &))); if (Device::RemoteFs==dev->devType()) { connect(static_cast(dev), SIGNAL(udiChanged()), SLOT(remoteDeviceUdiChanged())); } updateItemMenu(); } #else Q_UNUSED(opts) Q_UNUSED(details) #endif } void DevicesModel::removeRemoteDevice(const QString &udi, bool removeFromConfig) { #ifdef ENABLE_REMOTE_DEVICES int idx=indexOf(udi); if (idx<0) { return; } Device *dev=static_cast(collections.at(idx)); if (dev && Device::RemoteFs==dev->devType()) { beginRemoveRows(QModelIndex(), idx, idx); // Remove device from list, but do NOT delete - it may be scanning!!!! collections.takeAt(idx); endRemoveRows(); updateItemMenu(); RemoteFsDevice *rfs=qobject_cast(dev); if (rfs) { // Destroy will stop device, and delete it (via deleteLater()) rfs->destroy(removeFromConfig); } } #else Q_UNUSED(udi) Q_UNUSED(removeFromConfig) #endif } void DevicesModel::remoteDeviceUdiChanged() { #ifdef ENABLE_REMOTE_DEVICES updateItemMenu(); #endif } void DevicesModel::mountsChanged() { #ifdef ENABLE_REMOTE_DEVICES foreach (MusicLibraryItemRoot *col, collections) { Device *dev=static_cast(col); if (Device::RemoteFs==dev->devType() && ((RemoteFsDevice *)dev)->getDetails().isLocalFile()) { if (0==dev->childCount()) { ((RemoteFsDevice *)dev)->load(); } else if (!dev->isConnected()) { ((RemoteFsDevice *)dev)->clear(); } } } #endif // For some reason if a device without a partition (e.g. /dev/sdc) is mounted whilst cantata is running, then we receive no deviceAdded signal // So, as a work-around, each time a device is mounted - check for all local collections. :-) // BUG:127 loadLocal(); } void DevicesModel::loadLocal() { // Build set of currently known MTP/UMS collections... QSet existingUdis; foreach (MusicLibraryItemRoot *col, collections) { Device *dev=static_cast(col); if (Device::Mtp==dev->devType() || Device::Ums==dev->devType()) { existingUdis.insert(dev->id()); } } QList deviceList = Solid::Device::listFromType(Solid::DeviceInterface::PortableMediaPlayer); foreach (const Solid::Device &device, deviceList) { if (existingUdis.contains(device.udi())) { existingUdis.remove(device.udi()); continue; } if (device.as()) { DBUG << "Solid PMP that is also a StorageDrive, skipping, udi:" << device.udi() << "product:" << device.product() << "vendor:" << device.vendor(); continue; } DBUG << "Solid::PortableMediaPlayer with udi:" << device.udi() << "product:" << device.product() << "vendor:" << device.vendor(); addLocalDevice(device.udi()); } deviceList = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach (const Solid::Device &device, deviceList) { if (existingUdis.contains(device.udi())) { existingUdis.remove(device.udi()); continue; } DBUG << "Solid::StorageAccess with udi:" << device.udi() << "product:" << device.product() << "vendor:" << device.vendor(); const Solid::StorageAccess *ssa = device.as(); if (ssa) { if ((!device.parent().as() || Solid::StorageDrive::Usb!=device.parent().as()->bus()) && (!device.as() || Solid::StorageDrive::Usb!=device.as()->bus())) { DBUG << "Solid::StorageAccess that is not usb, skipping"; continue; } if (!volumes.contains(device.udi())) { connect(ssa, SIGNAL(accessibilityChanged(bool, const QString&)), this, SLOT(accessibilityChanged(bool, const QString&))); volumes.insert(device.udi()); } addLocalDevice(device.udi()); } } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND deviceList = Solid::Device::listFromType(Solid::DeviceInterface::OpticalDisc); foreach (const Solid::Device &device, deviceList) { if (existingUdis.contains(device.udi())) { existingUdis.remove(device.udi()); continue; } DBUG << "Solid::OpticalDisc with udi:" << device.udi() << "product:" << device.product() << "vendor:" << device.vendor(); const Solid::OpticalDisc * opt = device.as(); if (opt && (opt->availableContent()&Solid::OpticalDisc::Audio)) { addLocalDevice(device.udi()); } else { DBUG << "Solid::OpticalDisc that is not audio, skipping"; } } #endif // Remove any previous MTP/UMS devices that were not listed above. // This is to fix BUG:127 foreach (const QString &udi, existingUdis) { deviceRemoved(udi); } } #ifdef ENABLE_REMOTE_DEVICES void DevicesModel::loadRemote() { QList rem=RemoteFsDevice::loadAll(this); if (rem.count()) { beginInsertRows(QModelIndex(), collections.count(), collections.count()+(rem.count()-1)); foreach (Device *dev, rem) { collections.append(dev); connect(dev, SIGNAL(updating(const QString &, bool)), SLOT(deviceUpdating(const QString &, bool))); connect(dev, SIGNAL(error(const QString &)), SIGNAL(error(const QString &))); connect(dev, SIGNAL(cover(const Song &, const QImage &)), SLOT(setCover(const Song &, const QImage &))); if (Device::RemoteFs==dev->devType()) { connect(static_cast(dev), SIGNAL(udiChanged()), SLOT(remoteDeviceUdiChanged())); } } endInsertRows(); updateItemMenu(); } } void DevicesModel::unmountRemote() { foreach (MusicLibraryItemRoot *col, collections) { Device *dev=static_cast(col); if (Device::RemoteFs==dev->devType()) { static_cast(dev)->unmount(); } } } #endif #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND void DevicesModel::playCd(const QString &dev) { foreach (MusicLibraryItemRoot *col, collections) { Device *d=static_cast(col); if (Device::AudioCd==d->devType() && static_cast(d)->isAudioDevice(dev)) { static_cast(d)->autoplay(); return; } } autoplayCd=dev; } #endif static bool lessThan(const QString &left, const QString &right) { return left.localeAwareCompare(right)<0; } void DevicesModel::updateItemMenu() { if (inhibitMenuUpdate) { return; } if (!itemMenu) { itemMenu = new MirrorMenu(0); } itemMenu->clear(); if (!collections.isEmpty()) { QMap items; foreach (const MusicLibraryItemRoot *d, collections) { if (Device::AudioCd!=static_cast(d)->devType()) { items.insert(d->data(), d); } } QStringList keys=items.keys(); qSort(keys.begin(), keys.end(), lessThan); foreach (const QString &k, keys) { const MusicLibraryItemRoot *d=items[k]; QAction *act=itemMenu->addAction(d->icon(), k, this, SLOT(emitAddToDevice())); act->setData(d->id()); Action::initIcon(act); } } if (itemMenu->isEmpty()) { itemMenu->addAction(tr("No Devices Attached"))->setEnabled(false); } } QMimeData * DevicesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData=0; QStringList paths=playableUrls(indexes); if (!paths.isEmpty()) { mimeData=new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constUriMimeType, paths); } return mimeData; } void DevicesModel::play(const QList &songs) { QStringList paths; if (HttpServer::self()->isAlive()) { foreach (const Song &s, songs) { paths.append(HttpServer::self()->encodeUrl(s)); } if (!paths.isEmpty()) { emit add(paths, MPDConnection::ReplaceAndplay, 0, false); } } } cantata-2.2.0/models/devicesmodel.h000066400000000000000000000116301316350454000172270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEVICES_MODEL_H #define DEVICES_MODEL_H #include #include "mpd-interface/song.h" #include "config.h" #include "devices/remotefsdevice.h" #include "musiclibrarymodel.h" #include "devices/cdalbum.h" #include "musiclibraryproxymodel.h" class QMimeData; class Device; class MirrorMenu; class DevicesModel : public MusicLibraryModel { Q_OBJECT public: static DevicesModel * self(); static void enableDebug(); static bool debugEnabled(); static QString fixDevicePath(const QString &path); DevicesModel(QObject *parent = 0); ~DevicesModel(); QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const; QModelIndex parent(const QModelIndex &index) const; QVariant headerData(int, Qt::Orientation, int = Qt::DisplayRole) const { return QVariant(); } int columnCount(const QModelIndex & = QModelIndex()) const { return 1; } int rowCount(const QModelIndex &parent=QModelIndex()) const; void getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres); QList songs(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const; QStringList filenames(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const; int row(void *i) const { return collections.indexOf(static_cast(i)); } bool setData(const QModelIndex &index, const QVariant &value, int role); QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; QStringList playableUrls(const QModelIndexList &indexes) const; void clear(bool clearConfig=true); MirrorMenu * menu() { return itemMenu; } Device * device(const QString &udi); bool isEnabled() const { return enabled; } void setEnabled(bool e); void stop(); QMimeData * mimeData(const QModelIndexList &indexes) const; #ifdef ENABLE_REMOTE_DEVICES void unmountRemote(); #endif Action * configureAct() const { return configureAction; } Action * refreshAct() const { return refreshAction; } Action * connectAct() const { return connectAction; } Action * disconnectAct() const { return disconnectAction; } #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND Action * editAct() const { return editAction; } void playCd(const QString &dev); #endif private: int indexOf(const QString &id); public Q_SLOTS: void setCover(const Song &song, const QImage &img, const QString &file); void setCover(const Song &song, const QImage &img); void deviceAdded(const QString &udi); void deviceRemoved(const QString &udi); void accessibilityChanged(bool accessible, const QString &udi); void deviceUpdating(const QString &udi, bool state); void emitAddToDevice(); void addRemoteDevice(const DeviceOptions &opts, RemoteFsDevice::Details details); void removeRemoteDevice(const QString &udi, bool removeFromConfig=true); void remoteDeviceUdiChanged(); void mountsChanged(); private: void addLocalDevice(const QString &udi); #ifdef ENABLE_REMOTE_DEVICES void loadRemote(); #endif Q_SIGNALS: void addToDevice(const QString &udi); void error(const QString &text); void updated(const QModelIndex &idx); void matches(const QString &udi, const QList &albums); void invalid(const QList &songs); void updatedDetails(const QList &songs); void add(const QStringList &files, int action, quint8 priorty, bool decreasePriority); // add songs to MPD playqueue private Q_SLOTS: void play(const QList &songs); void loadLocal(); void updateItemMenu(); private: QList collections; QSet volumes; MirrorMenu *itemMenu; bool enabled; bool inhibitMenuUpdate; Action *configureAction; Action *refreshAction; Action *connectAction; Action *disconnectAction; #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND QString autoplayCd; Action *editAction; #endif friend class Device; }; #endif cantata-2.2.0/models/digitallyimported.cpp000066400000000000000000000154051316350454000206510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "digitallyimported.h" #include "support/configuration.h" #include "network/networkaccessmanager.h" #include "support/globalstatic.h" #include #include #include #include static const char * constDiGroup="DigitallyImported"; static const QStringList constPremiumValues=QStringList() << QLatin1String("premium_high") << QLatin1String("premium_medium") << QLatin1String("premium"); static const QUrl constAuthUrl(QLatin1String("http://api.audioaddict.com/v1/di/members/authenticate")); const QString DigitallyImported::constApiUserName=QLatin1String("ephemeron"); const QString DigitallyImported::constApiPassword=QLatin1String("dayeiph0ne@pp"); const QString DigitallyImported::constPublicValue=QLatin1String("public3"); GLOBAL_STATIC(DigitallyImported, instance) DigitallyImported::DigitallyImported() : job(0) , streamType(0) , timer(0) { load(); } DigitallyImported::~DigitallyImported() { } void DigitallyImported::login() { if (job) { job->deleteLater(); job=0; } QNetworkRequest req(constAuthUrl); addAuthHeader(req); job=NetworkAccessManager::self()->postFormData(req, "username="+QUrl::toPercentEncoding(userName)+"&password="+QUrl::toPercentEncoding(password)); connect(job, SIGNAL(finished()), SLOT(loginResponse())); } void DigitallyImported::logout() { if (job) { job->deleteLater(); job=0; } listenHash=QString(); expires=QDateTime(); controlTimer(); } void DigitallyImported::addAuthHeader(QNetworkRequest &req) const { req.setRawHeader("Authorization", "Basic "+QString("%1:%2").arg(constApiUserName, constApiPassword).toLatin1().toBase64()); } void DigitallyImported::load() { Configuration cfg(constDiGroup); userName=cfg.get("userName", userName); password=cfg.get("password", password); listenHash=cfg.get("listenHash", listenHash); streamType=cfg.get("streamType", streamType); QString ex=cfg.get("expires", QString()); status=tr("Not logged in"); if (ex.isEmpty()) { listenHash=QString(); } else { expires=QDateTime::fromString(ex, Qt::ISODate); // If we have expired, or are about to expire in 5 minutes, then clear the hash... if (QDateTime::currentDateTime().secsTo(expires)<(5*60)) { listenHash=QString(); } else if (!listenHash.isEmpty()) { status=tr("Logged in"); } } controlTimer(); } void DigitallyImported::save() { Configuration cfg(constDiGroup); cfg.set("userName", userName); cfg.set("password", password); cfg.set("listenHash", listenHash); cfg.set("streamType", streamType); cfg.set("expires", expires.toString(Qt::ISODate)); emit updated(); } bool DigitallyImported::isDiUrl(const QString &u) const { if (!u.startsWith(QLatin1String("http://"))) { return false; } QUrl url(u); if (!url.host().startsWith(QLatin1String("listen."))) { return false; } QStringList pathParts=url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); if (2!=pathParts.count()) { return false; } return pathParts.at(0)==constPublicValue; } QString DigitallyImported::modifyUrl(const QString &u) const { if (listenHash.isEmpty()) { return u; } QString premValue=constPremiumValues.at(streamType>0 && streamTypereadAll()).toVariant().toMap(); if (!data.contains("subscriptions")) { status=tr("No subscriptions"); emit loginStatus(false, status); return; } QVariantList subscriptions = data.value("subscriptions", QVariantList()).toList(); if (subscriptions.isEmpty() || QLatin1String("active")!=subscriptions[0].toMap().value("status").toString()) { status=tr("You do not have an active subscription"); emit loginStatus(false, status); return; } if (!subscriptions[0].toMap().contains("expires_on") || !data.contains("listen_key")) { status=tr("Unknown error"); emit loginStatus(false, status); return; } QDateTime ex = QDateTime::fromString(subscriptions[0].toMap()["expires_on"].toString(), Qt::ISODate); QString lh = data["listen_key"].toString(); if (ex!=expires || lh!=listenHash) { expires=ex; listenHash=lh; save(); } status=tr("Logged in (expiry:%1)").arg(expires.toString(Qt::ISODate)); controlTimer(); emit loginStatus(true, status); } void DigitallyImported::timeout() { listenHash=QString(); emit loginStatus(false, tr("Session expired")); } void DigitallyImported::controlTimer() { if (!expires.isValid() || QDateTime::currentDateTime().secsTo(expires)<15) { if (timer && timer->isActive()) { if (!listenHash.isEmpty()) { timeout(); } timer->stop(); } } else { if (!timer) { timer=new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timeout())); } int secsTo=QDateTime::currentDateTime().secsTo(expires); if (secsTo>4) { timer->start((secsTo-3)*1000); } else { timeout(); } } } cantata-2.2.0/models/digitallyimported.h000066400000000000000000000046551316350454000203230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DIGITALLYIMPORTED_H #define DIGITALLYIMPORTED_H #include #include class QNetworkRequest; class QNetworkReply; class QTimer; class DigitallyImported : public QObject { Q_OBJECT public: static const QString constApiUserName; static const QString constApiPassword; static const QString constPublicValue; static DigitallyImported * self(); DigitallyImported(); ~DigitallyImported(); void addAuthHeader(QNetworkRequest &req) const; void load(); void save(); bool haveAccount() const { return !userName.isEmpty() && !password.isEmpty(); } bool loggedIn() const { return !listenHash.isEmpty(); } const QString & user() const { return userName; } const QString & pass() const { return password; } int audioType() const { return streamType; } const QDateTime & sessionExpiry() const { return expires; } void setUser(const QString &u) { userName=u; } void setPass(const QString &p) { password=p; } void setAudioType(int a) { streamType=a; } const QString & statusString() const { return status; } bool isDiUrl(const QString &u) const; QString modifyUrl(const QString &u) const; public Q_SLOTS: void login(); void logout(); Q_SIGNALS: void loginStatus(bool ok, const QString &msg); void updated(); private Q_SLOTS: void timeout(); void loginResponse(); private: void controlTimer(); private: QNetworkReply *job; QString status; QString userName; QString password; QString listenHash; QDateTime expires; int streamType; QTimer *timer; }; #endif cantata-2.2.0/models/mpdlibrarymodel.cpp000066400000000000000000000206611316350454000203110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mpdlibrarymodel.h" #include "support/globalstatic.h" #include "support/configuration.h" #include "db/mpdlibrarydb.h" #include "gui/settings.h" #include "gui/covers.h" #include "roles.h" #include #include GLOBAL_STATIC(MpdLibraryModel, instance) MpdLibraryModel::MpdLibraryModel() : SqlLibraryModel(new MpdLibraryDb(0), 0) , showArtistImages(false) { connect(Covers::self(), SIGNAL(cover(Song,QImage,QString)), this, SLOT(cover(Song,QImage,QString))); connect(Covers::self(), SIGNAL(coverUpdated(Song,QImage,QString)), this, SLOT(coverUpdated(Song,QImage,QString))); connect(Covers::self(), SIGNAL(artistImage(Song,QImage,QString)), this, SLOT(artistImage(Song,QImage,QString))); connect(Covers::self(), SIGNAL(composerImage(Song,QImage,QString)), this, SLOT(artistImage(Song,QImage,QString))); if (MPDConnection::self()->isConnected()) { static_cast(db)->connectionChanged(MPDConnection::self()->getDetails()); } } QVariant MpdLibraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Cantata::Role_ListImage: { Item *item = static_cast(index.internalPointer()); return T_Album==item->getType() || (T_Artist==item->getType() && useArtistImages()); } case Cantata::Role_GridCoverSong: case Cantata::Role_CoverSong: { QVariant v; Item *item = static_cast(index.internalPointer()); switch (item->getType()) { case T_Album: if (item->getSong().isEmpty()) { Song song=static_cast(db)->getCoverSong(T_Album==topLevel() ? static_cast(item)->getArtistId() : item->getParent()->getId(), item->getId()); item->setSong(song); if (T_Genre==topLevel()) { song.addGenre(item->getParent()->getParent()->getId()); } } v.setValue(item->getSong()); break; case T_Artist: if (!showArtistImages && Cantata::Role_CoverSong==role) { return QVariant(); } if (item->getSong().isEmpty()) { Song song=static_cast(db)->getCoverSong(item->getId()); if (song.useComposer()) { song.setComposerImageRequest(); } else { song.setArtistImageRequest(); } if (T_Genre==topLevel()) { song.addGenre(item->getParent()->getId()); } item->setSong(song); } v.setValue(item->getSong()); break; default: break; } return v; } } return SqlLibraryModel::data(index, role); } void MpdLibraryModel::setUseArtistImages(bool u) { if (u!=showArtistImages) { showArtistImages=u; switch(topLevel()) { case T_Genre: { foreach (const Item *g, root->getChildren()) { const CollectionItem *genre=static_cast(g); if (genre->getChildCount()) { QModelIndex idx=index(genre->getRow(), 0, QModelIndex()); emit dataChanged(index(0, 0, idx), index(genre->getChildCount()-1, 0, idx)); } } break; } case T_Artist: if (root->getChildCount()) { emit dataChanged(index(0, 0, QModelIndex()), index(root->getChildCount()-1, 0, QModelIndex())); } break; default: break; } } } static QLatin1String constUseArtistImagesKey("artistImages"); void MpdLibraryModel::load(Configuration &config) { showArtistImages=config.get(constUseArtistImagesKey, showArtistImages); SqlLibraryModel::load(config); } void MpdLibraryModel::save(Configuration &config) { config.set(constUseArtistImagesKey, showArtistImages); SqlLibraryModel::save(config); } void MpdLibraryModel::listSongs() { listingTotal=db->trackCount(); listingCurrent=0; if (listingTotal>0) { QTimer::singleShot(0, this, SLOT(listNextChunk())); } else { emit songListing(QList(), 100.0); } } void MpdLibraryModel::cancelListing() { listingTotal=0; } static const int constMaxSongsInList=1000; void MpdLibraryModel::listNextChunk() { if (listingTotal<=0) { return; } QList songs=db->getTracks(listingCurrent, constMaxSongsInList); listingCurrent+=songs.count(); emit songListing(songs, (listingCurrent*100.0)/(listingTotal*1.0)); if (songs.count()>0) { if (listingCurrent==listingTotal) { emit songListing(QList(), 100.0); } else { QTimer::singleShot(0, this, SLOT(listNextChunk())); } } } void MpdLibraryModel::cover(const Song &song, const QImage &img, const QString &file) { if (file.isEmpty() || img.isNull() || song.isFromOnlineService()) { return; } switch(topLevel()) { case T_Genre: { const Item *genre=root ? root->getChild(song.genres[0]) : 0; if (genre) { const Item *artist=static_cast(genre)->getChild(song.artistOrComposer()); if (artist) { const Item *album=static_cast(artist)->getChild(song.albumId()); if (album) { QModelIndex idx=index(album->getRow(), 0, index(artist->getRow(), 0, index(genre->getRow(), 0, QModelIndex()))); emit dataChanged(idx, idx); } } } break; } case T_Artist: { const Item *artist=root ? root->getChild(song.artistOrComposer()) : 0; if (artist) { const Item *album=static_cast(artist)->getChild(song.albumId()); if (album) { QModelIndex idx=index(album->getRow(), 0, index(artist->getRow(), 0, QModelIndex())); emit dataChanged(idx, idx); } } break; } case T_Album: { const Item *album=root ? root->getChild(song.artistOrComposer()+song.albumId()) : 0; if (album) { QModelIndex idx=index(album->getRow(), 0, QModelIndex()); emit dataChanged(idx, idx); } break; } default: break; } } void MpdLibraryModel::coverUpdated(const Song &song, const QImage &img, const QString &file) { if (file.isEmpty() || img.isNull() || (T_Album==topLevel() && (song.isArtistImageRequest() || song.isComposerImageRequest()))) { return; } cover(song, img, file); } void MpdLibraryModel::artistImage(const Song &song, const QImage &img, const QString &file) { if (file.isEmpty() || img.isNull() || T_Album==topLevel()) { return; } switch(topLevel()) { case T_Genre: { const Item *genre=root ? root->getChild(song.genres[0]) : 0; if (genre) { const Item *artist=static_cast(genre)->getChild(song.artistOrComposer()); if (artist) { QModelIndex idx=index(artist->getRow(), 0, index(genre->getRow(), 0, QModelIndex())); emit dataChanged(idx, idx); } } break; } case T_Artist: { const Item *artist=root ? root->getChild(song.artistOrComposer()) : 0; if (artist) { QModelIndex idx=index(artist->getRow(), 0, QModelIndex()); emit dataChanged(idx, idx); } break; } default: break; } } cantata-2.2.0/models/mpdlibrarymodel.h000066400000000000000000000033771316350454000177630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MPD_LIBRARY_MODEL_H #define MPD_LIBRARY_MODEL_H #include "sqllibrarymodel.h" class MpdLibraryModel : public SqlLibraryModel { Q_OBJECT public: static MpdLibraryModel * self(); MpdLibraryModel(); QVariant data(const QModelIndex &index, int role) const; void setUseArtistImages(bool u); bool useArtistImages() const { return showArtistImages; } void load(Configuration &config); void save(Configuration &config); void listSongs(); void cancelListing(); Q_SIGNALS: void songListing(const QList &songs, double pc); private Q_SLOTS: void listNextChunk(); void cover(const Song &song, const QImage &img, const QString &file); void coverUpdated(const Song &song, const QImage &img, const QString &file); void artistImage(const Song &song, const QImage &img, const QString &file); private: bool showArtistImages; int listingTotal; int listingCurrent; }; #endif cantata-2.2.0/models/mpdsearchmodel.cpp000066400000000000000000000071601316350454000201110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mpdsearchmodel.h" #include "roles.h" #include "mpd-interface/mpdconnection.h" #include "gui/covers.h" MpdSearchModel::MpdSearchModel(QObject *parent) : SearchModel(parent) , currentId(0) { connect(this, SIGNAL(getRating(QString)), MPDConnection::self(), SLOT(getRating(QString))); connect(this, SIGNAL(search(QString,QString,int)), MPDConnection::self(), SLOT(search(QString,QString,int))); connect(MPDConnection::self(), SIGNAL(searchResponse(int,QList)), this, SLOT(searchFinished(int,QList))); connect(MPDConnection::self(), SIGNAL(rating(QString,quint8)), SLOT(ratingResult(QString,quint8))); connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); } MpdSearchModel::~MpdSearchModel() { } QVariant MpdSearchModel::data(const QModelIndex &index, int role) const { if (!index.isValid() && Cantata::Role_RatingCol==role) { return COL_RATING; } const Song *song = toSong(index); if (!song) { return QVariant(); } switch (role) { case Cantata::Role_SongWithRating: { QVariant var; if (Song::Standard==song->type && Song::Rating_Null==song->rating) { emit getRating(song->file); song->rating=Song::Rating_Requested; } var.setValue(*song); return var; } default: return SearchModel::data(index, role); } return QVariant(); } void MpdSearchModel::clear() { SearchModel::clear(); currentId++; } void MpdSearchModel::search(const QString &key, const QString &value) { if (key==currentKey && value==currentValue) { return; } emit searching(); clear(); currentKey=key; currentValue=value; currentId++; emit search(key, value, currentId); } void MpdSearchModel::searchFinished(int id, const QList &result) { if (id!=currentId) { return; } results(result); } void MpdSearchModel::coverLoaded(const Song &song, int s) { Q_UNUSED(s) if (!song.isArtistImageRequest() && !song.isComposerImageRequest()) { int row=0; foreach (const Song &s, songList) { if (s.albumArtist()==song.albumArtist() && s.album==song.album) { QModelIndex idx=index(row, 0, QModelIndex()); emit dataChanged(idx, idx); } row++; } } } void MpdSearchModel::ratingResult(const QString &file, quint8 r) { QList::iterator it=songList.begin(); QList::iterator end=songList.end(); int numCols=columnCount(QModelIndex())-1; for (int row=0; it!=end; ++it, ++row) { if (Song::Standard==(*it).type && r!=(*it).rating && (*it).file==file) { (*it).rating=r; emit dataChanged(index(row, 0), index(row, numCols)); } } } cantata-2.2.0/models/mpdsearchmodel.h000066400000000000000000000032721316350454000175560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MPD_SEARCH_MODEL_H #define MPD_SEARCH_MODEL_H #include "searchmodel.h" class MpdSearchModel : public SearchModel { Q_OBJECT public: MpdSearchModel(QObject *parent = 0); ~MpdSearchModel(); QVariant data(const QModelIndex &index, int role) const; void clear(); void search(const QString &key, const QString &value); Q_SIGNALS: void search(const QString &field, const QString &value, int id); void getRating(const QString &file) const; private Q_SLOTS: void searchFinished(int id, const QList &result); void coverLoaded(const Song &song, int s); void ratingResult(const QString &file, quint8 r); private: void clearItems(); const Song * toSong(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : 0; } private: int currentId; }; #endif cantata-2.2.0/models/musiclibraryitem.cpp000066400000000000000000000044121316350454000205030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "musiclibraryitem.h" MusicLibraryItem::MusicLibraryItem(MusicLibraryItemContainer *parent) : m_parentItem(parent) , m_checkState(Qt::Unchecked) , m_row(0) { } int MusicLibraryItem::row() const { // Calculate row value of this item. Use 0 to mean not-yet calcualted. Therefore, we // store rows as value+1 - so need to decrement upon return! if (m_row>0) { return m_row-1; } else if (m_parentItem) { m_row=m_parentItem->m_childItems.indexOf(const_cast(this))+1; m_parentItem->m_rowsSet=true; return m_row-1; } return 0; } void MusicLibraryItem::setParent(MusicLibraryItemContainer *p) { if (p==m_parentItem) { return; } if (m_parentItem) { m_parentItem->m_childItems.removeAll(this); m_parentItem->resetRows(); } m_parentItem=p; m_parentItem->m_childItems.append(this); } MusicLibraryItem * MusicLibraryItemContainer::childItem(const QString &name) const { foreach (MusicLibraryItem *i, m_childItems) { if (i->data()==name) { return i; } } return 0; } void MusicLibraryItemContainer::resetRows() { if (m_rowsSet) { foreach (MusicLibraryItem *i, m_childItems) { i->m_row=0; } m_rowsSet=false; } } void MusicLibraryItemContainer::clear() { qDeleteAll(m_childItems); m_childItems.clear(); m_rowsSet=false; } cantata-2.2.0/models/musiclibraryitem.h000066400000000000000000000062141316350454000201520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_ITEM_H #define MUSIC_LIBRARY_ITEM_H #include #include #include class MusicLibraryItemContainer; class MusicLibraryItem { public: enum Type { Type_Root, Type_Artist, Type_Album, Type_Song }; MusicLibraryItem(MusicLibraryItemContainer *parent); virtual ~MusicLibraryItem() { } MusicLibraryItemContainer * parentItem() const { return m_parentItem; } virtual MusicLibraryItem * childItem(int) const { return 0; } virtual int childCount() const { return 0; } int row() const; void setRow(int r) const { m_row=r+1; } int columnCount() const { return 1; } virtual QString data() const = 0; virtual QString displayData(bool full=false) const { Q_UNUSED(full) return data(); } void setParent(MusicLibraryItemContainer *p); Qt::CheckState checkState() const { return m_checkState; } void setCheckState(Qt::CheckState s) { m_checkState=s; } virtual Type itemType() const=0; protected: friend class MusicLibraryItemContainer; MusicLibraryItemContainer *m_parentItem; Qt::CheckState m_checkState; mutable quint32 m_row; }; class MusicLibraryItemContainer : public MusicLibraryItem { public: MusicLibraryItemContainer(const QString &data, MusicLibraryItemContainer *parent) : MusicLibraryItem(parent), m_itemData(data), m_isNew(false), m_rowsSet(false) { } virtual ~MusicLibraryItemContainer() { clear(); } virtual void append(MusicLibraryItem *i) { m_childItems.append(i); } virtual MusicLibraryItem * childItem(int row) const { return m_childItems.value(row); } MusicLibraryItem * childItem(const QString &name) const; QString data() const { return m_itemData; } void setData(const QString &d) { m_itemData=d; } int childCount() const { return m_childItems.count(); } const QList & childItems() const { return m_childItems; } void resetRows(); void clear(); int indexOf(MusicLibraryItem *c) const { return m_childItems.indexOf(c); } void setIsNew(quint32 v) { m_isNew=v; } bool isNew() const { return m_isNew; } protected: friend class MusicLibraryItem; QString m_itemData; QList m_childItems; bool m_isNew:1; bool m_rowsSet:1; }; #endif cantata-2.2.0/models/musiclibraryitemalbum.cpp000066400000000000000000000157141316350454000215330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "musiclibraryitemroot.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemalbum.h" #include "musiclibraryitemsong.h" #include "widgets/icons.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/device.h" #include "support/utils.h" #endif static bool dateSort=false; void MusicLibraryItemAlbum::setSortByDate(bool sd) { dateSort=sd; } bool MusicLibraryItemAlbum::sortByDate() { return dateSort; } bool MusicLibraryItemAlbum::lessThan(const MusicLibraryItem *a, const MusicLibraryItem *b) { const MusicLibraryItemAlbum *aa=static_cast(a); const MusicLibraryItemAlbum *ab=static_cast(b); if (!MusicLibraryItemAlbum::sortByDate() || aa->year()==ab->year()) { int compare=aa->sortString().localeAwareCompare(ab->sortString()); return compare==0 ? aa->id().compare(ab->id())<0 : compare<0; } return aa->year()year(); } static const QLatin1String constThe("The "); MusicLibraryItemAlbum::MusicLibraryItemAlbum(const Song &song, MusicLibraryItemContainer *parent) : MusicLibraryItemContainer(song.album, parent) , m_year(song.year) , m_yearOfTrack(0xFFFF) , m_yearOfDisc(0xFFFF) , m_numTracks(0) , m_totalTime(0) , m_sortString(song.hasAlbumSort() ? song.albumSort() : QString()) , m_id(song.hasMbAlbumId() ? song.mbAlbumId() : QString()) { } MusicLibraryItemAlbum::~MusicLibraryItemAlbum() { } QString MusicLibraryItemAlbum::displayData(bool full) const { return dateSort || full ? Song::displayAlbum(m_itemData, m_year) : m_itemData; } quint32 MusicLibraryItemAlbum::totalTime() { updateStats(); return m_totalTime; } quint32 MusicLibraryItemAlbum::trackCount() { updateStats(); return m_numTracks; } void MusicLibraryItemAlbum::updateStats() { if (0==m_totalTime) { m_numTracks=0; foreach (MusicLibraryItem *i, m_childItems) { MusicLibraryItemSong *song=static_cast(i); if (Song::Playlist!=song->song().type) { m_totalTime+=song->time(); m_numTracks++; } } } } void MusicLibraryItemAlbum::append(MusicLibraryItem *i) { MusicLibraryItemSong *song=static_cast(i); setYear(song); MusicLibraryItemContainer::append(i); m_totalTime=0; } void MusicLibraryItemAlbum::remove(int row) { MusicLibraryItem *i=m_childItems.takeAt(row); MusicLibraryItemSong *song=static_cast(i); if (m_yearOfDisc==song->disc() && m_yearOfTrack==song->track()) { m_yearOfDisc=m_yearOfTrack=0xFFFF; foreach (MusicLibraryItem *itm, m_childItems) { setYear(static_cast(itm)); } } delete i; m_totalTime=0; resetRows(); } void MusicLibraryItemAlbum::remove(MusicLibraryItemSong *i) { int idx=m_childItems.indexOf(i); if (-1!=idx) { remove(idx); } resetRows(); } void MusicLibraryItemAlbum::removeAll(const QSet &fileNames) { QSet fn=fileNames; for (int i=0; i(m_childItems.at(i)); if (fn.contains(song->file())) { fn.remove(song->file()); delete m_childItems.takeAt(i); m_totalTime=0; } else { ++i; } } resetRows(); } QMap MusicLibraryItemAlbum::getSongs(const QSet &fileNames) const { QMap map; foreach (const MusicLibraryItem *i, m_childItems) { const MusicLibraryItemSong *song=static_cast(i); if (fileNames.contains(song->file())) { map.insert(song->file(), song->song()); if (map.size()==fileNames.size()) { return map; } } } return map; } bool MusicLibraryItemAlbum::updateYear() { quint32 currentYear=m_year; foreach (MusicLibraryItem *track, m_childItems) { MusicLibraryItemSong *song=static_cast(track); if (Song::Playlist!=song->song().type) { m_year=song->song().year; // Store which track/disc we obtained the year from! m_yearOfTrack=song->track(); m_yearOfDisc=song->disc(); if (m_year==currentYear) { return false; } } } return true; } Song MusicLibraryItemAlbum::coverSong() const { Song song; if (childCount()) { MusicLibraryItemSong *firstSong=static_cast(childItem(0)); song.artist=firstSong->song().artist; song.albumartist=/*Song::useComposer() && !firstSong->song().composer().isEmpty() ? */firstSong->song().albumArtist() /*: parentItem()->data()*/; song.album=/*Song::useComposer() ? */firstSong->song().album /*: m_itemData*/; song.setMbAlbumId(firstSong->song().mbAlbumId()); song.setComposer(firstSong->song().composer()); song.year=m_year; song.file=firstSong->file(); #if defined ENABLE_DEVICES_SUPPORT MusicLibraryItemRoot *root=parentItem() && parentItem()->parentItem() && MusicLibraryItem::Type_Root==parentItem()->parentItem()->itemType() ? static_cast(parentItem()->parentItem()) : 0; if (root) { #ifdef ENABLE_DEVICES_SUPPORT if (root->isDevice()) { song.setIsFromDevice(static_cast(root)->id()); } #endif } #endif } return song; } void MusicLibraryItemAlbum::setYear(const MusicLibraryItemSong *song) { if (Song::Playlist!=song->song().type && (m_childItems.isEmpty() || (m_yearOfDisc>song->disc() || (m_yearOfDisc==song->disc() && m_yearOfTrack>song->track())))) { m_year=song->song().year; // Store which track/disc we obtained the year from! m_yearOfTrack=song->track(); m_yearOfDisc=song->disc(); Song::storeAlbumYear(song->song()); } } cantata-2.2.0/models/musiclibraryitemalbum.h000066400000000000000000000050161316350454000211720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_ITEM_ALBUM_H #define MUSIC_LIBRARY_ITEM_ALBUM_H #include #include #include #include "musiclibraryitem.h" #include "mpd-interface/song.h" class QPixmap; class MusicLibraryItemArtist; class MusicLibraryItemSong; class MusicLibraryItemAlbum : public MusicLibraryItemContainer { public: static void setSortByDate(bool sd); static bool sortByDate(); static bool lessThan(const MusicLibraryItem *a, const MusicLibraryItem *b); MusicLibraryItemAlbum(const Song &song, MusicLibraryItemContainer *parent); virtual ~MusicLibraryItemAlbum(); QString displayData(bool full=false) const; quint32 year() const { return m_year; } quint32 totalTime(); quint32 trackCount(); void append(MusicLibraryItem *i); void remove(int row); void remove(MusicLibraryItemSong *i); void removeAll(const QSet &fileNames); QMap getSongs(const QSet &fileNames) const; Type itemType() const { return Type_Album; } bool updateYear(); const QString & id() const { return m_id; } const QString & albumId() const { return m_id.isEmpty() ? m_itemData : m_id; } const QString & sortString() const { return m_sortString.isEmpty() ? m_itemData : m_sortString; } bool hasSort() const { return !m_sortString.isEmpty(); } Song coverSong() const; private: void setYear(const MusicLibraryItemSong *song); void updateStats(); private: quint16 m_year; quint16 m_yearOfTrack; quint16 m_yearOfDisc; quint16 m_numTracks; quint32 m_totalTime; QString m_sortString; QString m_id; mutable Song m_coverSong; }; #endif cantata-2.2.0/models/musiclibraryitemartist.cpp000066400000000000000000000124721316350454000217370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "musiclibraryitemroot.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemalbum.h" #include "musiclibraryitemsong.h" #include "mpd-interface/mpdparseutils.h" #include "widgets/icons.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/device.h" #include "support/utils.h" #endif bool MusicLibraryItemArtist::lessThan(const MusicLibraryItem *a, const MusicLibraryItem *b) { const MusicLibraryItemArtist *aa=static_cast(a); const MusicLibraryItemArtist *ab=static_cast(b); // if (aa->isVarious() != ab->isVarious()) { // return aa->isVarious() > ab->isVarious(); // } return aa->sortString().localeAwareCompare(ab->sortString())<0; } MusicLibraryItemArtist::MusicLibraryItemArtist(const Song &song, MusicLibraryItemContainer *parent) : MusicLibraryItemContainer(song.artistOrComposer(), parent) , m_sortString(song.hasAlbumArtistSort() ? song.albumArtistSort() : QString()) , m_actualArtist(song.useComposer() ? song.albumArtist() : QString()) { } MusicLibraryItemAlbum * MusicLibraryItemArtist::album(const Song &s, bool create) { MusicLibraryItemAlbum *albumItem=getAlbum(s.albumId()); return albumItem ? albumItem : (create ? createAlbum(s) : 0); } MusicLibraryItemAlbum * MusicLibraryItemArtist::createAlbum(const Song &s) { // If grouping via composer - then album name *might* need to include artist name (if this is different to composer) // So, when creating an album entry we need to use the "Album (Artist)" value for display/sort, and still store just // "Album" (for saving to cache, tag editing, etc.) QString albumId=s.albumId(); MusicLibraryItemAlbum *item=new MusicLibraryItemAlbum(s, this); m_indexes.insert(albumId, m_childItems.count()); m_childItems.append(item); return item; } void MusicLibraryItemArtist::remove(MusicLibraryItemAlbum *album) { int index=m_childItems.indexOf(album); if (index<0 || index>=m_childItems.count()) { return; } QHash::Iterator it=m_indexes.begin(); QHash::Iterator end=m_indexes.end(); for (; it!=end; ++it) { if ((*it)>index) { (*it)--; } } m_indexes.remove(album->albumId()); delete m_childItems.takeAt(index); resetRows(); } Song MusicLibraryItemArtist::coverSong() const { Song song; song.albumartist=song.title=m_itemData; // If title is empty, then Song::isUnknown() will be true!!! if (childCount()) { MusicLibraryItemAlbum *firstAlbum=static_cast(childItem(0)); MusicLibraryItemSong *firstSong=firstAlbum ? static_cast(firstAlbum->childItem(0)) : 0; if (firstSong) { song.file=firstSong->file(); //if (Song::useComposer() && !firstSong->song().composer().isEmpty()) { song.albumartist=firstSong->song().albumArtist(); //} song.addGenre(firstSong->song().firstGenre()); song.setComposer(firstSong->song().composer()); } } if (!m_actualArtist.isEmpty() && song.useComposer()) { song.setComposerImageRequest(); } else { song.setArtistImageRequest(); } return song; } MusicLibraryItemAlbum * MusicLibraryItemArtist::getAlbum(const QString &key) const { if (m_indexes.count()==m_childItems.count()) { if (m_childItems.isEmpty()) { return 0; } QHash::ConstIterator idx=m_indexes.find(key); if (m_indexes.end()==idx) { return 0; } // Check index value is within range if (*idx>=0 && *idx(m_childItems.at(*idx)); // Check id actually matches! if (a->albumId()==key) { return a; } } } // Something wrong with m_indexes??? So, refresh them... MusicLibraryItemAlbum *al=0; m_indexes.clear(); QList::ConstIterator it=m_childItems.constBegin(); QList::ConstIterator end=m_childItems.constEnd(); for (int i=0; it!=end; ++it, ++i) { MusicLibraryItemAlbum *currenAlbum=static_cast(*it); if (!al && currenAlbum->albumId()==key) { al=currenAlbum; } m_indexes.insert(currenAlbum->albumId(), i); } return al; } cantata-2.2.0/models/musiclibraryitemartist.h000066400000000000000000000042741316350454000214050ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_ITEM_ARTIST_H #define MUSIC_LIBRARY_ITEM_ARTIST_H #include #include #include #include "musiclibraryitem.h" #include "mpd-interface/song.h" class MusicLibraryItemRoot; class MusicLibraryItemAlbum; class MusicLibraryItemArtist : public MusicLibraryItemContainer { public: static bool lessThan(const MusicLibraryItem *a, const MusicLibraryItem *b); MusicLibraryItemArtist(const Song &song, MusicLibraryItemContainer *parent=0); virtual ~MusicLibraryItemArtist() { } MusicLibraryItemAlbum * album(const Song &s, bool create=true); MusicLibraryItemAlbum * createAlbum(const Song &s); const QString & sortString() const { return m_sortString.isEmpty() ? m_itemData : m_sortString; } void remove(MusicLibraryItemAlbum *album); Type itemType() const { return Type_Artist; } // 'data' could be 'Composer' if we are set to use that, but need to save real artist... const QString & actualArtist() const { return m_actualArtist; } Song coverSong() const; private: MusicLibraryItemAlbum * getAlbum(const QString &key) const; private: QString m_sortString; // Do we have an actual artist-sort, or is m_sortString just "Artist, The" ??? - needed for cache saving QString m_actualArtist; mutable QHash m_indexes; }; #endif cantata-2.2.0/models/musiclibraryitemroot.cpp000066400000000000000000000677721316350454000214310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "musiclibraryitemroot.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemalbum.h" #include "musiclibraryitemsong.h" #include "musiclibrarymodel.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdconnection.h" #include "qtiocompressor/qtiocompressor.h" #include #include #include #include //#define TIME_XML_FILE_LOADING #ifdef TIME_XML_FILE_LOADING #include #endif MusicLibraryItemArtist * MusicLibraryItemRoot::artist(const Song &s, bool create) { QString aa=songArtist(s); MusicLibraryItemArtist *artistItem=getArtist(aa); return artistItem ? artistItem : (create ? createArtist(s) : 0); } MusicLibraryItemArtist * MusicLibraryItemRoot::createArtist(const Song &s, bool forceComposer) { QString aa=songArtist(s, forceComposer); MusicLibraryItemArtist *item=new MusicLibraryItemArtist(s, this); m_indexes.insert(aa, m_childItems.count()); m_childItems.append(item); return item; } void MusicLibraryItemRoot::refreshIndexes() { if (isFlat) { return; } m_indexes.clear(); int i=0; foreach (MusicLibraryItem *item, m_childItems) { m_indexes.insert(item->data(), i++); } } void MusicLibraryItemRoot::remove(MusicLibraryItemArtist *artist) { if (isFlat) { return; } int index=m_childItems.indexOf(artist); if (index<0 || index>=m_childItems.count()) { return; } QHash::Iterator it=m_indexes.begin(); QHash::Iterator end=m_indexes.end(); for (; it!=end; ++it) { if ((*it)>index) { (*it)--; } } m_indexes.remove(artist->data()); delete m_childItems.takeAt(index); resetRows(); } QSet MusicLibraryItemRoot::allSongs(bool revertVa) const { QSet songs; foreach (const MusicLibraryItem *child, m_childItems) { if (MusicLibraryItem::Type_Song==child->itemType()) { if (revertVa) { Song s=static_cast(child)->song(); s.revertVariousArtists(); songs.insert(s); } else { songs.insert(static_cast(child)->song()); } } else { foreach (const MusicLibraryItem *album, static_cast(child)->childItems()) { foreach (const MusicLibraryItem *song, static_cast(album)->childItems()) { if (revertVa) { Song s=static_cast(song)->song(); s.revertVariousArtists(); songs.insert(s); } else { songs.insert(static_cast(song)->song()); } } } } } return songs; } void MusicLibraryItemRoot::getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres) { foreach (const MusicLibraryItem *child, m_childItems) { if (MusicLibraryItem::Type_Song==child->itemType()) { const Song &s=static_cast(child)->song(); artists.insert(s.artist); albumArtists.insert(s.albumArtist()); composers.insert(s.composer()); albums.insert(s.album); for (int i=0; iitemType()) { foreach (const MusicLibraryItem *album, static_cast(child)->childItems()) { foreach (const MusicLibraryItem *song, static_cast(album)->childItems()) { const Song &s=static_cast(song)->song(); artists.insert(s.artist); albumArtists.insert(s.albumArtist()); composers.insert(s.composer()); albums.insert(s.album); for (int i=0; ialbum(from, false); if (alb) { foreach (MusicLibraryItem *song, alb->childItems()) { if (static_cast(song)->file()==from.file) { static_cast(song)->setFile(to.file); return; } } } } } void MusicLibraryItemRoot::toXML(const QString &filename, MusicLibraryProgressMonitor *prog) const { if (isFlat) { return; } // If saving device cache, and we have NO items, then remove cache file... if (0==childCount()) { if (QFile::exists(filename)) { QFile::remove(filename); } return; } QFile file(filename); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor.open(QIODevice::WriteOnly)) { return; } QXmlStreamWriter writer(&compressor); toXML(writer, prog); compressor.close(); } static const quint32 constVersion=1; static const QString constTopTag=QLatin1String("tagcache"); static const QString constTrackElement=QLatin1String("track"); static const QString constTitleAttribute=QLatin1String("title"); static const QString constSortAttribute=QLatin1String("sort"); static const QString constArtistAttribute=QLatin1String("artist"); static const QString constAlbumArtistAttribute=QLatin1String("albumartist"); static const QString constArtistSortAttribute=QLatin1String("artistsort"); static const QString constAlbumArtistSortAttribute=QLatin1String("albumartistsort"); static const QString constComposerAttribute=QLatin1String("composer"); static const QString constAlbumAttribute=QLatin1String("album"); static const QString constAlbumSortAttribute=QLatin1String("albumsort"); static const QString constTrackAttribute=QLatin1String("track"); static const QString constGenreAttribute=QLatin1String("genre"); static const QString constYearAttribute=QLatin1String("year"); static const QString constTimeAttribute=QLatin1String("time"); static const QString constDiscAttribute=QLatin1String("disc"); static const QString constMbAlbumIdAttribute=QLatin1String("mbalbumid"); static const QString constFileAttribute=QLatin1String("file"); static const QString constPlaylistAttribute=QLatin1String("playlist"); static const QString constGuessedAttribute=QLatin1String("guessed"); static const QString constVersionAttribute=QLatin1String("version"); static const QString constnumTracksAttribute=QLatin1String("num"); static const QString constTrueValue=QLatin1String("true"); void MusicLibraryItemRoot::toXML(QXmlStreamWriter &writer, MusicLibraryProgressMonitor *prog) const { if (isFlat) { return; } quint64 total=0; quint64 count=0; int percent=0; QElapsedTimer timer; if (prog) { prog->writeProgress(0.0); timer.start(); } writer.writeStartDocument(); //Start with the document writer.writeStartElement(constTopTag); writer.writeAttribute(constVersionAttribute, QString::number(constVersion)); foreach (const MusicLibraryItem *a, childItems()) { foreach (const MusicLibraryItem *al, static_cast(a)->childItems()) { total+=al->childCount(); if (prog && prog->wasStopped()) { return; } } } writer.writeAttribute(constnumTracksAttribute, QString::number(total)); //Loop over all artist, albums and tracks. foreach (const MusicLibraryItem *a, childItems()) { const MusicLibraryItemArtist *artist = static_cast(a); foreach (const MusicLibraryItem *al, artist->childItems()) { if (prog && prog->wasStopped()) { return; } const MusicLibraryItemAlbum *album = static_cast(al); foreach (const MusicLibraryItem *t, album->childItems()) { const MusicLibraryItemSong *track = static_cast(t); const Song &song=track->song(); writer.writeStartElement(constTrackElement); writer.writeAttribute(constFileAttribute, track->file()); if (!song.title.isEmpty()) { writer.writeAttribute(constTitleAttribute, song.title); } if (0!=track->time()) { writer.writeAttribute(constTimeAttribute, QString::number(track->time())); } if (track->track()) { writer.writeAttribute(constTrackAttribute, QString::number(track->track())); } if (track->disc()) { writer.writeAttribute(constDiscAttribute, QString::number(track->disc())); } if (!song.artist.isEmpty()) { writer.writeAttribute(constArtistAttribute, song.artist); } if (!song.albumartist.isEmpty()) { writer.writeAttribute(constAlbumArtistAttribute, song.albumartist); } if (!song.album.isEmpty()) { writer.writeAttribute(constAlbumAttribute, song.album); } if (song.hasComposer()) { writer.writeAttribute(constComposerAttribute, song.composer()); } if (song.hasMbAlbumId()) { writer.writeAttribute(constMbAlbumIdAttribute, song.mbAlbumId()); } if (song.hasAlbumSort()) { writer.writeAttribute(constAlbumSortAttribute, song.albumSort()); } if (song.hasArtistSort()) { writer.writeAttribute(constArtistSortAttribute, song.artistSort()); } if (song.hasAlbumArtistSort()) { writer.writeAttribute(constAlbumArtistSortAttribute, song.albumArtistSort()); } QString trackGenre=track->song().genres[0]; for (int i=1; isong().genres[i].isEmpty(); ++i) { trackGenre+=","+track->song().genres[i]; } if (!trackGenre.isEmpty() && trackGenre!=Song::unknown()) { writer.writeAttribute(constGenreAttribute, trackGenre); } if (Song::Playlist==song.type) { writer.writeAttribute(constPlaylistAttribute, constTrueValue); } if (song.year) { writer.writeAttribute(constYearAttribute, QString::number(song.year)); } if (song.guessed) { writer.writeAttribute(constGuessedAttribute, constTrueValue); } if (prog && !prog->wasStopped() && total>0) { count++; int pc=((count*100.0)/(total*1.0))+0.5; if (pc!=percent && timer.elapsed()>=250) { prog->writeProgress(pc); timer.restart(); percent=pc; } } writer.writeEndElement(); } } } writer.writeEndElement(); writer.writeEndDocument(); } bool MusicLibraryItemRoot::fromXML(const QString &filename, const QString &baseFolder, MusicLibraryProgressMonitor *prog, MusicLibraryErrorMonitor *em) { if (isFlat) { return false; } #ifdef TIME_XML_FILE_LOADING QElapsedTimer timer; timer.start(); #endif QFile file(filename); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor.open(QIODevice::ReadOnly)) { return false; } QXmlStreamReader reader(&compressor); bool rv=fromXML(reader, baseFolder, prog, em); compressor.close(); #ifdef TIME_XML_FILE_LOADING qWarning() << filename << timer.elapsed(); #endif return rv; } bool MusicLibraryItemRoot::fromXML(QXmlStreamReader &reader, const QString &baseFolder, MusicLibraryProgressMonitor *prog, MusicLibraryErrorMonitor *em) { if (isFlat) { return false; } quint64 total=0; quint64 count=0; int percent=0; QElapsedTimer timer; bool valid=false; if (prog) { prog->readProgress(0.0); timer.start(); } while (!reader.atEnd() && (!prog || !prog->wasStopped())) { reader.readNext(); if (reader.error()) { if (em) { em->loadError(QObject::tr("Parse error loading cache file, please check your songs tags.")); } } else if (reader.isStartElement()) { QString element = reader.name().toString(); QXmlStreamAttributes attributes=reader.attributes(); if (constTopTag == element) { quint32 version = attributes.value(constVersionAttribute).toString().toUInt(); if (version < constVersion) { return false; } if (prog) { total=attributes.value(constnumTracksAttribute).toString().toUInt(); } valid = true; } else if (valid && constTrackElement == element) { Song song; song.file=attributes.value(constFileAttribute).toString(); if (!baseFolder.isEmpty() && song.file.startsWith(baseFolder)) { song.file=song.file.mid(baseFolder.length()); } if (attributes.hasAttribute(constTitleAttribute)) { song.title=attributes.value(constTitleAttribute).toString(); } if (attributes.hasAttribute(constTimeAttribute)) { song.time=attributes.value(constTimeAttribute).toString().toUInt(); } if (attributes.hasAttribute(constTrackAttribute)) { song.track=attributes.value(constTrackAttribute).toString().toUInt(); } if (attributes.hasAttribute(constDiscAttribute)) { song.disc=attributes.value(constDiscAttribute).toString().toUInt(); } if (attributes.hasAttribute(constArtistAttribute)) { song.artist=attributes.value(constArtistAttribute).toString(); } if (attributes.hasAttribute(constAlbumArtistAttribute)) { song.albumartist=attributes.value(constAlbumArtistAttribute).toString(); } if (attributes.hasAttribute(constAlbumAttribute)) { song.album=attributes.value(constAlbumAttribute).toString(); } if (attributes.hasAttribute(constComposerAttribute)) { song.setComposer(attributes.value(constComposerAttribute).toString()); } if (attributes.hasAttribute(constMbAlbumIdAttribute)) { song.setMbAlbumId(attributes.value(constMbAlbumIdAttribute).toString()); } if (attributes.hasAttribute(constAlbumSortAttribute)) { song.setAlbumSort(attributes.value(constAlbumSortAttribute).toString()); } if (attributes.hasAttribute(constArtistSortAttribute)) { song.setArtistSort(attributes.value(constArtistSortAttribute).toString()); } if (attributes.hasAttribute(constAlbumArtistSortAttribute)) { song.setAlbumArtistSort(attributes.value(constAlbumArtistSortAttribute).toString()); } if (attributes.hasAttribute(constGenreAttribute)) { QStringList genres=attributes.value(constGenreAttribute).toString().split(",", QString::SkipEmptyParts); for (int i=0; ialbum(song); albumItem->append(new MusicLibraryItemSong(song, albumItem)); if (prog && !prog->wasStopped() && total>0) { count++; int pc=((count*100.0)/(total*1.0))+0.5; if (pc!=percent && timer.elapsed()>=250) { prog->readProgress(pc); timer.restart(); percent=pc; } } } } } return valid; } void MusicLibraryItemRoot::add(const QSet &songs) { if (isFlat) { return; } MusicLibraryItemArtist *artistItem = 0; MusicLibraryItemAlbum *albumItem = 0; foreach (const Song &s, songs) { if (s.isEmpty()) { continue; } if (!artistItem || (supportsAlbumArtist ? s.albumArtist()!=artistItem->data() : s.album!=artistItem->data())) { artistItem = artist(s); } if (!albumItem || albumItem->parentItem()!=artistItem || s.album!=albumItem->data()) { albumItem = artistItem->album(s); } albumItem->append(new MusicLibraryItemSong(s, albumItem)); } } void MusicLibraryItemRoot::clearItems() { qDeleteAll(m_childItems); m_childItems.clear(); m_indexes.clear(); } bool MusicLibraryItemRoot::update(const QSet &songs) { QSet currentSongs=allSongs(); QSet updateSongs=songs; QSet removed=currentSongs-updateSongs; QSet added=updateSongs-currentSongs; bool updatedSongs=added.count()||removed.count(); foreach (const Song &s, removed) { removeSongFromList(s); } foreach (const Song &s, added) { addSongToList(s); } return updatedSongs; } const MusicLibraryItem * MusicLibraryItemRoot::findSong(const Song &s) const { if (isFlat) { foreach (const MusicLibraryItem *songItem, childItems()) { if (songItem->data()==s.displayTitle()) { return songItem; } } } else { MusicLibraryItemArtist *artistItem = const_cast(this)->artist(s, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (albumItem) { foreach (const MusicLibraryItem *songItem, albumItem->childItems()) { if (songItem->data()==s.displayTitle() && static_cast(songItem)->song().track==s.track) { return songItem; } } } } } return 0; } bool MusicLibraryItemRoot::songExists(const Song &s) const { const MusicLibraryItem *song=findSong(s); if (song) { return true; } if (!s.isVariousArtists()) { Song mod(s); mod.albumartist=Song::variousArtists(); } return false; } bool MusicLibraryItemRoot::updateSong(const Song &orig, const Song &edit) { if (!m_model) { return false; } if (isFlat) { int songRow=0; foreach (MusicLibraryItem *song, childItems()) { if (static_cast(song)->song().file==orig.file) { static_cast(song)->setSong(edit); QModelIndex idx=m_model->createIndex(songRow, 0, song); emit m_model->dataChanged(idx, idx); return true; } songRow++; } } else if ((supportsAlbumArtist ? orig.albumArtist()==edit.albumArtist() : orig.artist==edit.artist) && orig.album==edit.album) { MusicLibraryItemArtist *artistItem = artist(orig, false); if (!artistItem) { return false; } MusicLibraryItemAlbum *albumItem = artistItem->album(orig, false); if (!albumItem) { return false; } int songRow=0; foreach (MusicLibraryItem *song, albumItem->childItems()) { if (static_cast(song)->song().file==orig.file) { static_cast(song)->setSong(edit); bool yearUpdated=orig.year!=edit.year && albumItem->updateYear(); QModelIndex idx=m_model->createIndex(songRow, 0, song); emit m_model->dataChanged(idx, idx); if (yearUpdated) { idx=m_model->createIndex(albumItem->row(), 0, albumItem); emit m_model->dataChanged(idx, idx); } return true; } songRow++; } } return false; } void MusicLibraryItemRoot::addSongToList(const Song &s) { if (!m_model || isFlat) { return; } MusicLibraryItemArtist *artistItem = artist(s, false); if (!artistItem) { m_model->beginInsertRows(index(), childCount(), childCount()); artistItem = createArtist(s); artistItem->setIsNew(true); m_model->endInsertRows(); } MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (!albumItem) { m_model->beginInsertRows(m_model->createIndex(artistItem->row(), 0, artistItem), artistItem->childCount(), artistItem->childCount()); albumItem = artistItem->createAlbum(s); albumItem->setIsNew(true); m_model->endInsertRows(); if (!artistItem->isNew()) { artistItem->setIsNew(true); QModelIndex idx=m_model->createIndex(artistItem->row(), 0, artistItem); emit m_model->dataChanged(idx, idx); } } quint32 year=albumItem->year(); foreach (const MusicLibraryItem *songItem, albumItem->childItems()) { if (static_cast(songItem)->song().file==s.file) { return; } } m_model->beginInsertRows(m_model->createIndex(albumItem->row(), 0, albumItem), albumItem->childCount(), albumItem->childCount()); MusicLibraryItemSong *songItem = new MusicLibraryItemSong(s, albumItem); albumItem->append(songItem); m_model->endInsertRows(); if (!artistItem->isNew()) { artistItem->setIsNew(true); QModelIndex idx=m_model->createIndex(artistItem->row(), 0, artistItem); emit m_model->dataChanged(idx, idx); } if (!albumItem->isNew()) { albumItem->setIsNew(true); QModelIndex idx=m_model->createIndex(albumItem->row(), 0, albumItem); emit m_model->dataChanged(idx, idx); } else if (year!=albumItem->year()) { QModelIndex idx=m_model->createIndex(albumItem->row(), 0, albumItem); emit m_model->dataChanged(idx, idx); } } void MusicLibraryItemRoot::removeSongFromList(const Song &s) { if (!m_model || isFlat) { return; } MusicLibraryItemArtist *artistItem = artist(s, false); if (!artistItem) { return; } MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (!albumItem) { return; } MusicLibraryItem *songItem=0; int songRow=0; foreach (MusicLibraryItem *song, albumItem->childItems()) { if (static_cast(song)->song().file==s.file) { songItem=song; break; } songRow++; } if (!songItem) { return; } if (1==artistItem->childCount() && 1==albumItem->childCount()) { // 1 album with 1 song - so remove whole artist int row=artistItem->row(); m_model->beginRemoveRows(index(), row, row); remove(artistItem); m_model->endRemoveRows(); return; } if (1==albumItem->childCount()) { // multiple albums, but this album only has 1 song - remove album int row=albumItem->row(); m_model->beginRemoveRows(m_model->createIndex(artistItem->row(), 0, artistItem), row, row); artistItem->remove(albumItem); m_model->endRemoveRows(); return; } // Just remove particular song m_model->beginRemoveRows(m_model->createIndex(albumItem->row(), 0, albumItem), songRow, songRow); quint32 year=albumItem->year(); albumItem->remove(songRow); m_model->endRemoveRows(); if (year!=albumItem->year()) { QModelIndex idx=m_model->createIndex(albumItem->row(), 0, albumItem); emit m_model->dataChanged(idx, idx); } } QString MusicLibraryItemRoot::artistName(const Song &s, bool forceComposer) { if (Song::Standard==s.type || Song::Cdda==s.type || Song::OnlineSvrTrack==s.type || (Song::Playlist==s.type && !s.albumArtist().isEmpty())) { return forceComposer && s.hasComposer() ? s.composer() : s.artistOrComposer(); } return Song::variousArtists(); } QString MusicLibraryItemRoot::songArtist(const Song &s, bool forceComposer) const { if (isFlat || !supportsAlbumArtist) { return s.artist; } return artistName(s, forceComposer); } MusicLibraryItemArtist * MusicLibraryItemRoot::getArtist(const QString &key) const { if (m_indexes.count()==m_childItems.count()) { if (m_childItems.isEmpty()) { return 0; } QHash::ConstIterator idx=m_indexes.find(key); if (m_indexes.end()==idx) { return 0; } // Check index value is within range if (*idx>=0 && *idx(m_childItems.at(*idx)); // Check id actually matches! if (a->data()==key) { return a; } } } // Something wrong with m_indexes??? So, refresh them... MusicLibraryItemArtist *ar=0; m_indexes.clear(); QList::ConstIterator it=m_childItems.constBegin(); QList::ConstIterator end=m_childItems.constEnd(); for (int i=0; it!=end; ++it, ++i) { MusicLibraryItemArtist *currenArtist=static_cast(*it); if (!ar && currenArtist->data()==key) { ar=currenArtist; } m_indexes.insert(currenArtist->data(), i); } return ar; } cantata-2.2.0/models/musiclibraryitemroot.h000066400000000000000000000106261316350454000210600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_ITEM_ROOT_H #define MUSIC_LIBRARY_ITEM_ROOT_H #include #include #include #include #include #include #include "musiclibraryitem.h" #include "mpd-interface/song.h" #include "support/icon.h" class QXmlStreamReader; class QXmlStreamWriter; class MusicLibraryItemArtist; class MusicLibraryModel; class MusicLibraryErrorMonitor { public: virtual ~MusicLibraryErrorMonitor() { } virtual void loadError(const QString &) = 0; }; class MusicLibraryProgressMonitor { public: virtual ~MusicLibraryProgressMonitor() { } virtual void readProgress(double pc) =0; virtual void writeProgress(double pc) =0; virtual bool wasStopped() const =0; }; class MusicLibraryItemRoot : public MusicLibraryItemContainer { public: MusicLibraryItemRoot(const QString &name=QString(), bool albumArtistSupport=true, bool flatHierarchy=false) : MusicLibraryItemContainer(name, 0) , supportsAlbumArtist(albumArtistSupport) , isFlat(flatHierarchy) , m_model(0) { } virtual ~MusicLibraryItemRoot() { } virtual Icon icon() const { return Icon(); } virtual QImage image() const { return QImage(); } virtual Song fixPath(const Song &orig, bool) const { return orig; } virtual const QString & id() const { return m_itemData; } virtual bool canPlaySongs() const { return true; } virtual bool isOnlineService() const { return false; } virtual bool isDevice() const { return false; } MusicLibraryItemArtist * artist(const Song &s, bool create=true); MusicLibraryItemArtist * createArtist(const Song &s, bool forceComposer=false); void refreshIndexes(); void remove(MusicLibraryItemArtist *artist); QSet allSongs(bool revertVa=false) const; void getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres); void updateSongFile(const Song &from, const Song &to); void toXML(const QString &filename, MusicLibraryProgressMonitor *prog=0) const; void toXML(QXmlStreamWriter &writer, MusicLibraryProgressMonitor *prog=0) const; bool fromXML(const QString &filename, const QString &baseFolder=QString(), MusicLibraryProgressMonitor *prog=0, MusicLibraryErrorMonitor *em=0); bool fromXML(QXmlStreamReader &reader, const QString &baseFolder=QString(), MusicLibraryProgressMonitor *prog=0, MusicLibraryErrorMonitor *em=0); Type itemType() const { return Type_Root; } void add(const QSet &songs); bool supportsAlbumArtistTag() const { return supportsAlbumArtist; } void setSupportsAlbumArtistTag(bool s) { supportsAlbumArtist=s; } void clearItems(); void setModel(MusicLibraryModel *m) { m_model=m; } bool flat() const { return isFlat; } virtual QModelIndex index() const { return QModelIndex(); } bool update(const QSet &songs); const MusicLibraryItem * findSong(const Song &s) const; bool songExists(const Song &s) const; bool updateSong(const Song &orig, const Song &edit); void addSongToList(const Song &s); void removeSongFromList(const Song &s); static QString artistName(const Song &s, bool forceComposer=false); protected: QString songArtist(const Song &s, bool isLoadingCache=false) const; MusicLibraryItemArtist * getArtist(const QString &key) const; protected: bool supportsAlbumArtist; // TODO: ALBUMARTIST: Remove when libMPT supports album artist! bool isFlat; mutable QHash m_indexes; MusicLibraryModel *m_model; }; #endif cantata-2.2.0/models/musiclibraryitemsong.h000066400000000000000000000037041316350454000210420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_ITEM_SONG_H #define MUSIC_LIBRARY_ITEM_SONG_H #include #include #include "musiclibraryitem.h" #include "mpd-interface/song.h" class MusicLibraryItemAlbum; class MusicLibraryItemSong : public MusicLibraryItem { public: MusicLibraryItemSong(const Song &s, MusicLibraryItemContainer *parent) : MusicLibraryItem(parent), m_song(s) { } virtual ~MusicLibraryItemSong() { } QString data() const { return m_song.displayTitle(); } const QString & file() const { return m_song.file; } void setSong(const Song &s) { m_song=s; } void setFile(const QString &f) { m_song.file=f; } quint16 track() const { return m_song.track; } void setTrack(quint16 t) { m_song.track=t; } void setPlayed(bool p) { m_song.setPlayed(p); } quint16 disc() const { return m_song.disc; } quint32 time() const { return m_song.time; } QString genre() const { return m_song.firstGenre(); } const Song & song() const { return m_song; } Type itemType() const { return Type_Song; } protected: Song m_song; }; #endif cantata-2.2.0/models/musiclibrarymodel.cpp000066400000000000000000000320311316350454000206430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "musiclibraryitemalbum.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemsong.h" #include "musiclibraryitemroot.h" #include "musiclibrarymodel.h" #include "roles.h" #include "support/utils.h" #include "widgets/icons.h" #include "gui/settings.h" #include #include #include #include #include #include #include MusicLibraryModel::MusicLibraryModel(QObject *parent) : ActionModel(parent) , rootItem(new MusicLibraryItemRoot) { rootItem->setModel(this); } MusicLibraryModel::~MusicLibraryModel() { delete rootItem; } QModelIndex MusicLibraryModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const MusicLibraryItem * parentItem; if (!parent.isValid()) { parentItem = rootItem; } else { parentItem = static_cast(parent.internalPointer()); } MusicLibraryItem * const childItem = parentItem->childItem(row); if (childItem) { return createIndex(row, column, childItem); } return QModelIndex(); } QModelIndex MusicLibraryModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const MusicLibraryItem * const childItem = static_cast(index.internalPointer()); MusicLibraryItem * const parentItem = childItem->parentItem(); if (parentItem == rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } int MusicLibraryModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } const MusicLibraryItem *parentItem; if (!parent.isValid()) { parentItem = rootItem; } else { parentItem = static_cast(parent.internalPointer()); } return parentItem->childCount(); } const MusicLibraryItemRoot * MusicLibraryModel::root(const MusicLibraryItem *item) const { if (MusicLibraryItem::Type_Root==item->itemType()) { return static_cast(item); } if (MusicLibraryItem::Type_Artist==item->itemType()) { return static_cast(item->parentItem()); } if (MusicLibraryItem::Type_Album==item->itemType()) { return static_cast(item->parentItem()->parentItem()); } if (MusicLibraryItem::Type_Song==item->itemType()) { return root(item->parentItem()); } return 0; } static QString parentData(const MusicLibraryItem *i) { QString data; const MusicLibraryItem *itm=i; while (itm->parentItem()) { if (!itm->parentItem()->data().isEmpty()) { if (MusicLibraryItem::Type_Root==itm->parentItem()->itemType()) { data=""+itm->parentItem()->data()+"
    "+data; } else { data=itm->parentItem()->data()+"
    "+data; } } itm=itm->parentItem(); } return data; } QVariant MusicLibraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (Qt::CheckStateRole==role) { return static_cast(index.internalPointer())->checkState(); } MusicLibraryItem *item = static_cast(index.internalPointer()); switch (role) { case Qt::DecorationRole: switch (item->itemType()) { case MusicLibraryItem::Type_Root: return static_cast(item)->icon(); case MusicLibraryItem::Type_Artist: return Icons::self()->artistIcon; case MusicLibraryItem::Type_Album: return Icons::self()->albumIcon(32, true); case MusicLibraryItem::Type_Song: return Song::Playlist==static_cast(item)->song().type ? Icons::self()->playlistListIcon : Icons::self()->audioListIcon; default: return QVariant(); } case Cantata::Role_BriefMainText: if (MusicLibraryItem::Type_Album==item->itemType()) { return item->data(); } case Cantata::Role_MainText: case Qt::DisplayRole: if (MusicLibraryItem::Type_Song==item->itemType()) { MusicLibraryItemSong *song = static_cast(item); if (Song::Playlist==song->song().type) { return song->song().isCueFile() ? tr("Cue Sheet") : tr("Playlist"); } if (MusicLibraryItem::Type_Root==song->parentItem()->itemType()) { return song->song().trackAndTitleStr(); } return song->song().trackAndTitleStr(); } return item->displayData(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } if (MusicLibraryItem::Type_Song==item->itemType()) { return static_cast(item)->song().toolTip(); } return parentData(item)+ (0==item->childCount() ? item->displayData(true) : (item->displayData(true)+"
    "+data(index, Cantata::Role_SubText).toString())); case Cantata::Role_SubText: switch (item->itemType()) { case MusicLibraryItem::Type_Root: { MusicLibraryItemRoot *collection=static_cast(item); if (collection->flat()) { return tr("%n Track(s)", "", item->childCount()); } return tr("%n Artist(s)", "", item->childCount()); } case MusicLibraryItem::Type_Artist: return tr("%n Album(s)", "", item->childCount()); case MusicLibraryItem::Type_Song: return Utils::formatTime(static_cast(item)->time(), true); case MusicLibraryItem::Type_Album: return tr("%n Tracks (%1)", "", static_cast(item)->trackCount()) .arg(Utils::formatTime(static_cast(item)->totalTime())); default: return QVariant(); } case Cantata::Role_ListImage: switch (item->itemType()) { case MusicLibraryItem::Type_Album: return true; default: return false; } case Cantata::Role_CoverSong: { QVariant v; switch (item->itemType()) { case MusicLibraryItem::Type_Album: v.setValue(static_cast(item)->coverSong()); break; case MusicLibraryItem::Type_Artist: v.setValue(static_cast(item)->coverSong()); break; default: break; } return v; } case Cantata::Role_TitleText: if (MusicLibraryItem::Type_Album==item->itemType()) { return tr("%1 by %2", "Album by Artist").arg(item->data()).arg(item->parentItem()->data()); } return item->data(); default: break; } return ActionModel::data(index, role); } bool MusicLibraryModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (Qt::CheckStateRole==role) { if (!idx.isValid()) { return false; } MusicLibraryItem *item = static_cast(idx.internalPointer()); Qt::CheckState check=value.toBool() ? Qt::Checked : Qt::Unchecked; if (item->checkState()==check) { return false; } switch (item->itemType()) { case MusicLibraryItem::Type_Artist: { MusicLibraryItemArtist *artistItem=static_cast(item); QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex()); item->setCheckState(check); foreach (MusicLibraryItem *album, artistItem->childItems()) { if (check!=album->checkState()) { MusicLibraryItemAlbum *albumItem=static_cast(album); QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex); album->setCheckState(check); foreach (MusicLibraryItem *song, albumItem->childItems()) { song->setCheckState(check); } emit dataChanged(index(0, 0, albumIndex), index(0, albumItem->childCount(), albumIndex)); } emit dataChanged(index(0, 0, artistIndex), index(0, artistItem->childCount(), artistIndex)); } emit dataChanged(idx, idx); break; } case MusicLibraryItem::Type_Album: { MusicLibraryItemArtist *artistItem=static_cast(item->parentItem()); MusicLibraryItemAlbum *albumItem=static_cast(item); QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex()); item->setCheckState(check); foreach (MusicLibraryItem *song, albumItem->childItems()) { song->setCheckState(check); } setParentState(artistIndex); emit dataChanged(idx, idx); break; } case MusicLibraryItem::Type_Song: { item->setCheckState(check); MusicLibraryItemAlbum *albumItem=static_cast(item->parentItem()); MusicLibraryItemArtist *artistItem=static_cast(albumItem->parentItem()); QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex()); QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex); setParentState(albumIndex); setParentState(artistIndex); emit dataChanged(idx, idx); break; } case MusicLibraryItem::Type_Root: return false; } return true; } return ActionModel::setData(idx, value, role); } void MusicLibraryModel::setParentState(const QModelIndex &parent) { MusicLibraryItemContainer *parentItem=static_cast(parent.internalPointer()); Qt::CheckState parentCheck=parentItem->checkState(); bool haveCheckedChildren=false; bool haveUncheckedChildren=false; bool stop=false; foreach (MusicLibraryItem *child, parentItem->childItems()) { switch (child->checkState()) { case Qt::PartiallyChecked: parentCheck=Qt::PartiallyChecked; stop=true; break; case Qt::Unchecked: haveUncheckedChildren=true; parentCheck=haveCheckedChildren ? Qt::PartiallyChecked : Qt::Unchecked; stop=haveCheckedChildren && haveUncheckedChildren; break; case Qt::Checked: haveCheckedChildren=true; parentCheck=haveUncheckedChildren ? Qt::PartiallyChecked : Qt::Checked; stop=haveCheckedChildren && haveUncheckedChildren; break; } if (stop) { break; } } if (parentItem->checkState()!=parentCheck) { parentItem->setCheckState(parentCheck); emit dataChanged(parent, parent); } } void MusicLibraryModel::clear() { beginResetModel(); rootItem->clear(); endResetModel(); } void MusicLibraryModel::setSongs(const QSet &songs) { rootItem->update(songs); // foreach (MusicLibraryItem *artist, rootItem->childItems()) { // MusicLibraryItemArtist *artistItem=static_cast(artist); // artistItem->setCheckState(Qt::Checked); // foreach (MusicLibraryItem *album, artistItem->childItems()) { // MusicLibraryItemAlbum *albumItem=static_cast(album); // albumItem->setCheckState(Qt::Checked); // foreach (MusicLibraryItem *song, albumItem->childItems()) { // song->setCheckState(Qt::Checked); // } // } // } } Qt::ItemFlags MusicLibraryModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } return Qt::ItemIsDropEnabled; } cantata-2.2.0/models/musiclibrarymodel.h000066400000000000000000000044361316350454000203200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_MODEL_H #define MUSIC_LIBRARY_MODEL_H #include #include #include "musiclibraryitemroot.h" #include "musiclibraryitemalbum.h" #include "mpd-interface/song.h" #include "actionmodel.h" class QMimeData; class MusicLibraryItemArtist; class MusicLibraryModel : public ActionModel { Q_OBJECT public: MusicLibraryModel(QObject *parent=0); ~MusicLibraryModel(); QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const; QModelIndex parent(const QModelIndex &) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &i=QModelIndex()) const { Q_UNUSED(i) return 1; } QVariant data(const QModelIndex &, int) const; bool setData(const QModelIndex &idx, const QVariant &value, int role = Qt::EditRole); Qt::ItemFlags flags(const QModelIndex &index) const; void clear(); void setSongs(const QSet &songs); void setSupportsAlbumArtistTag(bool s) { rootItem->setSupportsAlbumArtistTag(s); } virtual int row(void *i) const { return rootItem->indexOf(static_cast(i)); } protected: const MusicLibraryItemRoot * root(const MusicLibraryItem *item) const; private: void setParentState(const QModelIndex &parent); private: MusicLibraryItemRoot *rootItem; friend class MusicLibraryItemRoot; friend class DevicesModel; friend class Device; }; #endif cantata-2.2.0/models/musiclibraryproxymodel.cpp000066400000000000000000000114761316350454000217570ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "musiclibraryitem.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemalbum.h" #include "musiclibraryitemsong.h" #include "musiclibraryproxymodel.h" MusicLibraryProxyModel::MusicLibraryProxyModel(QObject *parent) : ProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); } bool MusicLibraryProxyModel::filterAcceptsRoot(const MusicLibraryItem *item) const { foreach (const MusicLibraryItem *i, static_cast(item)->childItems()) { if (MusicLibraryItem::Type_Artist==i->itemType() && filterAcceptsArtist(i)) { return true; } else if (MusicLibraryItem::Type_Song==i->itemType() && filterAcceptsSong(i)) { return true; } } return false; } bool MusicLibraryProxyModel::filterAcceptsArtist(const MusicLibraryItem *item) const { foreach (const MusicLibraryItem *i, static_cast(item)->childItems()) { if (filterAcceptsAlbum(i)) { return true; } } return false; } bool MusicLibraryProxyModel::filterAcceptsAlbum(const MusicLibraryItem *item) const { foreach (const MusicLibraryItem *i, static_cast(item)->childItems()) { if (filterAcceptsSong(i)) { return true; } } return false; } bool MusicLibraryProxyModel::filterAcceptsSong(const MusicLibraryItem *item) const { return matchesFilter(static_cast(item)->song()); } bool MusicLibraryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { return true; } if (!isChildOfRoot(sourceParent)) { return true; } const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const MusicLibraryItem *item = static_cast(index.internalPointer()); if (filter) { if (item==filter) { // Accept top-level item! return true; } const MusicLibraryItem *p=item->parentItem(); while (p && p->parentItem()) { p=p->parentItem(); } if (p!=filter) { // Accept all items that are not children of top-level item! return true; } } if (filterStrings.isEmpty()) { return true; } switch (item->itemType()) { case MusicLibraryItem::Type_Root: return filterAcceptsRoot(item); case MusicLibraryItem::Type_Artist: return filterAcceptsArtist(item); case MusicLibraryItem::Type_Album: return filterAcceptsAlbum(item); case MusicLibraryItem::Type_Song: return filterAcceptsSong(item); default: break; } return false; } bool MusicLibraryProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (left.row()<0 || right.row()<0) { return left.row()<0; } if (static_cast(left.internalPointer())->itemType() == MusicLibraryItem::Type_Song) { MusicLibraryItemSong *l=static_cast(left.internalPointer()); MusicLibraryItemSong *r=static_cast(right.internalPointer()); return l->song()song(); } else if (static_cast(left.internalPointer())->itemType() == MusicLibraryItem::Type_Album) { return MusicLibraryItemAlbum::lessThan(static_cast(left.internalPointer()), static_cast(right.internalPointer())); } else if (static_cast(left.internalPointer())->itemType() == MusicLibraryItem::Type_Artist) { return MusicLibraryItemArtist::lessThan(static_cast(left.internalPointer()), static_cast(right.internalPointer())); } return QSortFilterProxyModel::lessThan(left, right); } cantata-2.2.0/models/musiclibraryproxymodel.h000066400000000000000000000030701316350454000214130ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MUSIC_LIBRARY_SORT_FILTER_MODEL_H #define MUSIC_LIBRARY_SORT_FILTER_MODEL_H #include "proxymodel.h" class MusicLibraryItem; class MusicLibraryProxyModel : public ProxyModel { Q_OBJECT public: MusicLibraryProxyModel(QObject *parent = 0); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool lessThan(const QModelIndex &left, const QModelIndex &right) const; private: bool filterAcceptsRoot(const MusicLibraryItem *item) const; bool filterAcceptsArtist(const MusicLibraryItem *item) const; bool filterAcceptsAlbum(const MusicLibraryItem *item) const; bool filterAcceptsSong(const MusicLibraryItem *item) const; }; #endif cantata-2.2.0/models/playlistsmodel.cpp000066400000000000000000000776721316350454000202060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include #include #include #include "config.h" #include "playlistsmodel.h" #include "playlistsproxymodel.h" #include "playqueuemodel.h" #include "widgets/groupedview.h" #include "roles.h" #include "gui/covers.h" #include "gui/settings.h" #include "support/utils.h" #include "support/monoicon.h" #include "support/globalstatic.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdstats.h" #include "mpd-interface/mpdconnection.h" #include "playqueuemodel.h" #include "widgets/icons.h" #include "widgets/mirrormenu.h" #ifdef ENABLE_HTTP_SERVER #include "http/httpserver.h" #endif QString PlaylistsModel::headerText(int col) { switch (col) { case COL_TITLE: return PlayQueueModel::headerText(PlayQueueModel::COL_TITLE); case COL_ARTIST: return PlayQueueModel::headerText(PlayQueueModel::COL_ARTIST); case COL_ALBUM: return PlayQueueModel::headerText(PlayQueueModel::COL_ALBUM); case COL_LENGTH: return PlayQueueModel::headerText(PlayQueueModel::COL_LENGTH); case COL_YEAR: return PlayQueueModel::headerText(PlayQueueModel::COL_YEAR); case COL_GENRE: return PlayQueueModel::headerText(PlayQueueModel::COL_GENRE); case COL_COMPOSER: return PlayQueueModel::headerText(PlayQueueModel::COL_COMPOSER); case COL_PERFORMER: return PlayQueueModel::headerText(PlayQueueModel::COL_PERFORMER); default: return QString(); } } GLOBAL_STATIC(PlaylistsModel, instance) PlaylistsModel::PlaylistsModel(QObject *parent) : ActionModel(parent) , multiCol(false) , itemMenu(0) , dropAdjust(0) { icn.addFile(":playlist.svg"); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionStateChanged(bool))); connect(MPDConnection::self(), SIGNAL(playlistsRetrieved(const QList &)), this, SLOT(setPlaylists(const QList &))); connect(MPDConnection::self(), SIGNAL(playlistInfoRetrieved(const QString &, const QList &)), this, SLOT(playlistInfoRetrieved(const QString &, const QList &))); connect(MPDConnection::self(), SIGNAL(removedFromPlaylist(const QString &, const QList &)), this, SLOT(removedFromPlaylist(const QString &, const QList &))); connect(MPDConnection::self(), SIGNAL(playlistRenamed(const QString &, const QString &)), this, SLOT(playlistRenamed(const QString &, const QString &))); connect(MPDConnection::self(), SIGNAL(movedInPlaylist(const QString &, const QList &, quint32)), this, SLOT(movedInPlaylist(const QString &, const QList &, quint32))); connect(this, SIGNAL(listPlaylists()), MPDConnection::self(), SLOT(listPlaylists())); connect(this, SIGNAL(playlistInfo(const QString &)), MPDConnection::self(), SLOT(playlistInfo(const QString &))); connect(this, SIGNAL(addToPlaylist(const QString &, const QStringList, quint32, quint32)), MPDConnection::self(), SLOT(addToPlaylist(const QString &, const QStringList, quint32, quint32))); connect(this, SIGNAL(moveInPlaylist(const QString &, const QList &, quint32, quint32)), MPDConnection::self(), SLOT(moveInPlaylist(const QString &, const QList &, quint32, quint32))); connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); newAction=new QAction(MonoIcon::icon(FontAwesome::asterisk, Utils::monoIconColor()), tr("New Playlist..."), this); connect(newAction, SIGNAL(triggered()), this, SIGNAL(addToNew())); Action::initIcon(newAction); alignments[COL_TITLE]=alignments[COL_ARTIST]=alignments[COL_ALBUM]=alignments[COL_GENRE]=alignments[COL_COMPOSER]=alignments[COL_PERFORMER]=int(Qt::AlignVCenter|Qt::AlignLeft); alignments[COL_LENGTH]=alignments[COL_YEAR]=int(Qt::AlignVCenter|Qt::AlignRight); } PlaylistsModel::~PlaylistsModel() { if (itemMenu) { itemMenu->deleteLater(); itemMenu=0; } } QString PlaylistsModel::name() const { return QLatin1String("playlists"); } QString PlaylistsModel::title() const { return tr("Stored Playlists"); } QString PlaylistsModel::descr() const { return tr("Standard playlists"); } int PlaylistsModel::rowCount(const QModelIndex &index) const { if (index.column()>0) { return 0; } if (!index.isValid()) { return items.size(); } Item *item=static_cast(index.internalPointer()); if (item->isPlaylist()) { PlaylistItem *pl=static_cast(index.internalPointer()); return pl->songs.count(); } return 0; } bool PlaylistsModel::canFetchMore(const QModelIndex &index) const { if (!index.isValid()) { return false; } Item *item=static_cast(index.internalPointer()); return item && item->isPlaylist() && !static_cast(item)->loaded; } void PlaylistsModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } Item *item=static_cast(index.internalPointer()); if (item->isPlaylist() && !static_cast(item)->loaded) { static_cast(item)->loaded=true; emit playlistInfo(static_cast(item)->name); } } bool PlaylistsModel::hasChildren(const QModelIndex &parent) const { return !parent.isValid() || static_cast(parent.internalPointer())->isPlaylist(); } QModelIndex PlaylistsModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } Item *item=static_cast(index.internalPointer()); if(item->isPlaylist()) { return QModelIndex(); } else { SongItem *song=static_cast(item); if (song->parent) { return createIndex(items.indexOf(song->parent), 0, song->parent); } } return QModelIndex(); } QModelIndex PlaylistsModel::index(int row, int col, const QModelIndex &parent) const { if (!hasIndex(row, col, parent)) { return QModelIndex(); } if (parent.isValid()) { Item *p=static_cast(parent.internalPointer()); if (p->isPlaylist()) { PlaylistItem *pl=static_cast(p); return rowsongs.count() ? createIndex(row, col, pl->songs.at(row)) : QModelIndex(); } } return row=0) { int al=value.toInt()|Qt::AlignVCenter; if (al!=alignments[section]) { alignments[section]=al; return true; } } return false; } QVariant PlaylistsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: return descr(); case Qt::DecorationRole: return icon(); } return QVariant(); } if (Qt::TextAlignmentRole==role) { return alignments[index.column()]; } Item *item=static_cast(index.internalPointer()); if (item->isPlaylist()) { PlaylistItem *pl=static_cast(item); switch(role) { case Cantata::Role_ListImage: return false; case Cantata::Role_IsCollection: return true; case Cantata::Role_CollectionId: return pl->key; case Cantata::Role_Key: return 0; case Cantata::Role_SongWithRating: case Cantata::Role_Song: { QVariant var; var.setValue(Song()); return var; } case Cantata::Role_AlbumDuration: return pl->totalTime(); case Cantata::Role_SongCount: if (!pl->loaded) { pl->loaded=true; emit playlistInfo(pl->name); } return pl->songs.count(); case Cantata::Role_CurrentStatus: case Cantata::Role_Status: return (int)GroupedView::State_Default; case Qt::FontRole: if (multiCol) { QFont font; font.setBold(true); return font; } return QVariant(); case Cantata::Role_TitleText: case Cantata::Role_MainText: case Qt::DisplayRole: if (multiCol) { switch (index.column()) { case COL_TITLE: return pl->visibleName(); case COL_ARTIST: case COL_ALBUM: return QVariant(); case COL_LENGTH: if (!pl->loaded) { pl->loaded=true; emit playlistInfo(pl->name); } return pl->loaded && !pl->isSmartPlaylist ? Utils::formatTime(pl->totalTime()) : QVariant(); case COL_YEAR: case COL_GENRE: return QVariant(); default: break; } } return pl->visibleName(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } if (!pl->loaded) { pl->loaded=true; emit playlistInfo(pl->name); } return 0==pl->songs.count() ? pl->visibleName() : pl->visibleName()+"\n"+tr("%n Tracks (%1)", "", pl->songs.count()).arg(Utils::formatTime(pl->totalTime())); case Qt::DecorationRole: return multiCol ? QVariant() : (pl->isSmartPlaylist ? Icons::self()->dynamicListIcon : Icons::self()->playlistListIcon); case Cantata::Role_SubText: if (!pl->loaded) { pl->loaded=true; emit playlistInfo(pl->name); } if (pl->isSmartPlaylist) { return tr("Smart Playlist"); } return tr("%n Tracks (%1)", "", pl->songs.count()).arg(Utils::formatTime(pl->totalTime())); case Cantata::Role_TitleActions: return true; default: return ActionModel::data(index, role); } } else { SongItem *s=static_cast(item); switch (role) { case Cantata::Role_ListImage: return true; case Cantata::Role_IsCollection: return false; case Cantata::Role_CollectionId: return s->parent->key; case Cantata::Role_Key: return s->key; case Cantata::Role_CoverSong: case Cantata::Role_SongWithRating: case Cantata::Role_Song: { QVariant var; var.setValue(*s); return var; } case Cantata::Role_AlbumDuration: { quint32 d=s->time; for (int i=index.row()+1; iparent->songs.count(); ++i) { const SongItem *song = s->parent->songs.at(i); if (song->key!=s->key) { break; } d+=song->time; } if (index.row()>1) { for (int i=index.row()-1; i<=0; ++i) { const SongItem *song = s->parent->songs.at(i); if (song->key!=s->key) { break; } d+=song->time; } } return d; } case Cantata::Role_SongCount:{ quint32 count=1; for (int i=index.row()+1; iparent->songs.count(); ++i) { const SongItem *song = s->parent->songs.at(i); if (song->key!=s->key) { break; } count++; } if (index.row()>1) { for (int i=index.row()-1; i<=0; ++i) { const SongItem *song = s->parent->songs.at(i); if (song->key!=s->key) { break; } count++; } } return count; } case Cantata::Role_CurrentStatus: case Cantata::Role_Status: return (int)GroupedView::State_Default; case Qt::DisplayRole: if (multiCol) { switch (index.column()) { case COL_TITLE: return s->trackAndTitleStr(false); case COL_ARTIST: return s->artist.isEmpty() ? Song::unknown() : s->artist; case COL_ALBUM: if (s->isStream() && s->album.isEmpty()) { QString n=s->name(); if (!n.isEmpty()) { return n; } } return s->album; case COL_LENGTH: return Utils::formatTime(s->time); case COL_YEAR: if (s->year <= 0) { return QVariant(); } return s->year; case COL_GENRE: return s->displayGenre(); case COL_COMPOSER: return s->composer(); case COL_PERFORMER: return s->performer(); default: break; } } return s->entryName(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } return s->toolTip(); case Qt::DecorationRole: return multiCol ? QVariant() : (s->title.isEmpty() ? Icons::self()->streamIcon : Icons::self()->audioListIcon); case Cantata::Role_MainText: return s->title.isEmpty() ? s->name().isEmpty() ? s->file : s->name() : s->title; case Cantata::Role_SubText: return (s->artist.isEmpty() ? QString() : (s->artist+QString(" – ")))+ (s->displayAlbum().isEmpty() ? QString() : (s->displayAlbum()+QString(" – ")))+ (s->time>0 ? Utils::formatTime(s->time) : QString()); default: return ActionModel::data(index, role); } } return QVariant(); } bool PlaylistsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (Cantata::Role_DropAdjust==role) { dropAdjust=value.toUInt(); return true; } else { return QAbstractItemModel::setData(index, value, role); } } Qt::ItemFlags PlaylistsModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; } return Qt::NoItemFlags; } Qt::DropActions PlaylistsModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } static QString songFileName(const Song &song) { if (song.isStream() && song.title.isEmpty()) { return MPDParseUtils::addStreamName(song.file, song.name()); } return song.file; } QStringList PlaylistsModel::filenames(const QModelIndexList &indexes, bool filesOnly) const { QStringList fnames; QSet selectedPlaylists; foreach(QModelIndex index, indexes) { Item *item=static_cast(index.internalPointer()); if (item->isPlaylist()) { selectedPlaylists.insert(item); foreach (const SongItem *s, static_cast(item)->songs) { if (!filesOnly || !s->file.contains(':')) { fnames << s->file; } } } else if (!selectedPlaylists.contains(static_cast(item)->parent)) { if (!filesOnly || !static_cast(item)->file.contains(':')) { fnames << songFileName(* static_cast(item)); } } } return fnames; } QList PlaylistsModel::songs(const QModelIndexList &indexes) const { QList songs; QSet selectedPlaylists; foreach (const QModelIndex &idx, indexes) { Item *item=static_cast(idx.internalPointer()); if (item->isPlaylist()) { PlaylistItem *playlist=static_cast(item); foreach (const SongItem *song, playlist->songs) { selectedPlaylists.insert(item); songs.append(*song); } } } foreach (const QModelIndex &idx, indexes) { PlaylistsModel::Item *item=static_cast(idx.internalPointer()); if (!item->isPlaylist()) { SongItem *song=static_cast(item); if (!selectedPlaylists.contains(song->parent)) { songs.append(*song); } } } return songs; } static const QLatin1String constPlaylistNameMimeType("cantata/playlistnames"); static const QLatin1String constPositionsMimeType("cantata/positions"); static const char *constPlaylistProp="hasPlaylist"; QMimeData * PlaylistsModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QStringList filenames; QStringList playlists; QList positions; QSet selectedPlaylists; foreach(QModelIndex index, indexes) { if (!index.isValid() || 0!=index.column()) { continue; } Item *item=static_cast(index.internalPointer()); if (item->isPlaylist()) { PlaylistItem *playlist=static_cast(item); int pos=0; selectedPlaylists.insert(item); if (playlist->songs.isEmpty()) { filenames << MPDConnection::constPlaylistPrefix+playlist->name; playlists << playlist->name; positions << pos++; } else { foreach (const SongItem *s, playlist->songs) { filenames << s->file; playlists << playlist->name; positions << pos++; } } mimeData->setProperty(constPlaylistProp, true); } else if (!selectedPlaylists.contains(static_cast(item)->parent)) { filenames << static_cast(item)->file; playlists << static_cast(item)->parent->name; positions << static_cast(item)->parent->songs.indexOf(static_cast(item)); } } PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames); PlayQueueModel::encode(*mimeData, constPlaylistNameMimeType, playlists); PlayQueueModel::encode(*mimeData, constPositionsMimeType, positions); return mimeData; } bool PlaylistsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int col, const QModelIndex &parent) { // Dont allow drag'n' drop of complete playlists... if (data->property(constPlaylistProp).isValid()) { return false; } // We may have dropped onto a song, so use songs row number... int origRow=row; if (row<0) { row=parent.row(); } if (origRow<0 && dropAdjust) { // Unlike the playqueue, we 'copy' items, so need to adjust 1 more place... dropAdjust++; } row+=dropAdjust; Q_UNUSED(col) if (Qt::IgnoreAction==action) { return true; } if (!parent.isValid()) { return false; } if (data->hasFormat(PlayQueueModel::constFileNameMimeType)) { Item *item=static_cast(parent.internalPointer()); PlaylistItem *pl=item->isPlaylist() ? static_cast(item) : static_cast(item)->parent; if (!pl) { return false; } QStringList filenames=PlayQueueModel::decode(*data, PlayQueueModel::constFileNameMimeType); if (data->hasFormat(PlayQueueModel::constMoveMimeType)) { emit addToPlaylist(pl->name, filenames, row < 0 || (origRow<0 && item->isPlaylist()) ? 0 : row, row < 0 || (origRow<0 && item->isPlaylist()) ? 0 : pl->songs.size()); return true; } else if (data->hasFormat(constPlaylistNameMimeType)) { QStringList playlists=PlayQueueModel::decode(*data, constPlaylistNameMimeType); bool fromThis=false; bool fromOthers=false; foreach (const QString &p, playlists) { if (p==pl->name) { fromThis=true; } else { fromOthers=true; } if (fromThis && fromOthers) { return false; // Weird mix... } } if (fromThis) { emit moveInPlaylist(pl->name, PlayQueueModel::decodeInts(*data, constPositionsMimeType), row < 0 ? pl->songs.size() : row, pl->songs.size()); return true; } else { emit addToPlaylist(pl->name, filenames, row < 0 || (origRow<0 && item->isPlaylist()) ? 0 : row, row < 0 || (origRow<0 && item->isPlaylist()) ? 0 : pl->songs.size()); return true; } } } return false; } QStringList PlaylistsModel::mimeTypes() const { QStringList types; types << PlayQueueModel::constFileNameMimeType; return types; } void PlaylistsModel::getPlaylists() { emit listPlaylists(); } void PlaylistsModel::clear() { beginResetModel(); clearPlaylists(); updateItemMenu(); endResetModel(); } MirrorMenu * PlaylistsModel::menu() { if (!itemMenu) { updateItemMenu(true); } return itemMenu; } void PlaylistsModel::setPlaylists(const QList &playlists) { if (items.isEmpty()) { if (playlists.isEmpty()) { return; } beginResetModel(); foreach (const Playlist &p, playlists) { items.append(new PlaylistItem(p, allocateKey())); } endResetModel(); updateItemMenu(); } else if (playlists.isEmpty()) { clear(); } else { QModelIndex parent=QModelIndex(); QSet existing; QSet retreived; QSet removed; QSet added; foreach (PlaylistItem *p, items) { existing.insert(p->name); } foreach (const Playlist &p, playlists) { retreived.insert(p.name); PlaylistItem *pl=getPlaylist(p.name); if (pl && pl->lastModifiedlastModified=p.lastModified; if (pl->loaded && !pl->isSmartPlaylist) { emit playlistInfo(pl->name); } } } removed=existing-retreived; added=retreived-existing; if (removed.count()) { foreach (const QString &p, removed) { PlaylistItem *pl=getPlaylist(p); if (pl) { int index=items.indexOf(pl); beginRemoveRows(parent, index, index); usedKeys.remove(pl->key); emit playlistRemoved(pl->key); delete items.takeAt(index); endRemoveRows(); } } } if (added.count()) { beginInsertRows(parent, items.count(), items.count()+added.count()-1); foreach (const QString &p, added) { int idx=playlists.indexOf(Playlist(p)); if (-1!=idx) { items.append(new PlaylistItem(playlists.at(idx), allocateKey())); } } endInsertRows(); } if (added.count() || removed.count()) { updateItemMenu(); } } } void PlaylistsModel::playlistInfoRetrieved(const QString &name, const QList &songs) { PlaylistItem *pl=getPlaylist(name); if (pl) { QModelIndex idx=createIndex(items.indexOf(pl), 0, pl); // If Cantata moves songs, then movedInPlaylist() is called. Likwise if // Cantata deltes songs, removedFromPlaylist() is called. Therefore, no // need to do partial updates here. If another client does a move whilst // a playlist has duplicate songs, then it can fail. #873 if (!pl->songs.isEmpty()) { beginRemoveRows(idx, 0, pl->songs.count()-1); pl->clearSongs(); endRemoveRows(); } if (!songs.isEmpty()) { beginInsertRows(idx, 0, songs.count()-1); foreach (const Song &s, songs) { pl->songs.append(new SongItem(s, pl)); } endInsertRows(); } emit updated(idx); emit dataChanged(idx, idx); } else { emit listPlaylists(); } } void PlaylistsModel::removedFromPlaylist(const QString &name, const QList &positions) { PlaylistItem *pl=0; if (0==positions.count() || !(pl=getPlaylist(name))) { emit listPlaylists(); return; } quint32 adjust=0; QModelIndex parent=createIndex(items.indexOf(pl), 0, pl); QList::ConstIterator it=positions.constBegin(); QList::ConstIterator end=positions.constEnd(); while(it!=end) { quint32 rowBegin=*it; quint32 rowEnd=*it; QList::ConstIterator next=it+1; while(next!=end) { if (*next!=(rowEnd+1)) { break; } else { it=next; rowEnd=*next; next++; } } beginRemoveRows(parent, rowBegin-adjust, rowEnd-adjust); for (quint32 i=rowBegin; i<=rowEnd; ++i) { delete pl->songs.takeAt(rowBegin-adjust); } adjust+=(rowEnd-rowBegin)+1; endRemoveRows(); it++; } emit updated(parent); } void PlaylistsModel::movedInPlaylist(const QString &name, const QList &idx, quint32 pos) { PlaylistItem *pl=0; if (!(pl=getPlaylist(name)) || idx.count()>pl->songs.count()) { emit listPlaylists(); return; } foreach (quint32 i, idx) { if (i>=(quint32)pl->songs.count()) { emit listPlaylists(); return; } } QList indexes=idx; QModelIndex parent=createIndex(items.indexOf(pl), 0, pl); while (indexes.count()) { quint32 from=indexes.takeLast(); if (from!=pos) { beginMoveRows(parent, from, from, parent, pos>from && pos<(quint32)pl->songs.count() ? pos+1 : pos); SongItem *si=pl->songs.takeAt(from); pl->songs.insert(pos, si); endMoveRows(); } } emit updated(parent); } void PlaylistsModel::emitAddToExisting() { QAction *act=qobject_cast(sender()); if (act) { emit addToExisting(Utils::strippedText(act->text())); } } void PlaylistsModel::playlistRenamed(const QString &from, const QString &to) { PlaylistItem *pl=getPlaylist(from); if (pl) { pl->name=to; updateItemMenu(); } } void PlaylistsModel::mpdConnectionStateChanged(bool connected) { if (!connected) { clear(); } else { getPlaylists(); } } void PlaylistsModel::coverLoaded(const Song &song, int s) { Q_UNUSED(s) if (!song.isArtistImageRequest() && !song.isComposerImageRequest()) { int plRow=0; foreach (const PlaylistItem *pl, items) { QModelIndex plIdx; int songRow=0; foreach (const SongItem *s, pl->songs) { if (s->albumArtist()==song.albumArtist() && s->album==song.album) { if (!plIdx.isValid()) { plIdx=index(plRow, 0, QModelIndex()); } QModelIndex idx=index(songRow, 0, plIdx); emit dataChanged(idx, idx); } songRow++; } plRow++; } } } void PlaylistsModel::updateItemMenu(bool create) { if (!itemMenu) { if (!create) { return; } itemMenu = new MirrorMenu(0); } itemMenu->clear(); itemMenu->addAction(newAction); QStringList names; foreach (const PlaylistItem *p, items) { if (!p->isSmartPlaylist) { names << p->name; } } qSort(names.begin(), names.end(), PlaylistsProxyModel::compareNames); foreach (const QString &n, names) { itemMenu->addAction(n, this, SLOT(emitAddToExisting())); } } PlaylistsModel::PlaylistItem * PlaylistsModel::getPlaylist(const QString &name) { foreach (PlaylistItem *p, items) { if (p->name==name) { return p; } } return 0; } void PlaylistsModel::clearPlaylists() { foreach (PlaylistItem *p, items) { usedKeys.remove(p->key); emit playlistRemoved(p->key); } qDeleteAll(items); items.clear(); } quint32 PlaylistsModel::allocateKey() { for(quint32 k=1; k<0xFFFFFFFE; ++k) { if (!usedKeys.contains(k)) { usedKeys.insert(k); return k; } } return 0xFFFFFFFF; } PlaylistsModel::PlaylistItem::PlaylistItem(const Playlist &pl, quint32 k) : name(pl.name) , time(0) , key(k) , lastModified(pl.lastModified) { loaded=isSmartPlaylist=MPDConnection::self()->isMopidy() && name.startsWith("Smart Playlist:"); if (isSmartPlaylist) { shortName=name.mid(16); } } PlaylistsModel::PlaylistItem::~PlaylistItem() { clearSongs(); } void PlaylistsModel::PlaylistItem::clearSongs() { qDeleteAll(songs); songs.clear(); } PlaylistsModel::SongItem * PlaylistsModel::PlaylistItem::getSong(const Song &song, int offset) { int i=0; foreach (SongItem *s, songs) { if (++itime; } } return time; } cantata-2.2.0/models/playlistsmodel.h000066400000000000000000000133321316350454000176320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef PLAYLISTS_MODEL_H #define PLAYLISTS_MODEL_H #include #include #include #include #include "mpd-interface/playlist.h" #include "mpd-interface/song.h" #include "support/icon.h" #include "actionmodel.h" class MirrorMenu; class QAction; class PlaylistsModel : public ActionModel { Q_OBJECT public: enum Columns { COL_TITLE, COL_ARTIST, COL_ALBUM, COL_YEAR, COL_GENRE, COL_LENGTH, COL_COMPOSER, COL_PERFORMER, COL_COUNT }; struct Item { virtual bool isPlaylist() = 0; virtual ~Item() { } }; struct PlaylistItem; struct SongItem : public Item, public Song { SongItem() : parent(0) { } SongItem(const Song &s, PlaylistItem *p=0) : Song(s), parent(p) { } bool isPlaylist() { return false; } PlaylistItem *parent; }; struct PlaylistItem : public Item { PlaylistItem(quint32 k) : loaded(false), isSmartPlaylist(false), time(0), key(k) { } PlaylistItem(const Playlist &pl, quint32 k); virtual ~PlaylistItem(); bool isPlaylist() { return true; } SongItem * getSong(const Song &song, int offset); void clearSongs(); quint32 totalTime(); const QString & visibleName() const { return shortName.isEmpty() ? name : shortName; } QString name; QString shortName; bool loaded; bool isSmartPlaylist; QList songs; quint32 time; quint32 key; QDateTime lastModified; }; static PlaylistsModel * self(); static QString headerText(int col); PlaylistsModel(QObject *parent = 0); ~PlaylistsModel(); QString name() const; QString title() const; QString descr() const; const Icon & icon() const { return icn; } int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const { Q_UNUSED(parent) return COL_COUNT; } bool canFetchMore(const QModelIndex &index) const; void fetchMore(const QModelIndex &index); bool hasChildren(const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &index) const; QModelIndex index(int row, int col, const QModelIndex &parent) const; QVariant data(const QModelIndex &, int) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); bool setData(const QModelIndex &index, const QVariant &value, int role); Qt::ItemFlags flags(const QModelIndex &index) const; Qt::DropActions supportedDropActions() const; QStringList filenames(const QModelIndexList &indexes, bool filesOnly=false) const; QList songs(const QModelIndexList &indexes) const; QMimeData * mimeData(const QModelIndexList &indexes) const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*col*/, const QModelIndex &parent); QStringList mimeTypes() const; void getPlaylists(); void clear(); bool exists(const QString &n) { return 0!=getPlaylist(n); } MirrorMenu * menu(); static QString strippedText(QString s); void setMultiColumn(bool m) { multiCol=m; } Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void add(const QStringList &files); void listPlaylists(); void playlistInfo(const QString &name) const; void addToPlaylist(const QString &name, const QStringList &songs, quint32 pos, quint32 size); void moveInPlaylist(const QString &name, const QList &idx, quint32 pos, quint32 size); void addToNew(); void addToExisting(const QString &name); void updated(const QModelIndex &idx); void playlistRemoved(quint32 key); // Used in Touch variant only... void updated(); private Q_SLOTS: void setPlaylists(const QList &playlists); void playlistInfoRetrieved(const QString &name, const QList &songs); void removedFromPlaylist(const QString &name, const QList &positions); void movedInPlaylist(const QString &name, const QList &idx, quint32 pos); void emitAddToExisting(); void playlistRenamed(const QString &from, const QString &to); void mpdConnectionStateChanged(bool connected); void coverLoaded(const Song &song, int s); private: void updateItemMenu(bool craete=false); PlaylistItem * getPlaylist(const QString &name); void clearPlaylists(); quint32 allocateKey(); private: Icon icn; bool multiCol; QList items; QSet usedKeys; MirrorMenu *itemMenu; quint32 dropAdjust; QAction *newAction; QMap alignments; }; #endif cantata-2.2.0/models/playlistsproxymodel.cpp000066400000000000000000000052251316350454000212710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "playlistsproxymodel.h" #include "playlistsmodel.h" PlaylistsProxyModel::PlaylistsProxyModel(QObject *parent) : ProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); } bool PlaylistsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { return true; } if (!isChildOfRoot(sourceParent)) { return true; } const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); PlaylistsModel::Item *item = static_cast(index.internalPointer()); if (item->isPlaylist()) { PlaylistsModel::PlaylistItem *pl = static_cast(item); if (matchesFilter(QStringList() << pl->name)) { return true; } foreach (PlaylistsModel::SongItem *s, pl->songs) { if (matchesFilter(*s)) { return true; } } } else { PlaylistsModel::SongItem *s = static_cast(item); return matchesFilter(*s); } return false; } bool PlaylistsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { PlaylistsModel::Item *l=static_cast(left.internalPointer()); PlaylistsModel::Item *r=static_cast(right.internalPointer()); if (l->isPlaylist() && r->isPlaylist()) { return compareNames(static_cast(l)->name, static_cast(r)->name); } else if(!l->isPlaylist() && !r->isPlaylist()) { return left.row() * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef PLAYLISTSPROXYMODEL_H #define PLAYLISTSPROXYMODEL_H #include "proxymodel.h" class PlaylistsProxyModel : public ProxyModel { Q_OBJECT public: PlaylistsProxyModel(QObject *parent = 0); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; static bool compareNames(const QString &l, const QString &r) { return l.localeAwareCompare(r)<0; } bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; #endif cantata-2.2.0/models/playqueuemodel.cpp000066400000000000000000001340371316350454000201610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "playqueuemodel.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdparseutils.h" #include "mpd-interface/mpdstats.h" #include "streams/streamfetcher.h" #include "streamsmodel.h" #include "http/httpserver.h" #include "gui/settings.h" #include "support/icon.h" #include "support/monoicon.h" #include "support/utils.h" #include "config.h" #include "support/action.h" #include "support/actioncollection.h" #include "support/globalstatic.h" #include "gui/covers.h" #include "widgets/groupedview.h" #include "widgets/icons.h" #include "roles.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devicesmodel.h" #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "devices/audiocddevice.h" #endif #endif #include #include #include #include #include #include #include #include #include #include GLOBAL_STATIC(PlayQueueModel, instance) const QLatin1String PlayQueueModel::constMoveMimeType("cantata/move"); const QLatin1String PlayQueueModel::constFileNameMimeType("cantata/filename"); const QLatin1String PlayQueueModel::constUriMimeType("text/uri-list"); static const char * constSortByKey="sort-by"; static const QLatin1String constSortByArtistKey("artist"); static const QLatin1String constSortByAlbumArtistKey("albumartist"); static const QLatin1String constSortByAlbumKey("album"); static const QLatin1String constSortByGenreKey("genre"); static const QLatin1String constSortByYearKey("year"); static const QLatin1String constSortByComposerKey("composer"); static const QLatin1String constSortByPerformerKey("performer"); static const QLatin1String constSortByTitleKey("title"); static const QLatin1String constSortByNumberKey("track"); static bool checkExtension(const QString &file) { static QSet constExtensions=QSet() << QLatin1String("mp3") << QLatin1String("ogg") << QLatin1String("flac") << QLatin1String("wma") << QLatin1String("m4a") << QLatin1String("m4b") << QLatin1String("mp4") << QLatin1String("m4p") << QLatin1String("wav") << QLatin1String("wv") << QLatin1String("wvp") << QLatin1String("aiff") << QLatin1String("aif") << QLatin1String("aifc") << QLatin1String("ape") << QLatin1String("spx") << QLatin1String("tta") << QLatin1String("mpc") << QLatin1String("mpp") << QLatin1String("mp+") << QLatin1String("dff") << QLatin1String("dsf"); int pos=file.lastIndexOf('.'); return pos>1 ? constExtensions.contains(file.mid(pos+1).toLower()) : false; } static QStringList parseUrls(const QStringList &urls, bool percentEncoded) { QStringList useable; foreach (const QString &path, urls) { QUrl u=percentEncoded ? QUrl::fromPercentEncoding(path.toUtf8()) : QUrl(path); #if defined ENABLE_DEVICES_SUPPORT && (defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND) QString cdDevice=AudioCdDevice::getDevice(u); if (!cdDevice.isEmpty()) { DevicesModel::self()->playCd(cdDevice); } else #endif if (QLatin1String("http")==u.scheme()) { useable.append(u.toString()); } else if ((u.scheme().isEmpty() || QLatin1String("file")==u.scheme()) && checkExtension(u.path())) { if (MPDConnection::self()->localFilePlaybackSupported() && !u.path().startsWith(QLatin1String("/media/"))) { useable.append(QLatin1String("file://")+u.path()); } else if (HttpServer::self()->isAlive()) { useable.append(HttpServer::self()->encodeUrl(u.path())); } } } return useable; } void PlayQueueModel::encode(QMimeData &mimeData, const QString &mime, const QStringList &values) { QByteArray encodedData; QTextStream stream(&encodedData, QIODevice::WriteOnly); foreach (const QString &v, values) { stream << v << endl; } mimeData.setData(mime, encodedData); } void PlayQueueModel::encode(QMimeData &mimeData, const QString &mime, const QList &values) { QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); foreach (quint32 v, values) { stream << v; } mimeData.setData(mime, encodedData); } QStringList PlayQueueModel::decode(const QMimeData &mimeData, const QString &mime) { QByteArray encodedData=mimeData.data(mime); QTextStream stream(&encodedData, QIODevice::ReadOnly); QStringList rv; while (!stream.atEnd()) { rv.append(stream.readLine().remove('\n')); } return rv; } QList PlayQueueModel::decodeInts(const QMimeData &mimeData, const QString &mime) { QByteArray encodedData=mimeData.data(mime); QDataStream stream(&encodedData, QIODevice::ReadOnly); QList rv; while (!stream.atEnd()) { quint32 v; stream >> v; rv.append(v); } return rv; } QString PlayQueueModel::headerText(int col) { switch (col) { case COL_TITLE: return tr("Title"); case COL_ARTIST: return tr("Artist"); case COL_ALBUM: return tr("Album"); case COL_TRACK: return tr("#", "Track number"); case COL_LENGTH: return tr("Length"); case COL_DISC: return tr("Disc"); case COL_YEAR: return tr("Year"); case COL_ORIGYEAR: return tr("Original Year"); case COL_GENRE: return tr("Genre"); case COL_PRIO: return tr("Priority"); case COL_COMPOSER: return tr("Composer"); case COL_PERFORMER: return tr("Performer"); case COL_RATING: return tr("Rating"); default: return QString(); } } PlayQueueModel::PlayQueueModel(QObject *parent) : QAbstractItemModel(parent) , currentSongId(-1) , currentSongRowNum(-1) , time(0) , mpdState(MPDState_Inactive) , stopAfterCurrent(false) , stopAfterTrackId(-1) , undoLimit(10) , undoEnabled(undoLimit>0) , lastCommand(Cmd_Other) , dropAdjust(0) { fetcher=new StreamFetcher(this); connect(this, SIGNAL(modelReset()), this, SLOT(stats())); connect(fetcher, SIGNAL(result(const QStringList &, int, int, quint8, bool)), SLOT(addFiles(const QStringList &, int, int, quint8, bool))); connect(fetcher, SIGNAL(result(const QStringList &, int, int, quint8, bool)), SIGNAL(streamsFetched())); connect(fetcher, SIGNAL(status(QString)), SIGNAL(streamFetchStatus(QString))); connect(this, SIGNAL(filesAdded(const QStringList, quint32, quint32, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList, quint32, quint32, int, quint8, bool))); connect(this, SIGNAL(populate(QStringList, QList)), MPDConnection::self(), SLOT(populate(QStringList, QList))); connect(this, SIGNAL(move(const QList &, quint32, quint32)), MPDConnection::self(), SLOT(move(const QList &, quint32, quint32))); connect(this, SIGNAL(setOrder(const QList &)), MPDConnection::self(), SLOT(setOrder(const QList &))); connect(MPDConnection::self(), SIGNAL(prioritySet(const QMap &)), SLOT(prioritySet(const QMap &))); connect(MPDConnection::self(), SIGNAL(stopAfterCurrentChanged(bool)), SLOT(stopAfterCurrentChanged(bool))); connect(this, SIGNAL(stop(bool)), MPDConnection::self(), SLOT(stopPlaying(bool))); connect(this, SIGNAL(clearStopAfter()), MPDConnection::self(), SLOT(clearStopAfter())); connect(this, SIGNAL(removeSongs(QList)), MPDConnection::self(), SLOT(removeSongs(QList))); connect(this, SIGNAL(clearEntries()), MPDConnection::self(), SLOT(clear())); connect(this, SIGNAL(addAndPlay(QString)), MPDConnection::self(), SLOT(addAndPlay(QString))); connect(this, SIGNAL(startPlayingSongId(qint32)), MPDConnection::self(), SLOT(startPlayingSongId(qint32))); connect(this, SIGNAL(getRating(QString)), MPDConnection::self(), SLOT(getRating(QString))); connect(this, SIGNAL(setRating(QStringList,quint8)), MPDConnection::self(), SLOT(setRating(QStringList,quint8))); connect(MPDConnection::self(), SIGNAL(rating(QString,quint8)), SLOT(ratingResult(QString,quint8))); connect(MPDConnection::self(), SIGNAL(stickerDbChanged()), SLOT(stickerDbChanged())); #ifdef ENABLE_DEVICES_SUPPORT //TODO: Problems here with devices support!!! connect(DevicesModel::self(), SIGNAL(updatedDetails(QList)), SLOT(updateDetails(QList))); #endif removeDuplicatesAction=new Action(tr("Remove Duplicates"), this); removeDuplicatesAction->setEnabled(false); QColor col=Utils::monoIconColor(); undoAction=ActionCollection::get()->createAction("playqueue-undo", tr("Undo"), MonoIcon::icon(FontAwesome::undo, col)); undoAction->setShortcut(Qt::ControlModifier+Qt::Key_Z); redoAction=ActionCollection::get()->createAction("playqueue-redo", tr("Redo"), MonoIcon::icon(FontAwesome::repeat, col)); redoAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_Z); connect(undoAction, SIGNAL(triggered()), this, SLOT(undo())); connect(redoAction, SIGNAL(triggered()), this, SLOT(redo())); connect(removeDuplicatesAction, SIGNAL(triggered()), this, SLOT(removeDuplicates())); shuffleAction=new Action(tr("Shuffle"), this); shuffleAction->setMenu(new QMenu(0)); Action *shuffleTracksAction = new Action(tr("Tracks"), shuffleAction); Action *shuffleAlbumsAction = new Action(tr("Albums"), shuffleAction); connect(shuffleTracksAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(shuffle())); connect(shuffleAlbumsAction, SIGNAL(triggered()), this, SLOT(shuffleAlbums())); shuffleAction->menu()->addAction(shuffleTracksAction); shuffleAction->menu()->addAction(shuffleAlbumsAction); sortAction=new Action(tr("Sort By"), this); sortAction->setMenu(new QMenu(0)); addSortAction(tr("Artist"), constSortByArtistKey); addSortAction(tr("Album Artist"), constSortByAlbumArtistKey); addSortAction(tr("Album"), constSortByAlbumKey); addSortAction(tr("Track Title"), constSortByTitleKey); addSortAction(tr("Track Number"), constSortByNumberKey); addSortAction(tr("Genre"), constSortByGenreKey); addSortAction(tr("Year"), constSortByYearKey); addSortAction(tr("Composer"), constSortByComposerKey); addSortAction(tr("Performer"), constSortByPerformerKey); controlActions(); shuffleAction->setEnabled(false); sortAction->setEnabled(false); alignments[COL_TITLE]=alignments[COL_ARTIST]=alignments[COL_ALBUM]=alignments[COL_GENRE]=alignments[COL_COMPOSER]=alignments[COL_PERFORMER]=int(Qt::AlignVCenter|Qt::AlignLeft); alignments[COL_TRACK]=alignments[COL_LENGTH]=alignments[COL_DISC]=alignments[COL_YEAR]=alignments[COL_ORIGYEAR]=alignments[COL_PRIO]=int(Qt::AlignVCenter|Qt::AlignRight); alignments[COL_RATING]=int(Qt::AlignVCenter|Qt::AlignHCenter); } PlayQueueModel::~PlayQueueModel() { } QModelIndex PlayQueueModel::index(int row, int column, const QModelIndex &parent) const { return hasIndex(row, column, parent) ? createIndex(row, column, (void *)&songs.at(row)) : QModelIndex(); } QModelIndex PlayQueueModel::parent(const QModelIndex &idx) const { Q_UNUSED(idx) return QModelIndex(); } QVariant PlayQueueModel::headerData(int section, Qt::Orientation orientation, int role) const { if (Qt::Horizontal==orientation) { switch (role) { case Qt::DisplayRole: return headerText(section); case Qt::TextAlignmentRole: return alignments[section]; case Cantata::Role_InitiallyHidden: return COL_YEAR==section || COL_ORIGYEAR==section || COL_DISC==section || COL_GENRE==section || COL_PRIO==section || COL_COMPOSER==section || COL_PERFORMER==section || COL_RATING==section; case Cantata::Role_Hideable: return COL_TITLE!=section && COL_ARTIST!=section; case Cantata::Role_Width: switch (section) { case COL_TRACK: return 0.075; case COL_DISC: return 0.03; case COL_TITLE: return 0.3; case COL_ARTIST: return 0.27; case COL_ALBUM: return 0.27; case COL_LENGTH: return 0.05; case COL_YEAR: return 0.05; case COL_ORIGYEAR: return 0.05; case COL_GENRE: return 0.1; case COL_PRIO: return 0.015; case COL_COMPOSER: return 0.2; case COL_PERFORMER: return 0.2; case COL_RATING: return 0.08; } case Cantata::Role_ContextMenuText: return COL_TRACK==section ? tr("# (Track Number)") : headerText(section); default: break; } } return QVariant(); } bool PlayQueueModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (Qt::Horizontal==orientation && Qt::TextAlignmentRole==role && section>=0) { int al=value.toInt()|Qt::AlignVCenter; if (al!=alignments[section]) { alignments[section]=al; return true; } } return false; } int PlayQueueModel::rowCount(const QModelIndex &idx) const { return idx.isValid() ? 0 : songs.size(); } static QString basicPath(const Song &song) { #ifdef ENABLE_HTTP_SERVER if (song.isCantataStream()) { Song mod=HttpServer::self()->decodeUrl(song.file); if (!mod.file.isEmpty()) { return mod.file; } } #endif QString path=song.filePath(); int marker=path.indexOf(QLatin1Char('#')); return -1==marker ? path : path.left(marker); } QVariant PlayQueueModel::data(const QModelIndex &index, int role) const { if (!index.isValid() && Cantata::Role_RatingCol==role) { return COL_RATING; } if (!index.isValid() || index.row() >= songs.size()) { return QVariant(); } switch (role) { case Cantata::Role_MainText: { const Song &s=songs.at(index.row()); return s.title.isEmpty() ? s.file : s.trackAndTitleStr(false); } case Cantata::Role_SubText: { const Song &s=songs.at(index.row()); return s.artist+QString(" – ")+s.displayAlbum(); } case Cantata::Role_Time: { const Song &s=songs.at(index.row()); return s.time>0 ? Utils::formatTime(s.time) : QLatin1String(""); } case Cantata::Role_IsCollection: return false; case Cantata::Role_CollectionId: return 0; case Cantata::Role_Key: return songs.at(index.row()).key; case Cantata::Role_Id: return songs.at(index.row()).id; case Cantata::Role_SongWithRating: case Cantata::Role_Song: { QVariant var; const Song &s=songs.at(index.row()); if (Cantata::Role_SongWithRating==role && Song::Standard==s.type && Song::Rating_Null==s.rating) { emit getRating(s.file); s.rating=Song::Rating_Requested; } var.setValue(s); return var; } case Cantata::Role_AlbumDuration: { const Song &first = songs.at(index.row()); quint32 d=first.time; for (int i=index.row()+1; i1) { for (int i=index.row()-1; i<=0; ++i) { const Song &song = songs.at(i); if (song.key!=first.key) { break; } d+=song.time; } } return d; } case Cantata::Role_SongCount: { const Song &first = songs.at(index.row()); quint32 count=1; for (int i=index.row()+1; i1) { for (int i=index.row()-1; i<=0; ++i) { const Song &song = songs.at(i); if (song.key!=first.key) { break; } count++; } } return count; } case Cantata::Role_CurrentStatus: { quint16 key=songs.at(index.row()).key; for (int i=index.row()+1; iinfoTooltips()) { return QVariant(); } Song s=songs.at(index.row()); if (s.album.isEmpty() && s.isStream()) { return basicPath(s); } else { return s.toolTip(); } } case Qt::TextAlignmentRole: return alignments[index.column()]; case Cantata::Role_Decoration: { qint32 id=songs.at(index.row()).id; if (id==currentSongId) { switch (mpdState) { case MPDState_Inactive: case MPDState_Stopped: return MonoIcon::icon(FontAwesome::stop, Utils::monoIconColor()); case MPDState_Playing: return MonoIcon::icon(stopAfterCurrent ? FontAwesome::playcircle : FontAwesome::play, Utils::monoIconColor()); case MPDState_Paused: return MonoIcon::icon(FontAwesome::pause, Utils::monoIconColor()); } } else if (-1!=id && id==stopAfterTrackId) { return MonoIcon::icon(FontAwesome::stop, Utils::monoIconColor()); } break; } default: break; } return QVariant(); } bool PlayQueueModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (Cantata::Role_DropAdjust==role) { dropAdjust=value.toUInt(); return true; } else { return QAbstractItemModel::setData(index, value, role); } } Qt::DropActions PlayQueueModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::ItemFlags PlayQueueModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } return Qt::ItemIsDropEnabled; } /** * @return A QStringList with the mimetypes we support */ QStringList PlayQueueModel::mimeTypes() const { return QStringList() << constMoveMimeType << constFileNameMimeType << constUriMimeType; } /** * Convert the data at indexes into mimedata ready for transport * * @param indexes The indexes to pack into mimedata * @return The mimedata */ QMimeData *PlayQueueModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QList positions; QStringList filenames; foreach(QModelIndex index, indexes) { if (index.isValid() && 0==index.column()) { positions.append(index.row()); filenames.append(static_cast(index.internalPointer())->file); } } encode(*mimeData, constMoveMimeType, positions); encode(*mimeData, constFileNameMimeType, filenames); return mimeData; } /** * Act on mime data that is dropped in our model * * @param data The actual data that is dropped * @param action The action. This could mean drop/copy etc * @param row The row where it is dropper * @param column The column where it is dropper * @param parent The parent of where we have dropped it * * @return bool if we accest the drop */ bool PlayQueueModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex & /*parent*/) { if (Qt::IgnoreAction==action) { return true; } row+=dropAdjust; if (data->hasFormat(constMoveMimeType)) { //Act on internal moves if (row < 0) { emit move(decodeInts(*data, constMoveMimeType), songs.size(), songs.size()); } else { emit move(decodeInts(*data, constMoveMimeType), row, songs.size()); } return true; } else if (data->hasFormat(constFileNameMimeType)) { //Act on moves from the music library and dir view addItems(decode(*data, constFileNameMimeType), row, false, 0, false); return true; } else if(data->hasFormat(constUriMimeType)/* && MPDConnection::self()->getDetails().isLocal()*/) { QStringList useable=parseUrls(decode(*data, constUriMimeType), true); if (useable.count()) { addItems(useable, row, false, 0, false); return true; } } return false; } void PlayQueueModel::load(const QStringList &urls) { QStringList useable=parseUrls(urls, false); if (useable.count()) { addItems(useable, songs.count(), songs.isEmpty(), 0, false); } } void PlayQueueModel::addItems(const QStringList &items, int row, int action, quint8 priority, bool decreasePriority) { bool haveRadioStream=false; foreach (const QString &f, items) { QUrl u(f); if (u.scheme().startsWith(StreamsModel::constPrefix)) { haveRadioStream=true; break; } } if (haveRadioStream) { emit fetchingStreams(); fetcher->get(items, row, action, priority, decreasePriority); } else { addFiles(items, row, action, priority, decreasePriority); } } void PlayQueueModel::addFiles(const QStringList &filenames, int row, int action, quint8 priority, bool decreasePriority) { if (MPDConnection::ReplaceAndplay==action) { emit filesAdded(filenames, 0, 0, MPDConnection::ReplaceAndplay, priority, decreasePriority); } else if (songs.isEmpty()) { emit filesAdded(filenames, 0, 0, MPDConnection::Append, priority, decreasePriority); } else if (row < 0) { emit filesAdded(filenames, songs.size(), songs.size(), action, priority, decreasePriority); } else { emit filesAdded(filenames, row, songs.size(), action, priority, decreasePriority); } } void PlayQueueModel::prioritySet(const QMap &prio) { QList prev; if (undoEnabled) { foreach (Song s, songs) { prev.append(s); } } QMap copy = prio; int row=0; foreach (const Song &s, songs) { QMap::ConstIterator it = copy.find(s.id); if (copy.end()!=it) { s.priority=it.value(); copy.remove(s.id); QModelIndex idx(index(row, 0)); emit dataChanged(idx, idx); if (copy.isEmpty()) { break; } } ++row; } saveHistory(prev); } qint32 PlayQueueModel::getIdByRow(qint32 row) const { return row>=songs.size() ? -1 : songs.at(row).id; } qint32 PlayQueueModel::getSongId(const QString &file) const { foreach (const Song &s, songs) { if (s.file==file) { return s.id; } } return -1; } // qint32 PlayQueueModel::getPosByRow(qint32 row) const // { // return row>=songs.size() ? -1 : songs.at(row).pos; // } qint32 PlayQueueModel::getRowById(qint32 id) const { for (int i = 0; i < songs.size(); i++) { if (songs.at(i).id == id) { return i; } } return -1; } Song PlayQueueModel::getSongByRow(const qint32 row) const { return row<0 || row>=songs.size() ? Song() : songs.at(row); } Song PlayQueueModel::getSongById(qint32 id) const { foreach (const Song &s, songs) { if (s.id==id) { return s; } } return Song(); } void PlayQueueModel::updateCurrentSong(quint32 id) { qint32 oldIndex = currentSongId; currentSongId = id; if (-1!=oldIndex) { int row=-1==currentSongRowNum ? getRowById(oldIndex) : currentSongRowNum; emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex())-1)); } currentSongRowNum=getRowById(currentSongId); if (currentSongRowNum>=0 && currentSongRowNum<=songs.count()) { const Song &song=songs.at(currentSongRowNum); if (Song::Rating_Null==song.rating) { song.rating=Song::Rating_Requested; emit getRating(song.file); } else if (Song::Rating_Requested!=song.rating) { emit currentSongRating(song.file, song.rating); } } emit dataChanged(index(currentSongRowNum, 0), index(currentSongRowNum, columnCount(QModelIndex())-1)); if (-1!=currentSongId && stopAfterTrackId==currentSongId) { stopAfterTrackId=-1; emit stop(true); } } void PlayQueueModel::clear() { beginResetModel(); songs.clear(); ids.clear(); currentSongId=-1; currentSongRowNum=0; stopAfterTrackId=-1; time=0; endResetModel(); } qint32 PlayQueueModel::currentSongRow() const { if (-1==currentSongRowNum) { currentSongRowNum=getRowById(currentSongId); } return currentSongRowNum; } void PlayQueueModel::setState(MPDState st) { if (st!=mpdState) { mpdState=st; if (-1!=currentSongId) { if (-1==currentSongRowNum) { currentSongRowNum=getRowById(currentSongId); } emit dataChanged(index(currentSongRowNum, 0), index(currentSongRowNum, 2)); } } } // Update playqueue with contents returned from MPD. void PlayQueueModel::update(const QList &songList, bool isComplete) { currentSongRowNum=-1; if (songList.isEmpty()) { Song::clearKeyStore(MPDParseUtils::Loc_PlayQueue); } removeDuplicatesAction->setEnabled(songList.count()>1); QList prev; if (undoEnabled) { prev=songs; } QSet newIds; foreach (const Song &s, songList) { newIds.insert(s.id); } // If we have too many changes UI can hang, so it is sometimes better just to do a complete reset! if (isComplete && songs.count()>MPDConnection::constMaxPqChanges) { songs.clear(); } if (songs.isEmpty() || songList.isEmpty()) { beginResetModel(); songs=songList; ids=newIds; endResetModel(); if (songList.isEmpty()) { stopAfterTrackId=-1; } } else { time = 0; QSet removed=ids-newIds; foreach (qint32 id, removed) { qint32 row=getRowById(id); if (row!=-1) { beginRemoveRows(QModelIndex(), row, row); songs.removeAt(row); endRemoveRows(); } } for (qint32 i=0; i=songs.count(); Song currentSongAtPos=newSong ? Song() : songs.at(i); bool isEmpty=s.isEmpty(); if (newSong || s.id!=currentSongAtPos.id) { qint32 existingPos=newSong ? -1 : getRowById(s.id); if (-1==existingPos) { beginInsertRows(QModelIndex(), i, i); songs.insert(i, s); endInsertRows(); } else { beginMoveRows(QModelIndex(), existingPos, existingPos, QModelIndex(), i>existingPos ? i+1 : i); Song old=songs.takeAt(existingPos); // old.pos=s.pos; s.rating=old.rating; songs.insert(i, isEmpty ? old : s); endMoveRows(); } } else if (isEmpty) { s=currentSongAtPos; } else { s.key=currentSongAtPos.key; s.rating=currentSongAtPos.rating; songs.replace(i, s); if (s.title!=currentSongAtPos.title || s.artist!=currentSongAtPos.artist || s.name()!=currentSongAtPos.name()) { emit dataChanged(index(i, 0), index(i, columnCount(QModelIndex())-1)); } } if (s.id==currentSongId) { currentSongRowNum=i; } time += s.time; } if (songs.count()>songList.count()) { int toBeRemoved=songs.count()-songList.count(); beginRemoveRows(QModelIndex(), songList.count(), songs.count()-1); for (int i=0; isetEnabled(songs.count()>1); sortAction->setEnabled(songs.count()>1); } void PlayQueueModel::setStopAfterTrack(qint32 track) { bool clear=track==stopAfterTrackId || (track==currentSongId && stopAfterCurrent); stopAfterTrackId=clear ? -1 : track; qint32 row=getRowById(track); if (-1!=row) { emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex())-1)); } if (clear) { emit clearStopAfter(); } else if (stopAfterTrackId==currentSongId) { emit stop(true); } } bool PlayQueueModel::removeCantataStreams(bool cdOnly) { QList ids; foreach (const Song &s, songs) { if (s.isCdda() || (!cdOnly && s.isCantataStream())) { ids.append(s.id); } } if (!ids.isEmpty()) { emit removeSongs(ids); return true; } return false; } void PlayQueueModel::removeAll() { emit clearEntries(); } void PlayQueueModel::remove(const QList &rowsToRemove) { QList removeIds; foreach (const int &r, rowsToRemove) { if (r>-1 && r &rowsToKeep) { QSet allIds; foreach(const Song &song, songs) { allIds.insert(song.id); } QSet keepIds; foreach (const int &r, rowsToKeep) { if (r>-1 && r removeIds=allIds-keepIds; if (!removeIds.isEmpty()) { emit removeSongs(removeIds.toList()); } } void PlayQueueModel::setRating(const QList &rows, quint8 rating) const { QSet files; foreach (const int &r, rows) { if (r>-1 && r0; if (!e) { undoStack.clear(); redoStack.clear(); } controlActions(); } static PlayQueueModel::UndoItem getState(const QList &songs) { PlayQueueModel::UndoItem item; foreach (const Song &s, songs) { item.files.append(s.file); item.priority.append(s.priority); } return item; } static bool equalSongList(const QList &a, const QList &b) { if (a.count()!=b.count()) { return false; } for (int i=0; i &prevList) { if (!undoEnabled) { return; } if (equalSongList(prevList, songs)) { lastCommand=Cmd_Other; return; } switch (lastCommand) { case Cmd_Redo: { if (redoStack.isEmpty()) { lastCommand=Cmd_Other; } else { UndoItem actioned=redoStack.pop(); if (actioned!=getState(songs)) { lastCommand=Cmd_Other; } else { undoStack.push(getState(prevList)); } } break; } case Cmd_Undo: { if (undoStack.isEmpty()) { lastCommand=Cmd_Other; } else { UndoItem actioned=undoStack.pop(); if (actioned!=getState(songs)) { lastCommand=Cmd_Other; } else { redoStack.push(getState(prevList)); } } break; } case Cmd_Other: break; } if (Cmd_Other==lastCommand) { redoStack.clear(); undoStack.push(getState(prevList)); if (undoStack.size()>undoLimit) { undoStack.pop_back(); } } controlActions(); lastCommand=Cmd_Other; } void PlayQueueModel::controlActions() { undoAction->setEnabled(!undoStack.isEmpty()); undoAction->setVisible(undoLimit>0); redoAction->setEnabled(!redoStack.isEmpty()); redoAction->setVisible(undoLimit>0); } void PlayQueueModel::addSortAction(const QString &name, const QString &key) { Action *action=new Action(name, sortAction); action->setProperty(constSortByKey, key); sortAction->menu()->addAction(action); connect(action, SIGNAL(triggered()), SLOT(sortBy())); } static bool composerSort(const Song *s1, const Song *s2) { const QString v1=s1->hasComposer() ? s1->composer() : QString(); const QString v2=s2->hasComposer() ? s2->composer() : QString(); int c=v1.localeAwareCompare(v2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool performerSort(const Song *s1, const Song *s2) { const QString v1=s1->hasPerformer() ? s1->performer() : QString(); const QString v2=s2->hasPerformer() ? s2->performer() : QString(); int c=v1.localeAwareCompare(v2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool artistSort(const Song *s1, const Song *s2) { const QString v1=s1->hasArtistSort() ? s1->artistSort() : s1->artist; const QString v2=s2->hasArtistSort() ? s2->artistSort() : s2->artist; int c=v1.localeAwareCompare(v2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool albumArtistSort(const Song *s1, const Song *s2) { const QString v1=s1->hasAlbumArtistSort() ? s1->albumArtistSort() : s1->artistOrComposer(); const QString v2=s2->hasAlbumArtistSort() ? s2->albumArtistSort() : s2->artistOrComposer(); int c=v1.localeAwareCompare(v2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool albumSort(const Song *s1, const Song *s2) { const QString v1=s1->hasAlbumSort() ? s1->albumSort() : s1->album; const QString v2=s2->hasAlbumSort() ? s2->albumSort() : s2->album; int c=v1.localeAwareCompare(v2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool genreSort(const Song *s1, const Song *s2) { int c=s1->compareGenres(*s2); return c<0 || (c==0 && (*s1)<(*s2)); } static bool yearSort(const Song *s1, const Song *s2) { return s1->yearyear || (s1->year==s2->year && (*s1)<(*s2)); } static bool titleSort(const Song *s1, const Song *s2) { int c=s1->title.localeAwareCompare(s2->title); return c<0 || (c==0 && (*s1)<(*s2)); } static bool trackSort(const Song *s1, const Song *s2) { return s1->tracktrack || (s1->track==s2->track && (*s1)<(*s2)); } void PlayQueueModel::sortBy() { Action *act=qobject_cast(sender()); if (act) { QString key=act->property(constSortByKey).toString(); QList copy; foreach (const Song &s, songs) { ((Song &)s).populateSorts(); copy.append(&s); } if (constSortByArtistKey==key) { qSort(copy.begin(), copy.end(), artistSort); } else if (constSortByAlbumArtistKey==key) { qSort(copy.begin(), copy.end(), albumArtistSort); } else if (constSortByAlbumKey==key) { qSort(copy.begin(), copy.end(), albumSort); } else if (constSortByGenreKey==key) { qSort(copy.begin(), copy.end(), genreSort); } else if (constSortByYearKey==key) { qSort(copy.begin(), copy.end(), yearSort); } else if (constSortByComposerKey==key) { qSort(copy.begin(), copy.end(), composerSort); } else if (constSortByPerformerKey==key) { qSort(copy.begin(), copy.end(), performerSort); } else if (constSortByTitleKey==key) { qSort(copy.begin(), copy.end(), titleSort); } else if (constSortByNumberKey==key) { qSort(copy.begin(), copy.end(), trackSort); } QList positions; foreach (const Song *s, copy) { positions.append(getRowById(s->id)); } emit setOrder(positions); } } void PlayQueueModel::removeDuplicates() { QMap > map; foreach (const Song &song, songs) { map[song.albumKey()+"-"+song.artistSong()+"-"+song.track].append(song); } QList toRemove; foreach (const QString &key, map.keys()) { QList values=map.value(key); if (values.size()>1) { Song::sortViaType(values); for (int i=1; i::iterator it=songs.begin(); QList::iterator end=songs.end(); int numCols=columnCount(QModelIndex())-1; for (int row=0; it!=end; ++it, ++row) { if (Song::Standard==(*it).type && r!=(*it).rating && (*it).file==file) { (*it).rating=r; emit dataChanged(index(row, 0), index(row, numCols)); if ((*it).id==currentSongId) { emit currentSongRating(file, r); } } } } void PlayQueueModel::stickerDbChanged() { // Sticker DB changed, need to re-request ratings... QSet requests; foreach (const Song &song, songs) { if (Song::Standard==song.type && song.rating<=Song::Rating_Max && !requests.contains(song.file)) { emit getRating(song.file); requests.insert(song.file); } } } void PlayQueueModel::undo() { if (!undoEnabled || undoStack.isEmpty()) { return; } UndoItem item=undoStack.top(); emit populate(item.files, item.priority); lastCommand=Cmd_Undo; } void PlayQueueModel::redo() { if (!undoEnabled || redoStack.isEmpty()) { return; } UndoItem item=redoStack.top(); emit populate(item.files, item.priority); lastCommand=Cmd_Redo; } void PlayQueueModel::playSong(const QString &file) { qint32 id=getSongId(file); if (-1==id) { emit addAndPlay(file); } else { emit startPlayingSongId(id); } } void PlayQueueModel::stats() { time = 0; //Loop over all songs foreach(const Song &song, songs) { time += song.time; } emit statsUpdated(songs.size(), time); } void PlayQueueModel::cancelStreamFetch() { fetcher->cancel(); } static bool songSort(const Song *s1, const Song *s2) { return s1->discdisc || (s1->disc==s2->disc && (s1->tracktrack || (s1->track==s2->track && (*s1)<(*s2)))); } void PlayQueueModel::shuffleAlbums() { QMap > albums; foreach (const Song &s, songs) { albums[s.key].append(&s); } QList keys=albums.keys(); if (keys.count()<2) { return; } QList positions; while (!keys.isEmpty()) { quint32 key=keys.takeAt(Utils::random(keys.count())); QList albumSongs=albums[key]; qSort(albumSongs.begin(), albumSongs.end(), songSort); foreach (const Song *song, albumSongs) { positions.append(getRowById(song->id)); } } emit setOrder(positions); } void PlayQueueModel::stopAfterCurrentChanged(bool afterCurrent) { if (afterCurrent!=stopAfterCurrent) { stopAfterCurrent=afterCurrent; emit dataChanged(index(currentSongRowNum, 0), index(currentSongRowNum, columnCount(QModelIndex())-1)); } } void PlayQueueModel::remove(const QList &rem) { QSet s; QList ids; foreach (const Song &song, rem) { s.insert(song.file); } foreach (const Song &song, songs) { if (s.contains(song.file)) { ids.append(song.id); s.remove(song.file); if (s.isEmpty()) { break; } } } if (!ids.isEmpty()) { emit removeSongs(ids); } } void PlayQueueModel::updateDetails(const QList &updated) { QMap songMap; QList updatedRows; bool currentUpdated=false; Song currentSong; foreach (const Song &song, updated) { songMap[song.file]=song; } for (int i=0; i * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef PLAYQUEUEMODEL_H #define PLAYQUEUEMODEL_H #include "mpd-interface/song.h" #include "mpd-interface/mpdstatus.h" #include "config.h" #include #include #include #include #include #include class StreamFetcher; class Action; class PlayQueueModel : public QAbstractItemModel { Q_OBJECT public: enum Columns { COL_TRACK, COL_DISC, COL_TITLE, COL_ARTIST, COL_ALBUM, COL_LENGTH, COL_YEAR, COL_ORIGYEAR, COL_GENRE, COL_PRIO, COL_COMPOSER, COL_PERFORMER, COL_RATING, COL_COUNT }; struct UndoItem { bool operator==(const UndoItem &o) const { return priority==o.priority && files==o.files; } bool operator!=(const UndoItem &o) const { return !(*this==o); } QStringList files; QList priority; }; static const QLatin1String constMoveMimeType; static const QLatin1String constFileNameMimeType; static const QLatin1String constUriMimeType; static void encode(QMimeData &mimeData, const QString &mime, const QStringList &values); static void encode(QMimeData &mimeData, const QString &mime, const QList &values); static QStringList decode(const QMimeData &mimeData, const QString &mime); static QList decodeInts(const QMimeData &mimeData, const QString &mime); static QString headerText(int col); static PlayQueueModel * self(); PlayQueueModel(QObject *parent = 0); ~PlayQueueModel(); QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const; QModelIndex parent(const QModelIndex &idx) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &) const { return COL_COUNT; } QVariant data(const QModelIndex &, int) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); void updateCurrentSong(quint32 id); qint32 getIdByRow(qint32 row) const; qint32 getSongId(const QString &file) const; // qint32 getPosByRow(qint32 row) const; qint32 getRowById(qint32 id) const; Song getSongByRow(const qint32 row) const; Song getSongById(qint32 id) const; Qt::DropActions supportedDropActions() const; Qt::ItemFlags flags(const QModelIndex &index) const; QStringList mimeTypes() const; QMimeData *mimeData(const QModelIndexList &indexes) const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); QStringList filenames(); void clear(); qint32 currentSong() const { return currentSongId; } qint32 currentSongRow() const; void setState(MPDState st); void update(const QList &songList, bool isComplete); void setStopAfterTrack(qint32 track); void clearStopAfterTrack() { setStopAfterTrack(-1); } bool removeCantataStreams(bool cdOnly=false); void removeAll(); void remove(const QList &rowsToRemove); void crop(const QList &rowsToKeep); void setRating(const QList &rows, quint8 rating) const; Action * shuffleAct() { return shuffleAction; } Action * removeDuplicatesAct() { return removeDuplicatesAction; } Action * sortAct() { return sortAction; } Action * undoAct() { return undoAction; } Action * redoAct() { return redoAction; } void enableUndo(bool e); bool lastCommandWasUnodOrRedo() const { return Cmd_Other!=lastCommand; } qint32 totalTime() const { return time; } void remove(const QList &rem); private: void saveHistory(const QList &prevList); void controlActions(); void addSortAction(const QString &name, const QString &key); public Q_SLOTS: void load(const QStringList &urls); void addItems(const QStringList &items, int row, int action, quint8 priority, bool decreasePriority); void addItems(const QStringList &items, int action, quint8 priority, bool decreasePriority) { addItems(items, -1, action, priority, decreasePriority); } void addFiles(const QStringList &filenames, int row, int action, quint8 priority, bool decreasePriority=false); void prioritySet(const QMap &prio); void stats(); void cancelStreamFetch(); void shuffleAlbums(); void playSong(const QString &file); private Q_SLOTS: void sortBy(); void stopAfterCurrentChanged(bool afterCurrent); void updateDetails(const QList &updated); void undo(); void redo(); void removeDuplicates(); void ratingResult(const QString &file, quint8 r); void stickerDbChanged(); Q_SIGNALS: void stop(bool afterCurrent); void clearStopAfter(); void filesAdded(const QStringList filenames, const quint32 row, const quint32 size, int action, quint8 priority, bool decreasePriority); void populate(const QStringList &items, const QList &priority); void move(const QList &items, const quint32 row, const quint32 size); void setOrder(const QList &items); void getRating(const QString &file) const; void setRating(const QStringList &files, quint8 rating) const; void statsUpdated(int songs, quint32 time); void fetchingStreams(); void streamsFetched(); void removeSongs(const QList &items); void updateCurrent(const Song &s); void streamFetchStatus(const QString &msg); void clearEntries(); void addAndPlay(const QString &file); void startPlayingSongId(qint32 id); void currentSongRating(const QString &file, quint8 r); private: QList songs; QSet ids; qint32 currentSongId; mutable qint32 currentSongRowNum; quint32 time; StreamFetcher *fetcher; MPDState mpdState; bool stopAfterCurrent; qint32 stopAfterTrackId; enum Command { Cmd_Other, Cmd_Undo, Cmd_Redo }; int undoLimit; bool undoEnabled; Command lastCommand; QStack undoStack; QStack redoStack; quint32 dropAdjust; Action *removeDuplicatesAction; Action *undoAction; Action *redoAction; Action *shuffleAction; Action *sortAction; QMap alignments; }; #endif cantata-2.2.0/models/playqueueproxymodel.cpp000066400000000000000000000041411316350454000212530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include #include "playqueueproxymodel.h" #include "playqueuemodel.h" #include "mpd-interface/song.h" PlayQueueProxyModel::PlayQueueProxyModel(QObject *parent) : ProxyModel(parent) { setFilterCaseSensitivity(Qt::CaseInsensitive); } bool PlayQueueProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { return true; } if (-1!=sourceParent.row()) { return false; } const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); return index.isValid() && matchesFilter(*static_cast(index.internalPointer())); } QMimeData *PlayQueueProxyModel::mimeData(const QModelIndexList &indexes) const { QModelIndexList sourceIndexes; foreach(QModelIndex index, indexes) { sourceIndexes.append(mapToSource(index)); } return sourceModel()->mimeData(sourceIndexes); } bool PlayQueueProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { const QModelIndex sourceIndex = mapToSource(index(row, column, parent)); return sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), sourceIndex.parent()); } cantata-2.2.0/models/playqueueproxymodel.h000066400000000000000000000025611316350454000207240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef PLAYQUEUEPROXYMODEL_H #define PLAYQUEUEPROXYMODEL_H #include "proxymodel.h" #include "config.h" class PlayQueueProxyModel : public ProxyModel { Q_OBJECT public: PlayQueueProxyModel(QObject *parent = 0); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; QMimeData *mimeData(const QModelIndexList &indexes) const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); }; #endif cantata-2.2.0/models/proxymodel.cpp000066400000000000000000000134071316350454000173250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "proxymodel.h" #include "gui/settings.h" #include #include #include #include #include bool ProxyModel::matchesFilter(const Song &s) const { QStringList strings; strings << s.albumArtist(); if (!s.albumartist.isEmpty() && s.albumartist!=s.artist) { strings << s.artist; } if (!s.composer().isEmpty() && s.composer()!=s.artist && s.composer()!=s.albumartist) { strings << s.composer(); } strings << s.title << s.album; return matchesFilter(strings); } bool ProxyModel::matchesFilter(const QStringList &strings) const { if (filterStrings.isEmpty()) { return true; } uint ums = unmatchedStrings; int numStrings = filterStrings.count(); foreach (const QString &str, strings) { QString candidate = str.simplified(); QString basic; bool useBasic=false; for (int i = 0; i < numStrings; ++i) { const QString &f=filterStrings.at(i); // Try to match string as entered by user... if (candidate.contains(f, Qt::CaseInsensitive)) { ums &= ~(1< bool ProxyModel::update(const QString &txt) { QString text=txt.length()<2 ? QString() : txt; // qWarning() << "UPDATE" << txt << (void *)f; if (text==origFilterText) { // qWarning() <<"NO CHANGE"; return false; } bool wasEmpty=isEmpty(); filterStrings = text.split(' ', QString::SkipEmptyParts, Qt::CaseInsensitive); unmatchedStrings = 0; const int n = qMin(filterStrings.count(), (int)(sizeof(uint)*8)); for ( int i = 0; i < n; ++i ) { unmatchedStrings |= (1< ProxyModel::mapToSourceRows(const QModelIndexList &list) const { QList rows; foreach (const QModelIndex &idx, list) { rows.append(mapToSource(idx).row()); } return rows; } QModelIndexList ProxyModel::mapToSource(const QModelIndexList &list, bool leavesOnly) const { QModelIndexList mapped; if (leavesOnly) { QModelIndexList l=leaves(list); foreach (const QModelIndex &idx, l) { mapped.append(mapToSource(idx)); } } else { foreach (const QModelIndex &idx, list) { mapped.append(mapToSource(idx)); } } return mapped; } QMimeData * ProxyModel::mimeData(const QModelIndexList &indexes) const { QModelIndexList nodes=leaves(indexes); QModelIndexList sourceIndexes; foreach (const QModelIndex &idx, nodes) { sourceIndexes << mapToSource(idx); } return sourceModel()->mimeData(sourceIndexes); } QModelIndexList ProxyModel::leaves(const QModelIndexList &list) const { QModelIndexList l; foreach (const QModelIndex &idx, list) { l+=leaves(idx); } return l; } QModelIndexList ProxyModel::leaves(const QModelIndex &idx) const { QModelIndexList list; int rc=rowCount(idx); if (rc>0) { for (int i=0; i * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PROXYMODEL_H #define PROXYMODEL_H #include #include #include "mpd-interface/song.h" #include "config.h" class QMimeData; class ProxyModel : public QSortFilterProxyModel { public: ProxyModel(QObject *parent) : QSortFilterProxyModel(parent), isSorted(false), filterEnabled(false), filter(0) { } virtual ~ProxyModel() { } bool update(const QString &text); const void * filterItem() const { return filter; } void setFilterItem(void *f) { filter=f; } void setRootIndex(const QModelIndex &idx) { rootIndex=idx.isValid() ? mapToSource(idx) : idx; } bool isChildOfRoot(const QModelIndex &idx) const; bool isEmpty() const { return filterStrings.isEmpty() && 0==filter; } bool enabled() const { return filterEnabled; } const QString & filterText() const { return origFilterText; } void resort(); void sort() { isSorted=false; sort(0); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); QList mapToSourceRows(const QModelIndexList &list) const; QModelIndex mapToSource(const QModelIndex &idx) const { return QSortFilterProxyModel::mapToSource(idx); } QModelIndexList mapToSource(const QModelIndexList &list, bool leavesOnly=true) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QModelIndexList leaves(const QModelIndexList &list) const; protected: bool matchesFilter(const Song &s) const; bool matchesFilter(const QStringList &strings) const; private: QModelIndexList leaves(const QModelIndex &idx) const; protected: bool isSorted; bool filterEnabled; QModelIndex rootIndex; QString origFilterText; QStringList filterStrings; uint unmatchedStrings; const void *filter; }; #endif cantata-2.2.0/models/roles.h000066400000000000000000000036401316350454000157120ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ROLES_H #define ROLES_H #include "config.h" #include namespace Cantata { enum Roles { // ItemView... Role_MainText = Qt::UserRole+100, Role_BriefMainText, Role_SubText, Role_TitleText, Role_TitleActions, Role_Image, Role_ListImage, // Should image been shown in list/tree view? Role_CoverSong, Role_GridCoverSong, Role_Capacity, Role_CapacityText, Role_Actions, Role_LoadCoverInUIThread, // GroupedView... Role_Key, Role_Id, Role_Song, Role_SongWithRating, Role_AlbumDuration, Role_Status, Role_CurrentStatus, Role_SongCount, Role_IsCollection, Role_CollectionId, Role_DropAdjust, // PlayQueueView ... Role_Decoration, // TableView... Role_Width, Role_InitiallyHidden, Role_Hideable, Role_ContextMenuText, Role_RatingCol, // PlayQueueModel... Role_Time }; } #endif cantata-2.2.0/models/searchmodel.cpp000066400000000000000000000246621316350454000174160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "searchmodel.h" #include "widgets/icons.h" #include "roles.h" #include "playqueuemodel.h" #include "gui/settings.h" #include #include #include #include #include QString SearchModel::headerText(int col) { switch (col) { case COL_TRACK: return PlayQueueModel::headerText(PlayQueueModel::COL_TRACK); case COL_DISC: return PlayQueueModel::headerText(PlayQueueModel::COL_DISC); case COL_TITLE: return PlayQueueModel::headerText(PlayQueueModel::COL_TITLE); case COL_ARTIST: return PlayQueueModel::headerText(PlayQueueModel::COL_ARTIST); case COL_ALBUM: return PlayQueueModel::headerText(PlayQueueModel::COL_ALBUM); case COL_LENGTH: return PlayQueueModel::headerText(PlayQueueModel::COL_LENGTH); case COL_YEAR: return PlayQueueModel::headerText(PlayQueueModel::COL_YEAR); case COL_ORIGYEAR: return PlayQueueModel::headerText(PlayQueueModel::COL_ORIGYEAR); case COL_GENRE: return PlayQueueModel::headerText(PlayQueueModel::COL_GENRE); case COL_COMPOSER: return PlayQueueModel::headerText(PlayQueueModel::COL_COMPOSER); case COL_PERFORMER: return PlayQueueModel::headerText(PlayQueueModel::COL_PERFORMER); case COL_RATING: return PlayQueueModel::headerText(PlayQueueModel::COL_RATING); default: return QString(); } } SearchModel::SearchModel(QObject *parent) : ActionModel(parent) , multiCol(false) { alignments[COL_TITLE]=alignments[COL_ARTIST]=alignments[COL_ALBUM]=alignments[COL_GENRE]=alignments[COL_COMPOSER]=alignments[COL_PERFORMER]=int(Qt::AlignVCenter|Qt::AlignLeft); alignments[COL_TRACK]=alignments[COL_LENGTH]=alignments[COL_DISC]=alignments[COL_YEAR]=alignments[COL_ORIGYEAR]=int(Qt::AlignVCenter|Qt::AlignRight); alignments[COL_RATING]=int(Qt::AlignVCenter|Qt::AlignHCenter); } SearchModel::~SearchModel() { clear(); } QModelIndex SearchModel::index(int row, int col, const QModelIndex &parent) const { if (!hasIndex(row, col, parent)) { return QModelIndex(); } return row=0) { int al=value.toInt()|Qt::AlignVCenter; if (al!=alignments[section]) { alignments[section]=al; return true; } } return false; } int SearchModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : songList.count(); } QVariant SearchModel::data(const QModelIndex &index, int role) const { if (!index.isValid() && Cantata::Role_RatingCol==role) { return COL_RATING; } const Song *song = toSong(index); if (!song) { return QVariant(); } switch (role) { case Qt::DecorationRole: if (multiCol) { return QVariant(); } return Song::Playlist==song->type ? Icons::self()->playlistListIcon : song->isStream() ? Icons::self()->streamIcon : Icons::self()->audioListIcon; case Qt::TextAlignmentRole: return alignments[index.column()]; case Qt::DisplayRole: if (multiCol) { switch (index.column()) { case COL_TITLE: return song->title.isEmpty() ? Utils::getFile(song->file) : song->title; case COL_ARTIST: return song->artist.isEmpty() ? Song::unknown() : song->artist; case COL_ALBUM: if (song->isStream() && song->album.isEmpty()) { QString n=song->name(); if (!n.isEmpty()) { return n; } } return song->album; case COL_TRACK: if (song->track <= 0) { return QVariant(); } return song->track; case COL_LENGTH: return Utils::formatTime(song->time); case COL_DISC: if (song->disc <= 0) { return QVariant(); } return song->disc; case COL_YEAR: if (song->year <= 0) { return QVariant(); } return song->year; case COL_ORIGYEAR: if (song->origYear <= 0) { return QVariant(); } return song->origYear; case COL_GENRE: return song->displayGenre(); case COL_COMPOSER: return song->composer(); case COL_PERFORMER: return song->performer(); default: break; } break; } return song->entryName(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } return song->toolTip(); case Cantata::Role_MainText: return song->title.isEmpty() ? song->file : song->trackAndTitleStr(); case Cantata::Role_SubText: { QString resp=song->artist; QString al=song->displayAlbum(); if (!al.isEmpty()) { if (!resp.isEmpty()) { resp+=QString(" – "); } resp+=al; } if (song->time>0) { if (!resp.isEmpty()) { resp+=QString(" – "); } resp+=Utils::formatTime(song->time); } return resp; } case Cantata::Role_ListImage: return true; case Cantata::Role_CoverSong: { QVariant v; v.setValue(*song); return v; } case Cantata::Role_Song: { QVariant var; var.setValue(*song); return var; } default: return ActionModel::data(index, role); } return QVariant(); } Qt::ItemFlags SearchModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } else { return Qt::NoItemFlags; } } QStringList SearchModel::filenames(const QModelIndexList &indexes, bool allowPlaylists) const { QList list=songs(indexes, allowPlaylists); QStringList fnames; foreach (const Song &s, list) { fnames.append(s.file); } return fnames; } QList SearchModel::songs(const QModelIndexList &indexes, bool allowPlaylists) const { QList list; QSet files; foreach(QModelIndex index, indexes) { if (!index.isValid() || 0!=index.column()) { continue; } Song *song=static_cast(index.internalPointer()); if ((allowPlaylists || Song::Playlist!=song->type) && !files.contains(song->file)) { Song s=*song; fixPath(s); list << s; files << s.file; } } return list; } QMimeData * SearchModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames(indexes, true)); return mimeData; } QStringList SearchModel::mimeTypes() const { QStringList types; types << PlayQueueModel::constFileNameMimeType; return types; } void SearchModel::refresh() { QString k=currentKey; QString v=currentValue; clear(); search(k, v); } void SearchModel::clear() { if (!songList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, songList.count()-1); songList.clear(); endRemoveRows(); } currentKey=currentValue=QString(); emit statsUpdated(0, 0); } void SearchModel::results(const QList &songs) { beginResetModel(); songList.clear(); songList=songs; endResetModel(); quint32 time=0; foreach (const Song &s, songList) { time+=s.time; } emit statsUpdated(songList.size(), time); emit searched(); } cantata-2.2.0/models/searchmodel.h000066400000000000000000000056401316350454000170560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SEARCH_MODEL_H #define SEARCH_MODEL_H #include "mpd-interface/song.h" #include "actionmodel.h" #include #include class SearchModel : public ActionModel { Q_OBJECT public: enum Columns { COL_TRACK, COL_DISC, COL_TITLE, COL_ARTIST, COL_ALBUM, COL_LENGTH, COL_YEAR, COL_ORIGYEAR, COL_GENRE, COL_COMPOSER, COL_PERFORMER, COL_RATING, COL_COUNT }; static QString headerText(int col); SearchModel(QObject *parent = 0); ~SearchModel(); QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const; QModelIndex parent(const QModelIndex &) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &) const { return COL_COUNT; } QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; QStringList filenames(const QModelIndexList &indexes, bool allowPlaylists=false) const; QList songs(const QModelIndexList &indexes, bool allowPlaylists=false) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QStringList mimeTypes() const; void refresh(); virtual void clear(); virtual void search(const QString &key, const QString &value)=0; void setMultiColumn(bool m) { multiCol=m; } Q_SIGNALS: void searching(); void searched(); void statsUpdated(int songs, quint32 time); protected: virtual Song & fixPath(Song &s) const { return s; } void results(const QList &songs); const Song * toSong(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : 0; } protected: bool multiCol; QList songList; QString currentKey; QString currentValue; QMap alignments; }; #endif cantata-2.2.0/models/searchproxymodel.cpp000066400000000000000000000025051316350454000205100ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "searchproxymodel.h" SearchProxyModel::SearchProxyModel(QObject *parent) : ProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); sort(0); } bool SearchProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { return *static_cast(left.internalPointer()) < *static_cast(right.internalPointer()); } cantata-2.2.0/models/searchproxymodel.h000066400000000000000000000021321316350454000201510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SEARCHPROXYMODEL_H #define SEARCHPROXYMODEL_H #include "proxymodel.h" class SearchProxyModel : public ProxyModel { public: SearchProxyModel(QObject *parent = 0); bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; #endif cantata-2.2.0/models/sqllibrarymodel.cpp000066400000000000000000000437471316350454000203420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sqllibrarymodel.h" #include "playqueuemodel.h" #include "gui/settings.h" #include "widgets/icons.h" #include "support/configuration.h" #include "roles.h" #include static QString parentData(const SqlLibraryModel::Item *i) { QString data; const SqlLibraryModel::Item *itm=i; while (itm->getParent()) { if (!itm->getParent()->getText().isEmpty()) { if (SqlLibraryModel::T_Root==itm->getParent()->getType()) { data=""+itm->getParent()->getText()+"
    "+data; } else { data=itm->getParent()->getText()+"
    "+data; } } itm=itm->getParent(); } return data; } SqlLibraryModel::Type SqlLibraryModel::toGrouping(const QString &str) { for (int i=T_Genre; iclear(); } void SqlLibraryModel::settings(Type top, LibraryDb::AlbumSort lib, LibraryDb::AlbumSort al) { bool changed=top!=tl || (T_Album!=top && lib!=librarySort) || (T_Album==top && al!=albumSort); tl=top; librarySort=lib; albumSort=al; if (changed) { libraryUpdated(); } } void SqlLibraryModel::setTopLevel(Type t) { if (t!=tl) { tl=t; libraryUpdated(); } } void SqlLibraryModel::setLibraryAlbumSort(LibraryDb::AlbumSort s) { if (s!=librarySort) { librarySort=s; if (T_Album!=tl) { libraryUpdated(); } } } void SqlLibraryModel::setAlbumAlbumSort(LibraryDb::AlbumSort s) { if (s!=albumSort) { albumSort=s; if (T_Album==tl) { libraryUpdated(); } } } static QLatin1String constGroupingKey("grouping"); static QLatin1String constAlbumSortKey("albumSort"); static QLatin1String constLibrarySortKey("librarySort"); void SqlLibraryModel::load(Configuration &config) { tl=toGrouping(config.get(constGroupingKey, groupingStr(tl))); albumSort=LibraryDb::toAlbumSort(config.get(constAlbumSortKey, LibraryDb::albumSortStr(albumSort))); librarySort=LibraryDb::toAlbumSort(config.get(constLibrarySortKey, LibraryDb::albumSortStr(librarySort))); } void SqlLibraryModel::save(Configuration &config) { config.set(constGroupingKey, groupingStr(tl)); config.set(constAlbumSortKey, LibraryDb::albumSortStr(albumSort)); config.set(constLibrarySortKey, LibraryDb::albumSortStr(librarySort)); } void SqlLibraryModel::libraryUpdated() { beginResetModel(); delete root; root=new CollectionItem(T_Root, QString()); switch (tl) { case T_Genre: { QList genres=db->getGenres(); if (!genres.isEmpty()) { foreach (const LibraryDb::Genre &genre, genres) { root->add(new CollectionItem(T_Genre, genre.name, genre.name, tr("%n Artist(s)", "", genre.artistCount), root)); } } break; } case T_Artist: { QList artists=db->getArtists(); if (!artists.isEmpty()) { foreach (const LibraryDb::Artist &artist, artists) { root->add(new CollectionItem(T_Artist, artist.name, artist.name, tr("%n Album(s)", "", artist.albumCount), root)); } } break; } case T_Album: { QList albums=db->getAlbums(QString(), QString(), albumSort); if (!albums.isEmpty()) { foreach (const LibraryDb::Album &album, albums) { root->add(new AlbumItem(T_Album==tl && album.identifyById ? QString() : album.artist, album.id, Song::displayAlbum(album.name, album.year), T_Album==tl ? album.artist : tr("%n Tracks (%1)", "", album.trackCount).arg(Utils::formatTime(album.duration, true)), root)); } } break; } default: break; } endResetModel(); } void SqlLibraryModel::search(const QString &str, const QString &genre) { if (db->setFilter(str, genre)) { libraryUpdated(); } } Qt::ItemFlags SqlLibraryModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex SqlLibraryModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const CollectionItem * p = parent.isValid() ? static_cast(parent.internalPointer()) : root; const Item * c = rowgetChildCount() ? p->getChildren().at(row) : 0; return c ? createIndex(row, column, (void *)c) : QModelIndex(); } QModelIndex SqlLibraryModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } const Item * const item = static_cast(child.internalPointer()); Item * const parentItem = item->getParent(); if (parentItem == root || 0==parentItem) { return QModelIndex(); } return createIndex(parentItem->getRow(), 0, parentItem); } int SqlLibraryModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } const CollectionItem *parentItem=parent.isValid() ? static_cast(parent.internalPointer()) : root; return parentItem ? parentItem->getChildCount() : 0; } int SqlLibraryModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } bool SqlLibraryModel::hasChildren(const QModelIndex &index) const { Item *item=toItem(index); return item && T_Track!=item->getType(); } bool SqlLibraryModel::canFetchMore(const QModelIndex &index) const { if (index.isValid()) { Item *item = toItem(index); return item && T_Track!=item->getType() && 0==item->getChildCount(); } else { return false; } } void SqlLibraryModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } CollectionItem *item = static_cast(toItem(index)); switch (item->getType()) { case T_Root: break; case T_Genre: { QList artists=db->getArtists(item->getId()); if (!artists.isEmpty()) { beginInsertRows(index, 0, artists.count()-1); foreach (const LibraryDb::Artist &artist, artists) { item->add(new CollectionItem(T_Artist, artist.name, artist.name, tr("%n Album(s)", "", artist.albumCount), item)); } endInsertRows(); } break; } case T_Artist: { QList albums=db->getAlbums(item->getId(), T_Genre==tl ? item->getParent()->getId() : QString(), librarySort); if (!albums.isEmpty()) { beginInsertRows(index, 0, albums.count()-1); foreach (const LibraryDb::Album &album, albums) { item->add(new CollectionItem(T_Album, album.id, Song::displayAlbum(album.name, album.year), tr("%n Tracks (%1)", "", album.trackCount).arg(Utils::formatTime(album.duration, true)), item)); } endInsertRows(); } break; } case T_Album: { QList songs=T_Album==tl ? db->getTracks(static_cast(item)->getArtistId(), item->getId(), QString(), albumSort) : db->getTracks(item->getParent()->getId(), item->getId(), T_Genre==tl ? item->getParent()->getParent()->getId() : QString(), librarySort); if (!songs.isEmpty()) { beginInsertRows(index, 0, songs.count()-1); foreach (const Song &song, songs) { item->add(new TrackItem(song, item)); } endInsertRows(); } break; } default: break; } } QVariant SqlLibraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } Item *item = static_cast(index.internalPointer()); switch (role) { case Qt::DecorationRole: switch (item->getType()) { case T_Genre: return Icons::self()->genreIcon; case T_Artist: return Icons::self()->artistIcon; case T_Album: return Icons::self()->albumIcon(32); case T_Track: return Song::Playlist==item->getSong().type ? Icons::self()->playlistListIcon : Icons::self()->audioListIcon; default: return QVariant(); } case Cantata::Role_LoadCoverInUIThread: return T_Album==item->getType() && T_Album!=tl; case Cantata::Role_BriefMainText: if (T_Album==item->getType()) { return item->getText(); } case Cantata::Role_MainText: case Qt::DisplayRole: if (T_Track==item->getType()) { TrackItem *track = static_cast(item); if (Song::Playlist==track->getSong().type) { return track->getSong().isCueFile() ? tr("Cue Sheet") : tr("Playlist"); } } return item->getText(); case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } if (T_Track==item->getType()) { return static_cast(item)->getSong().toolTip(); } return parentData(item)+ (0==item->getChildCount() ? item->getText() : (item->getText()+"
    "+data(index, Cantata::Role_SubText).toString())); case Cantata::Role_SubText: return item->getSubText(); case Cantata::Role_ListImage: return T_Album==item->getType(); case Cantata::Role_TitleText: return item->getText(); case Cantata::Role_TitleActions: switch (item->getType()) { case T_Artist: case T_Album: return true; default: return false; } default: break; } return ActionModel::data(index, role); } QMimeData * SqlLibraryModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QStringList files=filenames(indexes, true); PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files); return mimeData; } static bool containsParent(const QSet &set, const QModelIndex &idx) { QModelIndex p=idx.parent(); while (p.isValid()) { if (set.contains(p)) { return true; } p=p.parent(); } return false; } QList SqlLibraryModel::songs(const QModelIndexList &list, bool allowPlaylists) const { QList songList; QSet set=list.toSet(); populate(list); foreach (const QModelIndex &idx, list) { if (!containsParent(set, idx)) { songList+=songs(idx, allowPlaylists); } } return songList; } QStringList SqlLibraryModel::filenames(const QModelIndexList &list, bool allowPlaylists) const { QStringList files; QList songList=songs(list, allowPlaylists); foreach (const Song &s, songList) { files.append(s.file); } return files; } QModelIndex SqlLibraryModel::findSongIndex(const Song &song) { if (root) { QModelIndex albumIndex=findAlbumIndex(song.artistOrComposer(), song.albumId()); if (albumIndex.isValid()) { if (canFetchMore(albumIndex)) { fetchMore(albumIndex); } CollectionItem *al=static_cast(albumIndex.internalPointer()); foreach (Item *t, al->getChildren()) { if (static_cast(t)->getSong().title==song.title) { return index(t->getRow(), 0, albumIndex); } } } } // Hmm... Find song details in db via file path - fixes SingleTracks songs if (!song.file.isEmpty()) { QList dbSongs=db->songs(QStringList() << song.file); if (!dbSongs.isEmpty() && dbSongs.first().albumId()!=song.albumId()) { Song dbSong=dbSongs.first(); dbSong.file=QString(); // Prevent recursion! return findSongIndex(dbSong); } } return QModelIndex(); } QModelIndex SqlLibraryModel::findAlbumIndex(const QString &artist, const QString &album) { if (root) { if (T_Album==tl) { foreach (Item *a, root->getChildren()) { if (a->getId()==album && static_cast(a)->getArtistId()==artist) { return index(a->getRow(), 0, QModelIndex()); } } } else { QModelIndex artistIndex=findArtistIndex(artist); if (artistIndex.isValid()) { if (canFetchMore(artistIndex)) { fetchMore(artistIndex); } CollectionItem *ar=static_cast(artistIndex.internalPointer()); foreach (Item *al, ar->getChildren()) { if (al->getId()==album) { return index(al->getRow(), 0, artistIndex); } } } } } return QModelIndex(); } QModelIndex SqlLibraryModel::findArtistIndex(const QString &artist) { if (root) { if (T_Genre==tl) { foreach (Item *g, root->getChildren()) { QModelIndex gIndex=index(g->getRow(), 0, QModelIndex()); if (canFetchMore(gIndex)) { fetchMore(gIndex); } foreach (Item *a, static_cast(g)->getChildren()) { if (a->getId()==artist) { return index(a->getRow(), 0, gIndex); } } } } if (T_Artist==tl) { foreach (Item *a, root->getChildren()) { if (a->getId()==artist) { return index(a->getRow(), 0, QModelIndex()); } } } } return QModelIndex(); } QSet SqlLibraryModel::getGenres() const { return db->get("genre"); } QSet SqlLibraryModel::getArtists() const { return db->get("albumArtist"); } QList SqlLibraryModel::getAlbumTracks(const QString &artistId, const QString &albumId) const { return db->getTracks(artistId, albumId, QString(), LibraryDb::AS_ArAlYr, false); } QList SqlLibraryModel::songs(const QStringList &files, bool allowPlaylists) const { return db->songs(files, allowPlaylists); } QList SqlLibraryModel::getArtistAlbums(const QString &artist) const { return db->getAlbumsWithArtist(artist); } void SqlLibraryModel::getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres) { db->getDetails(artists, albumArtists, composers, albums, genres); } bool SqlLibraryModel::songExists(const Song &song) { return db->songExists(song); } void SqlLibraryModel::populate(const QModelIndexList &list) const { foreach (const QModelIndex &idx, list) { if (canFetchMore(idx)) { const_cast(this)->fetchMore(idx); } if (T_Track!=static_cast(idx.internalPointer())->getType()) { populate(children(idx)); } } } QModelIndexList SqlLibraryModel::children(const QModelIndex &parent) const { QModelIndexList list; for(int r=0; r SqlLibraryModel::songs(const QModelIndex &idx, bool allowPlaylists) const { QList list; if (hasChildren(idx)) { foreach (const QModelIndex &c, children(idx)) { list+=songs(c, allowPlaylists); } } else { const Item *i=static_cast(idx.internalPointer()); if (i && T_Track==i->getType() && (allowPlaylists || (Song::Playlist!=i->getSong().type && !i->getSong().isFromCue()))) { Song s(i->getSong()); list.append(fixPath(s)); } } return list; } void SqlLibraryModel::CollectionItem::add(Item *i) { children.append(i); i->setRow(children.count()-1); childMap.insert(i->getUniqueId(), i); } const SqlLibraryModel::Item *SqlLibraryModel::CollectionItem::getChild(const QString &id) const { QMap::ConstIterator it=childMap.find(id); return childMap.constEnd()==it ? 0 : it.value(); } cantata-2.2.0/models/sqllibrarymodel.h000066400000000000000000000155361316350454000200020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SQL_LIBRARY_MODEL_H #define SQL_LIBRARY_MODEL_H #include "actionmodel.h" #include "mpd-interface/song.h" #include "support/utils.h" #include "db/librarydb.h" #include class Configuration; class SqlLibraryModel : public ActionModel { Q_OBJECT public: enum Type { T_Root, T_Genre, T_Artist, T_Album, T_Track }; static Type toGrouping(const QString &str); static QString groupingStr(Type m); class CollectionItem; class Item { public: Item(Type t, CollectionItem *p=0) : type(t), parent(p) { } virtual ~Item() { } Type getType() const { return type; } virtual const QString & getId() const =0; virtual const QString getUniqueId() const { return getId(); } virtual QString getText() const =0; virtual QString getSubText() const =0; CollectionItem * getParent() const { return parent; } int getRow() const { return row; } void setRow(int r) { row=r; } virtual int getChildCount() const { return 0;} const Song & getSong() const { return song; } void setSong(const Song &s) { song=s; } private: Type type; CollectionItem *parent; int row; Song song; }; class TrackItem : public Item { public: TrackItem(const Song &s, CollectionItem *p=0) : Item(T_Track, p) { setSong(s); } virtual ~TrackItem() { } virtual const QString & getId() const { return getSong().file; } virtual QString getText() const { return getSong().trackAndTitleStr(); } virtual QString getSubText() const { return Utils::formatTime(getSong().time, true); } }; class CollectionItem : public Item { public: CollectionItem(Type t, const QString &i, const QString &txt=QString(), const QString &sub=QString(), CollectionItem *p=0) : Item(t, p), id(i), text(txt), subText(sub) { } virtual ~CollectionItem() { qDeleteAll(children); } const QList getChildren() const { return children; } virtual int getChildCount() const { return children.count();} void add(Item *i); const Item * getChild(const QString &id) const; virtual const QString & getId() const { return id; } virtual QString getText() const { return text; } virtual QString getSubText() const { return subText; } private: QString id; QString text; QString subText; QList children; QMap childMap; }; class AlbumItem : public CollectionItem { public: AlbumItem(const QString &ar, const QString &i, const QString &txt=QString(), const QString &sub=QString(), CollectionItem *p=0) : CollectionItem(T_Album, i, txt, sub, p), artistId(ar) { } virtual ~AlbumItem() { } const QString & getArtistId() const { return artistId; } const QString getUniqueId() const { return artistId+getId(); } private: QString artistId; }; SqlLibraryModel(LibraryDb *d, QObject *p, Type top=T_Artist); void reload() { libraryUpdated(); } void clear(); void settings(Type top, LibraryDb::AlbumSort lib, LibraryDb::AlbumSort al); Type topLevel() const { return tl; } LibraryDb::AlbumSort libraryAlbumSort() const { return librarySort; } LibraryDb::AlbumSort albumAlbumSort() const { return albumSort; } void setTopLevel(Type t); void setLibraryAlbumSort(LibraryDb::AlbumSort s); void setAlbumAlbumSort(LibraryDb::AlbumSort s); virtual void load(Configuration &config); virtual void save(Configuration &config); void search(const QString &str, const QString &genre=QString()); Qt::ItemFlags flags(const QModelIndex &index) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; bool hasChildren(const QModelIndex &index) const; bool canFetchMore(const QModelIndex &index) const; void fetchMore(const QModelIndex &index); QVariant data(const QModelIndex &index, int role) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QList songs(const QModelIndexList &list, bool allowPlaylists) const; QStringList filenames(const QModelIndexList &list, bool allowPlaylists) const; QModelIndex findSongIndex(const Song &song); QModelIndex findAlbumIndex(const QString &artist, const QString &album); QModelIndex findArtistIndex(const QString &artist); QSet getGenres() const; QSet getArtists() const; QList getAlbumTracks(const QString &artistId, const QString &albumId) const; QList getAlbumTracks(const Song &song) const { return getAlbumTracks(song.artistOrComposer(), song.albumId()); } QList songs(const QStringList &files, bool allowPlaylists=false) const; QList getArtistAlbums(const QString &artist) const; void getDetails(QSet &artists, QSet &albumArtists, QSet &composers, QSet &albums, QSet &genres); bool songExists(const Song &song); LibraryDb::Album getRandomAlbum(const QStringList &genres, const QStringList &artists) const { return db->getRandomAlbum(genres, artists); } Q_SIGNALS: void error(const QString &str); public Q_SLOTS: void clearDb(); protected Q_SLOTS: void libraryUpdated(); private: void populate(const QModelIndexList &list) const; QModelIndexList children(const QModelIndex &parent) const; QList songs(const QModelIndex &idx, bool allowPlaylists) const; Item * toItem(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : root; } virtual Song & fixPath(Song &s) const { return s; } protected: Type tl; CollectionItem *root; LibraryDb *db; LibraryDb::AlbumSort librarySort; LibraryDb::AlbumSort albumSort; }; #endif cantata-2.2.0/models/streamsearchmodel.cpp000066400000000000000000000320131316350454000206170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamsearchmodel.h" #include "widgets/icons.h" #include "roles.h" #include "playqueuemodel.h" #include "network/networkaccessmanager.h" #include "gui/stdactions.h" #include "gui/settings.h" #include #include #include #include #include #include #include static QIcon getIcon(const QString &name) { QIcon icon; icon.addFile(":"+name); return icon.isNull() ? Icons::self()->streamCategoryIcon : icon; } StreamSearchModel::StreamSearchModel(QObject *parent) : ActionModel(parent) , root(new StreamsModel::CategoryItem(QString(), "root")) { // ORDER *MUST* MATCH Category ENUM!!!!! root->children.append(new StreamsModel::CategoryItem("http://opml.radiotime.com/Search.ashx", tr("TuneIn"), root, getIcon("tunein"))); root->children.append(new StreamsModel::CategoryItem(QLatin1String("http://")+StreamsModel::constShoutCastHost+QLatin1String("/legacy/genrelist"), tr("ShoutCast"), root, getIcon("shoutcast"))); root->children.append(new StreamsModel::CategoryItem(QLatin1String("http://")+StreamsModel::constDirbleHost+QLatin1String("/v2/search/"), tr("Dirble"), root, getIcon("dirble"))); } StreamSearchModel::~StreamSearchModel() { clear(); delete root; } QModelIndex StreamSearchModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const StreamsModel::CategoryItem * p = parent.isValid() ? static_cast(parent.internalPointer()) : root; const StreamsModel::Item * c = rowchildren.count() ? p->children.at(row) : 0; return c ? createIndex(row, column, (void *)c) : QModelIndex(); } QModelIndex StreamSearchModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } StreamsModel::Item * parent = toItem(index)->parent; if (!parent || parent == root || !parent->parent) { return QModelIndex(); } return createIndex(static_cast(parent->parent)->children.indexOf(parent), 0, parent); } QVariant StreamSearchModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const { return QVariant(); } int StreamSearchModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { StreamsModel::Item *item = toItem(parent); return item->isCategory() ? static_cast(item)->children.count() : 0; } return root->children.count(); } int StreamSearchModel::columnCount(const QModelIndex &) const { return 1; } QVariant StreamSearchModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return tr("Stream Search"); case Cantata::Role_SubText: return tr("Search for radio streams"); case Qt::DecorationRole: return Icon("edit-find"); } return QVariant(); } const StreamsModel::Item *item = toItem(index); switch (role) { case Qt::DecorationRole: if (item->parent==root && item->isCategory()) { return static_cast(item) ->icon; } return item->isCategory() ? Icons::self()->streamCategoryIcon : Icons::self()->streamListIcon; case Qt::DisplayRole: return item->name; case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } return item->isCategory() ? item->name : (item->name+QLatin1String("
    ")+item->url+QLatin1String("")); case Cantata::Role_SubText: if (item->isCategory()) { const StreamsModel::CategoryItem *cat=static_cast(item); switch (cat->state) { case StreamsModel::CategoryItem::Initial: return root==item->parent ? tr("Enter string to search") : tr("Not Loaded"); case StreamsModel::CategoryItem::Fetching: return tr("Loading..."); default: return tr("%n Entry(s)", "", cat->children.count()); } } else { return item->subText.isEmpty() ? QLatin1String("-") : item->subText; } break; case Cantata::Role_Actions: if (item->isCategory()){ if (static_cast(item)->canBookmark) { QVariant v; v.setValue >(QList() << StreamsModel::self()->addBookmarkAct()); return v; } } else { QVariant v; v.setValue >(QList() << StdActions::self()->replacePlayQueueAction << StreamsModel::self()->addToFavouritesAct()); return v; } break; default: break; } return QVariant(); } Qt::ItemFlags StreamSearchModel::flags(const QModelIndex &index) const { if (index.isValid()) { if (toItem(index)->isCategory()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } } else { return Qt::NoItemFlags; } } bool StreamSearchModel::hasChildren(const QModelIndex &index) const { return index.isValid() ? toItem(index)->isCategory() : true; } bool StreamSearchModel::canFetchMore(const QModelIndex &index) const { if (index.isValid()) { StreamsModel::Item *item = toItem(index); return item->isCategory() && StreamsModel::CategoryItem::Initial==static_cast(item)->state && !item->url.isEmpty(); } else { return false; } } void StreamSearchModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } StreamsModel::Item *item = toItem(index); if (item->isCategory() && !item->url.isEmpty()) { StreamsModel::CategoryItem *cat=static_cast(item); NetworkJob *job=NetworkAccessManager::self()->get(cat->url); if (jobs.isEmpty()) { emit loading(); } jobs.insert(job, cat); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); cat->state=StreamsModel::CategoryItem::Fetching; emit dataChanged(index, index); } } QStringList StreamSearchModel::filenames(const QModelIndexList &indexes, bool addPrefix) const { QStringList fnames; foreach(QModelIndex index, indexes) { StreamsModel::Item *item=static_cast(index.internalPointer()); if (!item->isCategory() && !fnames.contains(item->url)) { fnames << StreamsModel::modifyUrl(item->url, addPrefix, item->name); } } return fnames; } QMimeData * StreamSearchModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames(indexes, true)); return mimeData; } QStringList StreamSearchModel::mimeTypes() const { QStringList types; types << PlayQueueModel::constFileNameMimeType; return types; } void StreamSearchModel::clear() { cancelAll(); foreach (StreamsModel::Item *item, root->children) { StreamsModel::CategoryItem *cat=static_cast(item); if (cat->children.count()) { QModelIndex index = createIndex(root->children.indexOf(cat), 0, (void *)cat); beginRemoveRows(index, 0, cat->children.count() - 1); qDeleteAll(cat->children); cat->children.clear(); endRemoveRows(); } } currentSearch=QString(); } static int getParam(const QString &key, QString &query) { int index=query.indexOf(" "+key+"="); int val=0; if (-1!=index) { int endPos=query.indexOf(" ", index+3); int end=endPos; if (-1==end) { end=query.length()-1; } int start=index+key.length()+2; val=query.mid(start, (end+1)-start).toInt(); if (endPos>start) { query=query.left(index)+query.mid(endPos); query=query.trimmed(); } else { query=query.left(index); } } return val; } void StreamSearchModel::search(const QString &searchTerm, bool stationsOnly) { if (searchTerm==currentSearch) { return; } clear(); currentSearch=searchTerm; foreach (StreamsModel::Item *item, root->children) { QUrl searchUrl; QUrlQuery query; switch (root->children.indexOf(item)) { case TuneIn: { searchUrl=QUrl(item->url); if (stationsOnly) { query.addQueryItem("types", "station"); } query.addQueryItem("query", searchTerm); QString locale=QLocale::system().name(); if (!locale.isEmpty()) { query.addQueryItem("locale", locale); } break; } case ShoutCast: { searchUrl=QUrl(item->url); QString search=searchTerm; int limit=getParam("limit", search); int bitrate=getParam("br", search); if (0==bitrate) { bitrate=getParam("bitrate", search); } query.addQueryItem("k", StreamsModel::constShoutCastApiKey); query.addQueryItem("search", search); query.addQueryItem("limit", QString::number(limit<1 ? 100 : limit)); if (bitrate>=32 && bitrate<=512) { query.addQueryItem("br", QString::number(bitrate)); } break; } case Dirble: searchUrl=QUrl(item->url+searchTerm); query.addQueryItem("token", StreamsModel::constDirbleApiKey); break; } searchUrl.setQuery(query); NetworkJob *job=NetworkAccessManager::self()->get(searchUrl); if (jobs.isEmpty()) { emit loading(); } jobs.insert(job, static_cast(item)); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); } } void StreamSearchModel::cancelAll() { if (!jobs.isEmpty()) { QList jobList=jobs.keys(); foreach (NetworkJob *j, jobList) { j->cancelAndDelete(); } jobs.clear(); emit loaded(); } } void StreamSearchModel::jobFinished() { NetworkJob *job=dynamic_cast(sender()); if (!job) { return; } job->deleteLater(); if (jobs.contains(job)) { StreamsModel::CategoryItem *cat=jobs[job]; cat->state=StreamsModel::CategoryItem::Fetched; jobs.remove(job); QModelIndex index=cat==root ? QModelIndex() : createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); if (job->ok()) { QList newItems; StreamsModel::Item *i=cat; while (i->parent && i->parent!=root) { i=i->parent; } switch(root->children.indexOf(i)) { case TuneIn: newItems=StreamsModel::parseRadioTimeResponse(job->actualJob(), cat, true); break; case ShoutCast: newItems=StreamsModel::parseShoutCastSearchResponse(job->actualJob(), cat); break; case Dirble: newItems=StreamsModel::parseDirbleStations(job->actualJob(), cat); break; default : break; } if (!newItems.isEmpty()) { beginInsertRows(index, cat->children.count(), (cat->children.count()+newItems.count())-1); cat->children+=newItems; endInsertRows(); } } emit dataChanged(index, index); if (jobs.isEmpty()) { emit loaded(); } } } QList StreamSearchModel::getStreams(StreamsModel::CategoryItem *cat) { QList streams; if (cat) { foreach (StreamsModel::Item *i, cat->children) { if (i->isCategory()) { streams+=getStreams(static_cast(i)); } else { streams.append(i); } } } return streams; } cantata-2.2.0/models/streamsearchmodel.h000066400000000000000000000056531316350454000202760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAM_SEARCH_MODEL_H #define STREAM_SEARCH_MODEL_H #include "streamsmodel.h" #include #include #include #include class NetworkJob; class QXmlStreamReader; class QIODevice; class StreamSearchModel : public ActionModel { Q_OBJECT public: enum Category { // DO NOT CHANGE ORDER! TuneIn, ShoutCast, Dirble, NumCategories }; StreamSearchModel(QObject *parent = 0); ~StreamSearchModel(); QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const; QModelIndex parent(const QModelIndex &) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &) const; QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; bool hasChildren(const QModelIndex &index) const; bool canFetchMore(const QModelIndex &index) const; void fetchMore(const QModelIndex &index); QStringList filenames(const QModelIndexList &indexes, bool addPrefix) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QStringList mimeTypes() const; void clear(); void search(const QString &searchTerm, bool stationsOnly); void cancelAll(); Q_SIGNALS: void loading(); void loaded(); void error(const QString &msg); private Q_SLOTS: void jobFinished(); private: QList getStreams(StreamsModel::CategoryItem *cat); StreamsModel::Item * toItem(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : root; } QList parseRadioTimeResponse(QIODevice *dev, StreamsModel::CategoryItem *cat); StreamsModel::Item * parseRadioTimeEntry(QXmlStreamReader &doc, StreamsModel::CategoryItem *parent); private: Category category; QMap jobs; StreamsModel::CategoryItem *root; QString currentSearch; }; #endif cantata-2.2.0/models/streamsmodel.cpp000066400000000000000000001752311316350454000176260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamsmodel.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdparseutils.h" #include "widgets/icons.h" #include "network/networkaccessmanager.h" #include "support/utils.h" #include "gui/settings.h" #include "playqueuemodel.h" #include "roles.h" #include "support/action.h" #include "gui/stdactions.h" #include "support/actioncollection.h" #include "digitallyimported.h" #include "qtiocompressor/qtiocompressor.h" #include "support/utils.h" #include "config.h" #include "support/globalstatic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined Q_OS_WIN #include #endif #include #include #include GLOBAL_STATIC(StreamsModel, instance) const QString StreamsModel::constSubDir=QLatin1String("streams"); const QString StreamsModel::constCacheExt=QLatin1String(".xml.gz"); const QString StreamsModel::constShoutCastApiKey=QLatin1String("fa1669MuiRPorUBw"); const QString StreamsModel::constShoutCastHost=QLatin1String("api.shoutcast.com"); const QString StreamsModel::constDirbleApiKey=QLatin1String("1035d2834bdc7195b8929ad7f70a8410f02c633e"); const QString StreamsModel::constDirbleHost=QLatin1String("api.dirble.com"); const QString StreamsModel::constCompressedXmlFile=QLatin1String("streams.xml.gz"); const QString StreamsModel::constXmlFile=QLatin1String("streams.xml"); const QString StreamsModel::constSettingsFile=QLatin1String("settings.json"); const QString StreamsModel::constPngIcon=QLatin1String("icon.png"); const QString StreamsModel::constSvgIcon=QLatin1String("icon.svg"); static const char * constOrigUrlProperty = "orig-url"; static QString constRadioTimeHost=QLatin1String("opml.radiotime.com"); static QString constRadioTimeUrl=QLatin1String("http://")+constRadioTimeHost+QLatin1String("/Browse.ashx"); static QString constFavouritesUrl=QLatin1String("cantata://internal"); static QString constIceCastUrl=QLatin1String("http://dir.xiph.org/yp.xml"); static const QString constDiChannelListHost=QLatin1String("api.v2.audioaddict.com"); static const QString constDiChannelListUrl=QLatin1String("http://")+constDiChannelListHost+("/v1/%1/mobile/batch_update?asset_group_key=mobile_icons&stream_set_key="); static const QString constDiStdUrl=QLatin1String("http://%1/public3/%2.pls"); static QString constShoutCastUrl=QLatin1String("http://")+StreamsModel::constShoutCastHost+QLatin1String("/genre/primary?f=xml&k=")+StreamsModel::constShoutCastApiKey; static QString constDirbleUrl=QLatin1String("http://")+StreamsModel::constDirbleHost+QLatin1String("/v2/categories/primary?token=")+StreamsModel::constDirbleApiKey; static const QLatin1String constBookmarksDir=QLatin1String("bookmarks"); static QIcon getIcon(const QString &name) { QIcon icon; icon.addFile(":"+name); return icon.isNull() ? Icons::self()->streamCategoryIcon : icon; } static Icon getExternalIcon(const QString &path, QStringList files=QStringList() << StreamsModel::constSvgIcon << StreamsModel::constPngIcon) { foreach (const QString &file, files) { QString iconFile=path+Utils::constDirSep+file; if (QFile::exists(iconFile)) { Icon icon; icon.addFile(iconFile); return icon; } } return Icon(); } static QString categoryCacheName(const QString &name, bool createDir=false) { return Utils::cacheDir(StreamsModel::constSubDir, createDir)+name+StreamsModel::constCacheExt; } static QString categoryBookmarksName(const QString &name, bool createDir=false) { return Utils::dataDir(constBookmarksDir, createDir)+name+StreamsModel::constCacheExt; } QString StreamsModel::Item::modifiedName() const { if (isCategory()) { return name; } const CategoryItem *cat=getTopLevelCategory(); if (!cat || !cat->addCatToModifiedName || name.startsWith(cat->name)) { return name; } return cat->name+QString(" – ")+name; } StreamsModel::CategoryItem * StreamsModel::Item::getTopLevelCategory() const { StreamsModel::Item *item=const_cast(this); while (item->parent && item->parent->parent) { item=item->parent; } return item && item->isCategory() ? static_cast(item) : 0; } void StreamsModel::CategoryItem::removeBookmarks() { if (bookmarksName.isEmpty()) { return; } QString fileName=categoryBookmarksName(bookmarksName); if (QFile::exists(fileName)) { QFile::remove(fileName); } } void StreamsModel::CategoryItem::saveBookmarks() { if (bookmarksName.isEmpty() || !supportsBookmarks) { return; } foreach (Item *child, children) { if (child->isCategory()) { CategoryItem *cat=static_cast(child); if (cat->isBookmarks) { if (cat->children.count()) { QFile file(categoryBookmarksName(bookmarksName, true)); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { QXmlStreamWriter doc(&compressor); doc.writeStartDocument(); doc.writeStartElement("bookmarks"); doc.writeAttribute("version", "1.0"); doc.setAutoFormatting(false); foreach (Item *i, cat->children) { doc.writeStartElement("bookmark"); doc.writeAttribute("name", i->name); doc.writeAttribute("url", i->url); doc.writeEndElement(); } doc.writeEndElement(); doc.writeEndElement(); } } break; } } } } QList StreamsModel::CategoryItem::loadBookmarks() { QList newItems; if (bookmarksName.isEmpty() || !supportsBookmarks) { return newItems; } QString fileName=categoryBookmarksName(bookmarksName); if (!QFile::exists(fileName)) { return newItems; } QFile file(fileName); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { QXmlStreamReader doc(&compressor); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("bookmark")==doc.name()) { QString name=doc.attributes().value("name").toString(); QString url=doc.attributes().value("url").toString(); if (!name.isEmpty() && !url.isEmpty()) { newItems.append(new CategoryItem(url, name, this)); } } } } return newItems; } StreamsModel::CategoryItem * StreamsModel::CategoryItem::getBookmarksCategory() { foreach (Item *i, children) { if (i->isCategory() && static_cast(i)->isBookmarks) { return static_cast(i); } } return 0; } StreamsModel::CategoryItem * StreamsModel::CategoryItem::createBookmarksCategory() { Icon icon=Icon("bookmarks"); if (icon.isNull()) { icon=Icon("user-bookmarks"); } CategoryItem *bookmarkCat = new CategoryItem(QString(), tr("Bookmarks"), this, icon); bookmarkCat->state=CategoryItem::Fetched; bookmarkCat->isBookmarks=true; return bookmarkCat; } void StreamsModel::CategoryItem::removeCache() { if (!cacheName.isEmpty()) { QString cache=categoryCacheName(cacheName); if (QFile::exists(cache)) { QFile::remove(cache); } } } void StreamsModel::CategoryItem::saveCache() const { if (!cacheName.isEmpty()) { saveXml(categoryCacheName(cacheName, true)); } } QList StreamsModel::CategoryItem::loadCache() { if (!cacheName.isEmpty()) { QString cache=categoryCacheName(cacheName); if (!cache.isEmpty() && QFile::exists(cache)) { return loadXml(cache); } } return QList(); } QList StreamsModel::XmlCategoryItem::loadCache() { QList newItems; if (QFile::exists(cacheName)) { newItems=loadXml(cacheName); QString dir=Utils::getDir(cacheName); foreach (Item *i, newItems) { if (i->isCategory()) { StreamsModel::CategoryItem *cat=static_cast(i); QString name=cat->name; name=name.replace(Utils::constDirSep, "_"); cat->icon=getExternalIcon(dir, QStringList() << name+".svg" << name+".png"); } } } return newItems; } bool StreamsModel::CategoryItem::saveXml(const QString &fileName, bool format) const { if (children.isEmpty()) { // No children, so remove XML... return !QFile::exists(fileName) || QFile::remove(fileName); } QFile file(fileName); if (fileName.endsWith(".xml")) { return file.open(QIODevice::WriteOnly) && saveXml(&file, format); } else { QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); return compressor.open(QIODevice::WriteOnly) && saveXml(&compressor, format); } } static void saveStream(QXmlStreamWriter &doc, const StreamsModel::Item *item) { doc.writeStartElement("stream"); doc.writeAttribute("name", item->name); doc.writeAttribute("url", item->url); doc.writeEndElement(); } static void saveCategory(QXmlStreamWriter &doc, const StreamsModel::CategoryItem *cat) { if (cat->isBookmarks) { return; } doc.writeStartElement("category"); doc.writeAttribute("name", cat->name); if (cat->isAll) { doc.writeAttribute("isAll", "true"); } foreach (const StreamsModel::Item *i, cat->children) { if (i->isCategory()) { saveCategory(doc, static_cast(i)); } else { saveStream(doc, static_cast(i)); } } doc.writeEndElement(); } bool StreamsModel::CategoryItem::saveXml(QIODevice *dev, bool format) const { QXmlStreamWriter doc(dev); doc.writeStartDocument(); doc.writeStartElement("streams"); doc.writeAttribute("version", "1.0"); if (format) { doc.setAutoFormatting(true); doc.setAutoFormattingIndent(1); } else { doc.setAutoFormatting(false); } foreach (const Item *i, children) { if (i->isCategory()) { ::saveCategory(doc, static_cast(i)); } else { ::saveStream(doc, i); } } doc.writeEndElement(); doc.writeEndDocument(); return true; } QList StreamsModel::CategoryItem::loadXml(const QString &fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { return QList(); } // Check for gzip header... QByteArray header=file.read(2); bool isCompressed=((unsigned char)header[0])==0x1f && ((unsigned char)header[1])==0x8b; file.seek(0); QtIOCompressor compressor(&file); if (isCompressed) { compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor.open(QIODevice::ReadOnly)) { return QList(); } } return loadXml(isCompressed ? (QIODevice *)&compressor : (QIODevice *)&file); } QList StreamsModel::CategoryItem::loadXml(QIODevice *dev) { QList newItems; QXmlStreamReader doc(dev); CategoryItem *currentCat=this; CategoryItem *prevCat=this; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (QLatin1String("streams")==doc.name()) { if (QLatin1String("true")==doc.attributes().value("addCategoryName").toString()) { addCatToModifiedName=true; } } else if (QLatin1String("stream")==doc.name()) { QString name=doc.attributes().value("name").toString(); QString url=doc.attributes().value("url").toString(); if (currentCat==this) { newItems.append(new Item(url, name, currentCat)); } else { currentCat->children.append(new Item(url, name, currentCat)); } } else if (QLatin1String("category")==doc.name()) { prevCat=currentCat; QString name=doc.attributes().value("name").toString(); currentCat=new CategoryItem(QString(), name, prevCat); currentCat->state=CategoryItem::Fetched; currentCat->isAll=QLatin1String("true")==doc.attributes().value("isAll").toString(); newItems.append(currentCat); } } else if (doc.isEndElement() && QLatin1String("category")==doc.name()) { currentCat=prevCat; } } return newItems; } QList StreamsModel::FavouritesCategoryItem::loadXml(QIODevice *dev) { QList newItems; QXmlStreamReader doc(dev); QSet existingUrls; QSet existingNames; foreach (Item *i, children) { existingUrls.insert(i->url); existingNames.insert(i->name); } while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("stream")==doc.name()) { QString name=doc.attributes().value("name").toString(); QString origName=name; QString url=doc.attributes().value("url").toString(); if (!url.isEmpty() && !name.isEmpty() && !existingUrls.contains(url)) { int i=1; for (; i<100 && existingNames.contains(name); ++i) { name=origName+QLatin1String(" (")+QString::number(i)+QLatin1Char(')'); } if (i<100) { existingNames.insert(name); existingUrls.insert(url); newItems.append(new Item(url, name, this)); } } } } return newItems; } void StreamsModel::IceCastCategoryItem::addHeaders(QNetworkRequest &req) { req.setRawHeader("Accept-Encoding", "gzip"); } void StreamsModel::ShoutCastCategoryItem::addHeaders(QNetworkRequest &req) { req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); } NetworkJob * StreamsModel::ShoutCastCategoryItem::fetchSecondardyUrl() { if (url!=constShoutCastUrl) { // Get stations... QUrl url(QLatin1String("http://")+constShoutCastHost+QLatin1String("/legacy/genresearch")); QUrlQuery query; query.addQueryItem("k", constShoutCastApiKey); query.addQueryItem("genre", name); url.setQuery(query); QNetworkRequest req(url); addHeaders(req); NetworkJob *job=NetworkAccessManager::self()->get(req); job->setProperty(constOrigUrlProperty, url.toString()); return job; } return 0; } void StreamsModel::DiCategoryItem::addHeaders(QNetworkRequest &req) { DigitallyImported::self()->addAuthHeader(req); } StreamsModel::StreamsModel(QObject *parent) : ActionModel(parent) , root(new CategoryItem(QString(), "root")) { icn.addFile(":radio.svg"); tuneIn=new CategoryItem(constRadioTimeUrl+QLatin1String("?locale=")+QLocale::system().name(), tr("TuneIn"), root, getIcon("tunein"), QString(), "tunein"); tuneIn->supportsBookmarks=true; root->children.append(tuneIn); root->children.append(new IceCastCategoryItem(constIceCastUrl, tr("IceCast"), root, getIcon("icecast"), "icecast")); shoutCast=new ShoutCastCategoryItem(constShoutCastUrl, tr("ShoutCast"), root, getIcon("shoutcast")); shoutCast->configName="shoutcast"; root->children.append(shoutCast); dirble=new DirbleCategoryItem(constDirbleUrl, tr("Dirble"), root, getIcon("dirble")); dirble->configName="dirble"; root->children.append(dirble); favourites=new FavouritesCategoryItem(constFavouritesUrl, tr("Favorites"), root, getIcon("favourites")); root->children.append(favourites); loadInstalledProviders(); addBookmarkAction = new Action(Icons::self()->addBookmarkIcon, tr("Bookmark Category"), this); addToFavouritesAction = new Action(favouritesIcon(), tr("Add Stream To Favorites"), this); configureDiAction = new Action(Icons::self()->configureIcon, tr("Configure Digitally Imported"), this); reloadAction = new Action(Icons::self()->reloadIcon, tr("Reload"), this); QSet hidden=Settings::self()->hiddenStreamCategories().toSet(); foreach (Item *c, root->children) { if (c!=favourites) { CategoryItem *cat=static_cast(c); if (hidden.contains(cat->configName)) { hiddenCategories.append(c); root->children.removeAll(c); } } } connect(MPDConnection::self(), SIGNAL(savedStream(QString,QString)), SLOT(savedFavouriteStream(QString,QString))); connect(MPDConnection::self(), SIGNAL(removedStreams(QList)), SLOT(removedFavouriteStreams(QList))); connect(MPDConnection::self(), SIGNAL(streamList(QList)), SLOT(favouriteStreams(QList))); connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionState(bool))); connect(this, SIGNAL(listFavouriteStreams()), MPDConnection::self(), SLOT(listStreams())); connect(this, SIGNAL(saveFavouriteStream(QString,QString)), MPDConnection::self(), SLOT(saveStream(QString,QString))); connect(this, SIGNAL(removeFavouriteStreams(QList)), MPDConnection::self(), SLOT(removeStreams(QList))); connect(this, SIGNAL(editFavouriteStream(QString,QString,quint32)), MPDConnection::self(), SLOT(editStream(QString,QString,quint32))); } StreamsModel::~StreamsModel() { delete root; } QString StreamsModel::name() const { return QLatin1String("streams"); } QString StreamsModel::title() const { return tr("Streams"); } QString StreamsModel::descr() const { return tr("Radio stations"); } QModelIndex StreamsModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const CategoryItem * p = parent.isValid() ? static_cast(parent.internalPointer()) : root; const Item * c = rowchildren.count() ? p->children.at(row) : 0; return c ? createIndex(row, column, (void *)c) : QModelIndex(); } QModelIndex StreamsModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } Item * parent = toItem(index)->parent; if (!parent || parent == root || !parent->parent) { return QModelIndex(); } return createIndex(static_cast(parent->parent)->children.indexOf(parent), 0, parent); } QVariant StreamsModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const { return QVariant(); } int StreamsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { Item *item = toItem(parent); return item->isCategory() ? static_cast(item)->children.count() : 0; } return root->children.count(); } int StreamsModel::columnCount(const QModelIndex &) const { return 1; } QVariant StreamsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: return descr(); case Qt::DecorationRole: return icon(); } return QVariant(); } const Item *item = toItem(index); switch (role) { case Qt::DecorationRole: if (item->isCategory()) { const CategoryItem *cat=static_cast(item); return cat->icon.isNull() ? Icons::self()->streamCategoryIcon : cat->icon; } else { return Icons::self()->streamListIcon; } case Qt::DisplayRole: return item->name; case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } return item->isCategory() ? item->name : (item->name+QLatin1String("
    ")+item->url+QLatin1String("")); case Cantata::Role_SubText: if (item->isCategory()) { const CategoryItem *cat=static_cast(item); switch (cat->state) { case CategoryItem::Initial: return tr("Not Loaded"); case CategoryItem::Fetching: return tr("Loading..."); default: return tr("%n Entry(s)", "", cat->children.count()); } } break; case Cantata::Role_Actions: { QList actions; if (item->isCategory()){ const CategoryItem *cat=static_cast(item); if (cat->isDi()) { actions << configureDiAction; } if (cat->canReload()) { actions << reloadAction; } if (cat->canBookmark) { actions << addBookmarkAction; } } else { actions << StdActions::self()->replacePlayQueueAction; if (item->parent!=favourites) { actions << addToFavouritesAction; } } if (!actions.isEmpty()) { QVariant v; v.setValue >(actions); return v; } break; } default: break; } return ActionModel::data(index, role); } Qt::ItemFlags StreamsModel::flags(const QModelIndex &index) const { if (index.isValid()) { if (toItem(index)->isCategory()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } bool StreamsModel::hasChildren(const QModelIndex &index) const { return index.isValid() ? toItem(index)->isCategory() : true; } bool StreamsModel::canFetchMore(const QModelIndex &index) const { if (index.isValid()) { Item *item = toItem(index); return item->isCategory() && CategoryItem::Initial==static_cast(item)->state && !item->url.isEmpty() && !static_cast(item)->isFavourites(); } else { return false; } } void StreamsModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } Item *item = toItem(index); if (item->isCategory() && !item->url.isEmpty()) { CategoryItem *cat=static_cast(item); if (!cat->isFavourites() && !loadCache(cat)) { QNetworkRequest req; if (cat->isDi()) { req=QNetworkRequest(constDiChannelListUrl.arg(cat->url.split(".").at(1))); } else { req=QNetworkRequest(cat->url); } cat->addHeaders(req); NetworkJob *job=NetworkAccessManager::self()->get(req); job->setProperty(constOrigUrlProperty, cat->url); if (jobs.isEmpty()) { emit loading(); } jobs.insert(job, cat); connect(job, SIGNAL(finished()), SLOT(jobFinished())); cat->state=CategoryItem::Fetching; job=cat->fetchSecondardyUrl(); if (job) { jobs.insert(job, cat); connect(job, SIGNAL(finished()), SLOT(jobFinished())); } } emit dataChanged(index, index); } } void StreamsModel::reload(const QModelIndex &index) { Item *item = toItem(index); if (!item->isCategory()) { return; } CategoryItem *cat=static_cast(item); if (!cat->children.isEmpty()) { cat->removeCache(); beginRemoveRows(index, 0, cat->children.count()-1); qDeleteAll(cat->children); cat->children.clear(); endRemoveRows(); } fetchMore(index); } bool StreamsModel::exportFavourites(const QString &fileName) { return favourites->saveXml(fileName, true); } void StreamsModel::importIntoFavourites(const QString &fileName) { QList newItems=favourites->CategoryItem::loadXml(fileName); if (!newItems.isEmpty()) { foreach (Item *i, newItems) { emit saveFavouriteStream(i->url, i->name); } qDeleteAll(newItems); } } void StreamsModel::removeFromFavourites(const QModelIndexList &indexes) { QList rows; foreach (const QModelIndex &idx, indexes) { rows.append(idx.row()); } if (!rows.isEmpty()) { emit removeFavouriteStreams(rows); } } bool StreamsModel::addToFavourites(const QString &url, const QString &name) { QSet existingNames; foreach (Item *i, favourites->children) { if (i->url==url) { return false; } existingNames.insert(i->name); } QString n=name; int i=1; for (; i<100 && existingNames.contains(n); ++i) { n=name+QLatin1String("_")+QString::number(i); } if (i<100) { emit saveFavouriteStream(url, name); return true; } return false; } QString StreamsModel::favouritesNameForUrl(const QString &u) { foreach (Item *i, favourites->children) { if (i->url==u) { return i->name; } } return QString(); } bool StreamsModel::nameExistsInFavourites(const QString &n) { foreach (Item *i, favourites->children) { if (i->name==n) { return true; } } return false; } void StreamsModel::updateFavouriteStream(const QString &url, const QString &name, const QModelIndex &idx) { emit editFavouriteStream(url, name, idx.row()); } bool StreamsModel::addBookmark(const QString &url, const QString &name, CategoryItem *bookmarkParentCat) { if (!bookmarkParentCat) { bookmarkParentCat=tuneIn; } if (bookmarkParentCat && !url.isEmpty() && !name.isEmpty()) { CategoryItem *bookmarkCat=bookmarkParentCat->getBookmarksCategory(); if (!bookmarkCat) { QModelIndex index=createIndex(bookmarkParentCat->parent->children.indexOf(bookmarkParentCat), 0, (void *)bookmarkParentCat); beginInsertRows(index, bookmarkParentCat->children.count(), bookmarkParentCat->children.count()); bookmarkCat=bookmarkParentCat->createBookmarksCategory(); bookmarkParentCat->children.append(bookmarkCat); endInsertRows(); } foreach (Item *i, bookmarkCat->children) { if (i->url==url) { return false; } } QModelIndex index=createIndex(bookmarkCat->parent->children.indexOf(bookmarkCat), 0, (void *)bookmarkCat); beginInsertRows(index, bookmarkCat->children.count(), bookmarkCat->children.count()); bookmarkCat->children.append(new CategoryItem(url, name, bookmarkCat)); endInsertRows(); bookmarkParentCat->saveBookmarks(); return true; } return false; } void StreamsModel::removeBookmark(const QModelIndex &index) { Item *item=toItem(index); if (item->isCategory() && item->parent && item->parent->isBookmarks) { CategoryItem *bookmarkCat=item->parent; // 'Bookmarks' CategoryItem *cat=bookmarkCat->parent; // e.g. 'TuneIn' if (1==bookmarkCat->children.count()) { // Only 1 bookark, so remove 'Bookmarks' folder... int pos=cat->children.indexOf(bookmarkCat); QModelIndex index=createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); beginRemoveRows(index, pos, pos); delete cat->children.takeAt(pos); endRemoveRows(); cat->removeBookmarks(); } else if (!bookmarkCat->children.isEmpty()) { // More than 1 bookmark... int pos=bookmarkCat->children.indexOf(item); QModelIndex index=createIndex(bookmarkCat->parent->children.indexOf(bookmarkCat), 0, (void *)bookmarkCat); beginRemoveRows(index, pos, pos); delete bookmarkCat->children.takeAt(pos); endRemoveRows(); cat->saveBookmarks(); } } } void StreamsModel::removeAllBookmarks(const QModelIndex &index) { Item *item=toItem(index); if (item->isCategory() && static_cast(item)->isBookmarks) { CategoryItem *cat=item->parent; // e.g. 'TuneIn' int pos=cat->children.indexOf(item); QModelIndex index=createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); beginRemoveRows(index, pos, pos); delete cat->children.takeAt(pos); endRemoveRows(); cat->removeBookmarks(); } } QModelIndex StreamsModel::favouritesIndex() const { return createIndex(root->children.indexOf(favourites), 0, (void *)favourites); } static QString addDiHash(const StreamsModel::Item *item) { return ( (item->parent && item->parent->isDi()) || DigitallyImported::self()->isDiUrl(item->url) ) ? DigitallyImported::self()->modifyUrl(item->url) : item->url; } QStringList StreamsModel::filenames(const QModelIndexList &indexes, bool addPrefix) const { QStringList fnames; foreach(QModelIndex index, indexes) { Item *item=static_cast(index.internalPointer()); if (!item->isCategory()) { fnames << modifyUrl(addDiHash(item), addPrefix, addPrefix ? item->modifiedName() : item->name); } } return fnames; } QMimeData * StreamsModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames(indexes, true)); return mimeData; } QStringList StreamsModel::mimeTypes() const { QStringList types; types << PlayQueueModel::constFileNameMimeType; return types; } void StreamsModel::save() { QStringList disabled; foreach (Item *i, hiddenCategories) { disabled.append(static_cast(i)->configName); } disabled.sort(); Settings::self()->saveHiddenStreamCategories(disabled); } QList StreamsModel::getCategories() const { QList categories; foreach (Item *i, hiddenCategories) { categories.append(Category(i->name, static_cast(i)->icon, static_cast(i)->configName, static_cast(i)->isBuiltIn(), true, static_cast(i)->isDi())); } foreach (Item *i, root->children) { if (i!=favourites) { categories.append(Category(i->name, static_cast(i)->icon, static_cast(i)->configName, static_cast(i)->isBuiltIn(), false, static_cast(i)->isDi())); } } return categories; } void StreamsModel::setHiddenCategories(const QSet &cats) { bool changed=false; foreach (Item *i, hiddenCategories) { if (!cats.contains(static_cast(i)->configName)) { beginInsertRows(QModelIndex(), root->children.count(), root->children.count()); root->children.append(i); hiddenCategories.removeAll(i); endInsertRows(); changed=true; } } foreach (Item *i, root->children) { if (cats.contains(static_cast(i)->configName)) { int row=root->children.indexOf(i); if (row>0) { beginRemoveRows(QModelIndex(), row, row); hiddenCategories.append(root->children.takeAt(row)); endRemoveRows(); changed=true; } } } if (changed) { emit categoriesChanged(); } } bool StreamsModel::loadCache(CategoryItem *cat) { QList newItems=cat->loadCache(); if (!newItems.isEmpty()) { QModelIndex index=createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); beginInsertRows(index, cat->children.count(), (cat->children.count()+newItems.count())-1); cat->children+=newItems; endInsertRows(); cat->state=CategoryItem::Fetched; emit dataChanged(index, index); return true; } return false; } void StreamsModel::jobFinished() { NetworkJob *job=dynamic_cast(sender()); if (!job) { return; } job->deleteLater(); if (jobs.contains(job)) { CategoryItem *cat=jobs[job]; if (!cat) { return; } jobs.remove(job); // ShoutCast and Dirble have two jobs when listing a category - child categories and station list. // So, only set as fetched if both are finished. bool haveOtherJob=false; foreach (CategoryItem *c, jobs.values()) { if (c==cat) { haveOtherJob=true; break; } } if (!haveOtherJob) { cat->state=CategoryItem::Fetched; } QModelIndex index=createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); if (job->ok()) { QList newItems; if (cat!=favourites && QLatin1String("http")==job->url().scheme()) { QString url=job->origUrl().toString(); if (constRadioTimeHost==job->origUrl().host()) { newItems=parseRadioTimeResponse(job->actualJob(), cat); } else if (constIceCastUrl==url) { newItems=parseIceCastResponse(job->actualJob(), cat); } else if (cat->isSoma()) { newItems=parseSomaFmResponse(job->actualJob(), cat); } else if (constDiChannelListHost==job->origUrl().host()) { newItems=parseDigitallyImportedResponse(job->actualJob(), cat); } else if (constShoutCastHost==job->origUrl().host()) { newItems=parseShoutCastResponse(job->actualJob(), cat); } else if (constDirbleHost==job->origUrl().host()) { newItems=parseDirbleResponse(job->actualJob(), cat, job->property(constOrigUrlProperty).toString()); } else if (cat->isListenLive()) { newItems=parseListenLiveResponse(job->actualJob(), cat); } } if (cat->parent==root && cat->supportsBookmarks) { QList bookmarks=cat->loadBookmarks(); if (bookmarks.count()) { CategoryItem *bookmarksCat=cat->getBookmarksCategory(); if (bookmarksCat) { QList newBookmarks; foreach (Item *bm, bookmarks) { foreach (Item *ex, bookmarksCat->children) { if (ex->url==bm->url) { delete bm; bm=0; break; } } if (bm) { newBookmarks.append(bm); bm->parent=bookmarksCat; } } if (newBookmarks.count()) { QModelIndex index=createIndex(bookmarksCat->parent->children.indexOf(bookmarksCat), 0, (void *)bookmarksCat); beginInsertRows(index, bookmarksCat->children.count(), (bookmarksCat->children.count()+newBookmarks.count())-1); bookmarksCat->children+=newBookmarks; endInsertRows(); } } else { bookmarksCat=cat->createBookmarksCategory(); foreach (Item *i, bookmarks) { i->parent=bookmarksCat; } bookmarksCat->children=bookmarks; newItems.append(bookmarksCat); } } } if (!newItems.isEmpty()) { beginInsertRows(index, cat->children.count(), (cat->children.count()+newItems.count())-1); cat->children+=newItems; endInsertRows(); if (cat!=favourites) { cat->saveCache(); } } } emit dataChanged(index, index); if (jobs.isEmpty()) { emit loaded(); } } } void StreamsModel::savedFavouriteStream(const QString &url, const QString &name) { if (favouritesNameForUrl(url).isEmpty()) { beginInsertRows(favouritesIndex(), favourites->children.count(), favourites->children.count()); favourites->children.append(new Item(url, name, favourites)); endInsertRows(); emit addedToFavourites(name); } else { emit listFavouriteStreams(); } } void StreamsModel::removedFavouriteStreams(const QList &removed) { if (removed.isEmpty()) { return; } quint32 adjust=0; QModelIndex parent=favouritesIndex(); QList::ConstIterator it=removed.constBegin(); QList::ConstIterator end=removed.constEnd(); while(it!=end) { quint32 rowBegin=*it; quint32 rowEnd=*it; QList::ConstIterator next=it+1; while(next!=end) { if (*next!=(rowEnd+1)) { break; } else { it=next; rowEnd=*next; next++; } } beginRemoveRows(parent, rowBegin-adjust, rowEnd-adjust); for (quint32 i=rowBegin; i<=rowEnd; ++i) { delete favourites->children.takeAt(rowBegin-adjust); } adjust+=(rowEnd-rowBegin)+1; endRemoveRows(); it++; } emit dataChanged(parent, parent); } static StreamsModel::Item * getStream(StreamsModel::FavouritesCategoryItem *fav, const Stream &stream, int offset) { int i=0; foreach (StreamsModel::Item *s, fav->children) { if (++iname==stream.name && s->url==stream.url) { return s; } } return 0; } void StreamsModel::favouriteStreams(const QList &streams) { QModelIndex idx=favouritesIndex(); if (favourites->children.isEmpty()) { if (streams.isEmpty()) { if (CategoryItem::Fetched!=favourites->state) { favourites->state=CategoryItem::Fetched; emit dataChanged(idx, idx); } return; } beginInsertRows(idx, 0, streams.count()-1); foreach (const Stream &s, streams) { favourites->children.append(new Item(s.url, s.name, favourites)); } endInsertRows(); } else if (streams.isEmpty()) { beginRemoveRows(idx, 0, favourites->children.count()-1); qDeleteAll(favourites->children); favourites->children.clear(); endRemoveRows(); } else { for (qint32 i=0; ichildren.count() ? favourites->children.at(i) : 0; if (!si || si->name!=s.name || si->url!=s.url) { si=ichildren.count() ? getStream(favourites, s, i) : 0; if (!si) { beginInsertRows(idx, i, i); favourites->children.insert(i, new Item(s.url, s.name, favourites)); endInsertRows(); } else { int existing=favourites->children.indexOf(si); beginMoveRows(idx, existing, existing, idx, i>existing ? i+1 : i); Item *si=favourites->children.takeAt(existing); favourites->children.insert(i, si); endMoveRows(); } } } if (favourites->children.count()>streams.count()) { int toRemove=favourites->children.count()-streams.count(); beginRemoveRows(idx, favourites->children.count()-toRemove, favourites->children.count()-1); for (int i=0; ichildren.takeLast(); } endRemoveRows(); } } importOldFavourites(); if (CategoryItem::Fetched!=favourites->state) { favourites->state=CategoryItem::Fetched; emit dataChanged(idx, idx); emit favouritesLoaded(); } } void StreamsModel::mpdConnectionState(bool c) { if (c) { emit listFavouriteStreams(); } } QList StreamsModel::parseRadioTimeResponse(QIODevice *dev, CategoryItem *cat, bool parseSubText) { QList newItems; QXmlStreamReader doc(dev); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("outline")==doc.name()) { Item *item = parseRadioTimeEntry(doc, cat, parseSubText); if (item) { newItems.append(item); } } } return newItems; } static QStringList fixGenres(const QString &genre) { QStringList allGenres=Song::capitalize(genre).split(' ', QString::SkipEmptyParts); QStringList fixed; foreach (const QString &genre, allGenres) { if (genre.length() < 2 || genre.contains("ÃÂ") || // Broken unicode. genre.contains(QRegExp("^#x[0-9a-f][0-9a-f]"))) { // Broken XML entities. continue; } // Convert 80 -> 80s. if (genre.contains(QRegExp("^[0-9]0$"))) { fixed << genre + 's'; } else { fixed << genre; } } if (fixed.isEmpty()) { fixed << QObject::tr("Other"); } return fixed; } static void trimGenres(QMap > &genres) { QString other=QObject::tr("Other"); QSet genreSet = genres.keys().toSet(); foreach (const QString &genre, genreSet) { if (other!=genre && genres[genre].count() < 2) { genres[other]+=genres[genre]; genres.remove(genre); } } } QList StreamsModel::parseIceCastResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; QtIOCompressor compressor(dev); compressor.setStreamFormat(QtIOCompressor::GzipFormat); compressor.open(QIODevice::ReadOnly); QXmlStreamReader doc(&compressor); QSet names; QMap > genres; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("entry")==doc.name()) { QString name; QString url; QStringList stationGenres; while (!doc.atEnd()) { doc.readNext(); if (QXmlStreamReader::StartElement==doc.tokenType()) { QStringRef elem = doc.name(); if (QLatin1String("server_name")==elem) { name=doc.readElementText().trimmed(); } else if (QLatin1String("listen_url")==elem) { url=doc.readElementText().trimmed(); } else if (QLatin1String("genre")==elem) { stationGenres=fixGenres(doc.readElementText().trimmed()); } } else if (doc.isEndElement() && QLatin1String("entry")==doc.name()) { break; } } if (!name.isEmpty() && !url.isEmpty() && !names.contains(name)) { names.insert(name); foreach (const QString &g, stationGenres) { genres[g].append(new Item(url, name, cat)); } } } } trimGenres(genres); QMap >::ConstIterator it(genres.constBegin()); QMap >::ConstIterator end(genres.constEnd()); for (; it!=end; ++it) { CategoryItem *genre=new CategoryItem(QString(), it.key(), cat); genre->state=CategoryItem::Fetched; foreach (Item *i, it.value()) { i->parent=genre; genre->children.append(i); } newItems.append(genre); } return newItems; } QList StreamsModel::parseSomaFmResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; QXmlStreamReader doc(dev); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("channel")==doc.name()) { Item *item = parseSomaFmEntry(doc, cat); if (item) { newItems.append(item); } } } return newItems; } QList StreamsModel::parseDigitallyImportedResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; QVariantMap data=QJsonDocument::fromJson(dev->readAll()).toVariant().toMap(); QString listenHost=QLatin1String("listen.")+QUrl(cat->url).host().remove("www."); if (data.contains("channel_filters")) { QVariantList filters = data["channel_filters"].toList(); foreach (const QVariant &filter, filters) { // Find the filter called "All" QVariantMap filterMap = filter.toMap(); if (filterMap.value("name", QString()).toString() != "All") { continue; } // Add all its stations to the result QVariantList channels = filterMap.value("channels", QVariantList()).toList(); foreach (const QVariant &channel, channels) { QVariantMap channelMap = channel.toMap(); QString url=constDiStdUrl.arg(listenHost).arg(channelMap.value("key").toString()); newItems.append(new Item(url, channelMap.value("name").toString(), cat)); } break; } } return newItems; } struct ListenLiveStream { enum Format { Unknown, WMA, OGG, MP3, AAC }; ListenLiveStream() : format(Unknown), bitrate(0) { } bool operator<(const ListenLiveStream &o) const { return weight()>o.weight(); } int weight() const { return ((bitrate&0xff)<<4)+(format&0x0f); } void setFormat(const QString &f) { if (QLatin1String("mp3")==f.toLower()) { format=MP3; } else if (QLatin1String("aacplus")==f.toLower()) { format=AAC; } else if (QLatin1String("ogg vorbis")==f.toLower()) { format=OGG; } else if (QLatin1String("windows media")==f.toLower()) { format=WMA; } else { format=Unknown; } } QString url; Format format; unsigned int bitrate; }; struct ListenLiveStationEntry { ListenLiveStationEntry() { clear(); } void clear() { name=location=QString(); streams.clear(); } QString name; QString location; QList streams; }; static QString getString(QString &str, const QString &start, const QString &end) { QString rv; int b=str.indexOf(start); int e=-1==b ? -1 : str.indexOf(end, b+start.length()); if (-1!=e) { rv=str.mid(b+start.length(), e-(b+start.length())).trimmed(); str=str.mid(e+end.length()); } return rv; } QList StreamsModel::parseListenLiveResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; if (dev) { ListenLiveStationEntry entry; while (!dev->atEnd()) { QString line=QString::fromUtf8(dev->readLine()).trimmed().replace("> <", "><").replace("", "").replace("
    ", "
    ") .replace(" ,", ","); if (""==line) { entry.clear(); } else if (line.startsWith("", ""); QString extra=getString(line, "", ""); if (!extra.isEmpty()) { entry.name+=" "+extra; } } else { // Station URLs... QString url; QString bitrate; int idx=0; do { url=getString(line, "href=\"", "\""); bitrate=getString(line, ">", " Kbps"); bool sameFormatAsLast=line.startsWith(","); if (!url.isEmpty() && !bitrate.isEmpty() && !url.startsWith(QLatin1String("javascript")) && idx")) { if (entry.location.isEmpty()) { entry.location=getString(line, "", ""); } } else if (""==line) { if (entry.streams.count()) { qSort(entry.streams); QString name; QString url=entry.streams.at(0).url; if (QLatin1String("National")==entry.location || entry.name.endsWith(QLatin1Char('(')+entry.location+QLatin1Char(')'))) { name=entry.name; } else if (entry.name.endsWith(QLatin1Char(')'))) { name=entry.name.left(entry.name.length()-1)+QLatin1String(", ")+entry.location+QLatin1Char(')'); } else { name=entry.name+QLatin1String(" (")+entry.location+QLatin1Char(')'); } if (!name.isEmpty()) { newItems.append(new Item(url, name, cat)); } } } } } return newItems; } QList StreamsModel::parseShoutCastSearchResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; QXmlStreamReader doc(dev); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("stationlist")==doc.name()) { newItems+=parseShoutCastStations(doc, cat); } } return newItems; } QList StreamsModel::parseShoutCastResponse(QIODevice *dev, CategoryItem *cat) { QList newItems; QXmlStreamReader doc(dev); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("genrelist")==doc.name()) { newItems+=parseShoutCastLinks(doc, cat); } else if (doc.isStartElement() && QLatin1String("stationlist")==doc.name()) { newItems+=parseShoutCastStations(doc, cat); } } return newItems; } QList StreamsModel::parseShoutCastLinks(QXmlStreamReader &doc, CategoryItem *cat) { QList newItems; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("genre")==doc.name()) { newItems.append(new ShoutCastCategoryItem(QLatin1String("http://")+constShoutCastHost+QLatin1String("/genre/secondary?parentid=")+ doc.attributes().value("id").toString()+QLatin1String("&f=xml&k=")+constShoutCastApiKey, doc.attributes().value("name").toString(), cat)); } else if (doc.isEndElement() && QLatin1String("genrelist")==doc.name()) { return newItems; } } return newItems; } QList StreamsModel::parseShoutCastStations(QXmlStreamReader &doc, CategoryItem *cat) { QList newItems; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("station")==doc.name()) { QString name=doc.attributes().value("name").toString().trimmed().simplified(); if (!name.isEmpty()) { newItems.append(new Item(QLatin1String("http://yp.shoutcast.com/sbin/tunein-station.pls?id=")+ doc.attributes().value("id").toString(), doc.attributes().value("name").toString(), cat)); } } else if (doc.isEndElement() && QLatin1String("stationlist")==doc.name()) { return newItems; } } return newItems; } QList StreamsModel::parseDirbleResponse(QIODevice *dev, CategoryItem *cat, const QString &origUrl) { if (origUrl.contains("/v2/category/") && origUrl.contains("/stations")) { return parseDirbleStations(dev, cat); } QList newItems; QVariantList data=QJsonDocument::fromJson(dev->readAll()).toVariant().toList(); // Get categories...::f if (!data.isEmpty()) { foreach (const QVariant &d, data) { QVariantMap map = d.toMap(); newItems.append(new DirbleCategoryItem(QLatin1String("http://")+constDirbleHost+QLatin1String("/v2/category/")+map["id"].toString()+ QLatin1String("/stations?token=")+StreamsModel::constDirbleApiKey, map["title"].toString(), cat)); } } return newItems; } QList StreamsModel::parseDirbleStations(QIODevice *dev, CategoryItem *cat) { QList newItems; QVariantList data=QJsonDocument::fromJson(dev->readAll()).toVariant().toList(); QSet added; foreach (const QVariant &d, data) { QVariantMap map = d.toMap(); QString name=map["name"].toString().trimmed().simplified(); if (!name.isEmpty()) { QVariantList streams=map["streams"].toList(); int bitrate=0; QString url; foreach (const QVariant &s, streams) { QVariantMap streamMap=s.toMap(); int br=streamMap["bitrate"].toInt(); QString u=streamMap["stream"].toString().trimmed().simplified(); if (br>bitrate && !u.isEmpty()) { bitrate=br; url=u; } } if (!url.isEmpty() && !added.contains(url)) { added.insert(url); newItems.append(new Item(url, name, cat, QString::number(bitrate))); } } } return newItems; } StreamsModel::Item * StreamsModel::parseRadioTimeEntry(QXmlStreamReader &doc, CategoryItem *parent, bool parseSubText) { Item *item=0; CategoryItem *cat=0; while (!doc.atEnd()) { if (doc.isStartElement()) { QString text=doc.attributes().value("text").toString(); if (!text.isEmpty()) { QString url=doc.attributes().value("URL").toString(); QString subText=parseSubText ? doc.attributes().value("subtext").toString() : QString(); bool isStation=QLatin1String("audio")==doc.attributes().value("type").toString(); if (isStation) { item=new Item(url, text, parent, subText); } else { cat=new CategoryItem(url, text, parent); cat->canBookmark=!url.isEmpty(); item=cat; } } } doc.readNext(); if (doc.isStartElement() && QLatin1String("outline")==doc.name()) { Item *child = parseRadioTimeEntry(doc, cat); if (child) { if (cat) { cat->state=CategoryItem::Fetched; cat->children.append(child); } else { delete child; } } } else if (doc.isEndElement() && QLatin1String("outline")==doc.name()) { break; } } return item; } StreamsModel::Item * StreamsModel::parseSomaFmEntry(QXmlStreamReader &doc, CategoryItem *parent) { QString name; QString url; QString streamFormat; while (!doc.atEnd()) { doc.readNext(); if (QXmlStreamReader::StartElement==doc.tokenType()) { QStringRef elem = doc.name(); if (QLatin1String("title")==elem) { name=doc.readElementText().trimmed(); } else if (QLatin1String("fastpls")==elem) { if (streamFormat.isEmpty() || QLatin1String("mp3")!=streamFormat) { streamFormat=doc.attributes().value("format").toString(); url=doc.readElementText(); } } } else if (doc.isEndElement() && QLatin1String("channel")==doc.name()) { break; } } return name.isEmpty() || url.isEmpty() ? 0 : new Item(url, name, parent); } void StreamsModel::importOldFavourites() { if (!favourites->importedOld) { if (Settings::self()->version()storeStreamsInMpdDir() ? MPDConnection::self()->getDetails().dir : Utils::dataDir(QString(), false); if (!prevFile.isEmpty()) { prevFile+="streams.xml.gz"; if (QFile::exists(prevFile)) { importIntoFavourites(prevFile); } } } favourites->importedOld=true; } } void StreamsModel::loadInstalledProviders() { QSet added; QString dir=Utils::dataDir(StreamsModel::constSubDir); QStringList subDirs=QDir(dir).entryList(QStringList() << "*", QDir::Dirs|QDir::Readable|QDir::NoDot|QDir::NoDotDot); QStringList streamFiles=QStringList() << constCompressedXmlFile << constXmlFile << constSettingsFile; foreach (const QString &sub, subDirs) { if (!added.contains(sub)) { foreach (const QString &streamFile, streamFiles) { if (QFile::exists(dir+sub+Utils::constDirSep+streamFile)) { addInstalledProvider(sub, getExternalIcon(dir+sub), dir+sub+Utils::constDirSep+streamFile, false); added.insert(sub); break; } } } } } StreamsModel::CategoryItem * StreamsModel::addInstalledProvider(const QString &name, const Icon &icon, const QString &streamsFileName, bool replace) { CategoryItem *cat=0; if (streamsFileName.endsWith(constSettingsFile)) { QFile file(streamsFileName); if (file.open(QIODevice::ReadOnly)) { QVariantMap map=QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); QString type=map["type"].toString(); QString url=map["url"].toString(); if (!url.isEmpty() && !type.isEmpty()) { QStringList toRemove=QStringList() << " " << "." << "/" << "\\" << "(" << ")"; QString cacheName=name; foreach (const QString &rem, toRemove) { cacheName=cacheName.replace(rem, ""); } cacheName=cacheName.toLower(); if (type=="di") { cat=new DiCategoryItem(url, name, root, icon, cacheName); } else if (type=="soma") { cat=new SomaCategoryItem(url, name, root, icon, cacheName, map["modName"].toBool()); } else if (type=="listenlive") { cat=new ListenLiveCategoryItem(url, name, root, icon, cacheName); } } } } else { cat=new XmlCategoryItem(name, root, icon, streamsFileName); } if (!cat) { return 0; } cat->configName="x-"+name; if (replace) { // Remove any existing entry... removeInstalledProvider(cat->configName); } if (replace) { beginInsertRows(QModelIndex(), root->children.count(), root->children.count()); root->children.append(cat); endInsertRows(); } else { root->children.append(cat); } return cat; } void StreamsModel::removeInstalledProvider(const QString &key) { foreach (Item *i, root->children) { if (key==static_cast(i)->configName) { int row=root->children.indexOf(i); if (row>=0) { static_cast(i)->removeCache(); beginRemoveRows(QModelIndex(), row, row); delete root->children.takeAt(row); endRemoveRows(); } break; } } } QModelIndex StreamsModel::categoryIndex(const CategoryItem *cat) const { int row=root->children.indexOf(const_cast(cat)); return -1==row ? QModelIndex() : createIndex(row, 0, (void *)cat); } const QString StreamsModel::constPrefix=QLatin1String("cantata-stream-"); QString StreamsModel::modifyUrl(const QString &u, bool addPrefix, const QString &name) { return MPDParseUtils::addStreamName(!addPrefix || !u.startsWith("http:") ? u : (constPrefix+u), name); } cantata-2.2.0/models/streamsmodel.h000066400000000000000000000326021316350454000172650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMSMODEL_H #define STREAMSMODEL_H #include "config.h" #include #include "actionmodel.h" #include "mpd-interface/stream.h" #include "mpd-interface/playlist.h" #include "support/icon.h" #include #include #include #include class NetworkJob; class QNetworkRequest; class QXmlStreamReader; class QIODevice; class StreamsModel : public ActionModel { Q_OBJECT public: struct CategoryItem; struct Item : public Stream { Item(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QString &sub=QString()) : Stream(u, n), subText(sub), parent(p) { } virtual ~Item() { } QString modifiedName() const; QString subText; CategoryItem *parent; virtual bool isCategory() const { return false; } CategoryItem *getTopLevelCategory() const; }; struct CategoryItem : public Item { enum State { Initial, Fetching, Fetched }; CategoryItem(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QIcon &i=QIcon(), const QString &cn=QString(), const QString &bn=QString(), bool modName=false) : Item(u, n, p), state(Initial), isAll(false), isBookmarks(false), supportsBookmarks(false), canBookmark(false), addCatToModifiedName(modName), icon(i), cacheName(cn), bookmarksName(bn), configName(cn.isEmpty() ? bn : cn) { } virtual ~CategoryItem() { qDeleteAll(children); } virtual bool isCategory() const { return true; } virtual bool isFavourites() const { return false; } virtual bool isBuiltIn() const { return true; } virtual bool isDi() const { return false; } virtual bool isSoma() const { return false; } virtual bool isListenLive() const { return false; } virtual void removeCache(); bool isTopLevel() const { return parent && 0==parent->parent; } virtual bool canReload() const { return !cacheName.isEmpty() || isTopLevel() || !url.isEmpty(); } void removeBookmarks(); void saveBookmarks(); QList loadBookmarks(); CategoryItem * getBookmarksCategory(); CategoryItem * createBookmarksCategory(); void saveCache() const; virtual QList loadCache(); bool saveXml(const QString &fileName, bool format=false) const; bool saveXml(QIODevice *dev, bool format=false) const; QList loadXml(const QString &fileName); virtual QList loadXml(QIODevice *dev); virtual void addHeaders(QNetworkRequest &) { } virtual NetworkJob * fetchSecondardyUrl() { return 0; } State state; bool isAll : 1; bool isBookmarks : 1; // 'Virtual' bookmarks category... bool supportsBookmarks : 1; // Intended for top-level items, indicates if bookmarks can be added bool canBookmark : 1; // Can this category be bookmark'ed in top-level parent? can have the cache bool addCatToModifiedName : 1; // When adding to playqueue/favourites, should name contian category? QList children; QIcon icon; QString cacheName; QString bookmarksName; QString configName; }; struct FavouritesCategoryItem : public CategoryItem { FavouritesCategoryItem(const QString &u, const QString &n, CategoryItem *p, const QIcon &i) : CategoryItem(u, n, p, i), importedOld(false) { } QList loadXml(QIODevice *dev); bool isFavourites() const { return true; } bool canReload() const { return false; } bool importedOld; }; struct IceCastCategoryItem : public CategoryItem { IceCastCategoryItem(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QIcon &i=QIcon(), const QString &cn=QString(), const QString &bn=QString()) : CategoryItem(u, n, p, i, cn, bn) { } void addHeaders(QNetworkRequest &req); }; struct ShoutCastCategoryItem : public CategoryItem { ShoutCastCategoryItem(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QIcon &i=QIcon(), const QString &cn=QString(), const QString &bn=QString()) : CategoryItem(u, n, p, i, cn, bn) { } void addHeaders(QNetworkRequest &req); NetworkJob * fetchSecondardyUrl(); }; struct DirbleCategoryItem : public CategoryItem { DirbleCategoryItem(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QIcon &i=QIcon(), const QString &cn=QString(), const QString &bn=QString()) : CategoryItem(u, n, p, i, cn, bn) { } }; struct ListenLiveCategoryItem : public CategoryItem { ListenLiveCategoryItem(const QString &u, const QString &n, CategoryItem *p, const QIcon &i, const QString &cn) : CategoryItem(u, n, p, i, cn) { } bool isListenLive() const { return true; } bool isBuiltIn() const { return false; } }; struct DiCategoryItem : public CategoryItem { DiCategoryItem(const QString &u, const QString &n, CategoryItem *p, const QIcon &i, const QString &cn) : CategoryItem(u, n, p, i, cn, QString(), true) { } void addHeaders(QNetworkRequest &req); bool isDi() const { return true; } bool isBuiltIn() const { return false; } }; struct SomaCategoryItem : public CategoryItem { SomaCategoryItem(const QString &u, const QString &n, CategoryItem *p, const QIcon &i, const QString &cn, bool mod) : CategoryItem(u, n, p, i, cn, QString(), mod) { } bool isSoma() const { return true; } bool isBuiltIn() const { return false; } }; struct XmlCategoryItem : public CategoryItem { XmlCategoryItem(const QString &n, CategoryItem *p, const QIcon &i, const QString &cn) : CategoryItem(QLatin1String("-"), n, p, i, cn, QString(), true) { } QList loadCache(); bool canReload() const { return false; } void removeCache() { } bool isBuiltIn() const { return false; } }; struct Category { Category(const QString &n, const QIcon &i, const QString &k, bool b, bool h, bool c) : name(n), icon(i), key(k), builtin(b), hidden(h), configurable(c) { } QString name; QIcon icon; QString key; bool builtin; bool hidden; bool configurable; }; static const QString constPrefix; static const QString constSubDir; static const QString constCacheExt; static const QString constShoutCastApiKey; static const QString constShoutCastHost; static const QString constDirbleApiKey; static const QString constDirbleHost; static const QString constCompressedXmlFile; static const QString constXmlFile; static const QString constSettingsFile; static const QString constPngIcon; static const QString constSvgIcon; static const QStringList constInstallFiles; static StreamsModel * self(); static QString favouritesDir(); static QString modifyUrl(const QString &u, bool addPrefix=true, const QString &name=QString()); // static bool validProtocol(const QString &file); StreamsModel(QObject *parent = 0); ~StreamsModel(); QString name() const; QString title() const; QString descr() const; const Icon & icon() const { return icn; } QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const; QModelIndex parent(const QModelIndex &) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &) const; QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; bool hasChildren(const QModelIndex &index) const; bool canFetchMore(const QModelIndex &index) const; void fetchMore(const QModelIndex &index); void reload(const QModelIndex &index); bool exportFavourites(const QString &fileName); void importIntoFavourites(const QString &fileName); bool haveFavourites() const { return !favourites->children.isEmpty(); } void removeFromFavourites(const QModelIndexList &indexes); bool addToFavourites(const QString &url, const QString &name); QString favouritesNameForUrl(const QString &u); bool nameExistsInFavourites(const QString &n); void updateFavouriteStream(const QString &url, const QString &name, const QModelIndex &idx); bool addBookmark(const QString &url, const QString &name, CategoryItem *bookmarkParentCat); void removeBookmark(const QModelIndex &index); void removeAllBookmarks(const QModelIndex &index); QModelIndex favouritesIndex() const; const QIcon & favouritesIcon() const { return favourites->icon; } bool isTuneIn(const CategoryItem *cat) const { return tuneIn==cat; } bool isShoutCast(const CategoryItem *cat) const { return shoutCast==cat; } bool isDirble(const CategoryItem *cat) const { return dirble==cat; } CategoryItem * tuneInCat() const { return tuneIn; } QStringList filenames(const QModelIndexList &indexes, bool addPrefix) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QStringList mimeTypes() const; Action *addBookmarkAct() { return addBookmarkAction; } Action *addToFavouritesAct() { return addToFavouritesAction; } Action *configureDiAct() { return configureDiAction; } Action *reloadAct() { return reloadAction; } void save(); QList getCategories() const; void setHiddenCategories(const QSet &cats); CategoryItem * addInstalledProvider(const QString &name, const Icon &icon, const QString &streamsFileName, bool replace); void removeInstalledProvider(const QString &key); QModelIndex categoryIndex(const CategoryItem *cat) const; Q_SIGNALS: void loading(); void loaded(); void error(const QString &msg); void categoriesChanged(); // Streams stored on MPD... void listFavouriteStreams(); void saveFavouriteStream(const QString &url, const QString &name); void removeFavouriteStreams(const QList &positions); void editFavouriteStream(const QString &url, const QString &name, quint32 position); void favouritesLoaded(); void addedToFavourites(const QString &name); public: static QList parseRadioTimeResponse(QIODevice *dev, CategoryItem *cat, bool parseSubText=false); static QList parseIceCastResponse(QIODevice *dev, CategoryItem *cat); static QList parseSomaFmResponse(QIODevice *dev, CategoryItem *cat); static QList parseDigitallyImportedResponse(QIODevice *dev, CategoryItem *cat); static QList parseListenLiveResponse(QIODevice *dev, CategoryItem *cat); static QList parseShoutCastSearchResponse(QIODevice *dev, CategoryItem *cat); QList parseShoutCastResponse(QIODevice *dev, CategoryItem *cat); static QList parseShoutCastLinks(QXmlStreamReader &doc, CategoryItem *cat); static QList parseShoutCastStations(QXmlStreamReader &doc, CategoryItem *cat); QList parseDirbleResponse(QIODevice *dev, CategoryItem *cat, const QString &origUrl); static QList parseDirbleStations(QIODevice *dev, CategoryItem *cat); static Item * parseRadioTimeEntry(QXmlStreamReader &doc, CategoryItem *parent, bool parseSubText=false); static Item * parseSomaFmEntry(QXmlStreamReader &doc, CategoryItem *parent); private Q_SLOTS: void jobFinished(); // Responses from MPD... void savedFavouriteStream(const QString &url, const QString &name); void removedFavouriteStreams(const QList &removed); // void editedFavouriteStream(const QString &url, const QString &name, quint32 position); void favouriteStreams(const QList &streams); void mpdConnectionState(bool c); private: bool loadCache(CategoryItem *cat); Item * toItem(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : root; } void importOldFavourites(); void loadInstalledProviders(); private: QMap jobs; CategoryItem *root; FavouritesCategoryItem *favourites; CategoryItem *tuneIn; CategoryItem *shoutCast; CategoryItem *dirble; Action *addBookmarkAction; Action *addToFavouritesAction; Action *configureDiAction; Action *reloadAction; QList hiddenCategories; Icon icn; }; #endif cantata-2.2.0/models/streamsproxymodel.cpp000066400000000000000000000103771316350454000207270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamsproxymodel.h" #include "streamsmodel.h" StreamsProxyModel::StreamsProxyModel(QObject *parent) : ProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); sort(0); } bool StreamsProxyModel::filterAcceptsItem(const void *i, QStringList strings) const { const StreamsModel::Item *item=static_cast(i); strings << item->name; if (matchesFilter(strings)) { return true; } if (item->isCategory()) { const StreamsModel::CategoryItem *cat=static_cast(item); foreach (const StreamsModel::Item *c, cat->children) { if (filterAcceptsItem(c, strings)) { return true; } } } return false; } bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { return true; } if (!isChildOfRoot(sourceParent)) { return true; } const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); StreamsModel::Item *item = static_cast(index.internalPointer()); QModelIndex idx=index.parent(); QStringList strings; if (filter && item==filter) { // Accept top-level item! return true; } if (filter && !idx.isValid() && item!=filter) { // Accept all items that are not children of top-level item! return true; } // Traverse back up tree, so we get parent strings... while (idx.isValid()) { StreamsModel::Item *i = static_cast(idx.internalPointer()); if (!i->isCategory()) { break; } strings << i->name; idx=idx.parent(); if (filter && !idx.isValid() && i!=filter) { // Accept all items that are not children of top-level item! return true; } } if (item->isCategory()) { // Check *all* children... if (filterAcceptsItem(item, strings)) { return true; } } else { strings << item->name; return matchesFilter(strings); } return false; } bool StreamsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const StreamsModel::Item * leftItem = static_cast(left.internalPointer()); const StreamsModel::Item * rightItem = static_cast(right.internalPointer()); if (leftItem->isCategory() && !rightItem->isCategory()) { return true; } if (!leftItem->isCategory() && rightItem->isCategory()) { return false; } if (leftItem->isCategory() && rightItem->isCategory()) { const StreamsModel::CategoryItem * leftCat = static_cast(leftItem); const StreamsModel::CategoryItem * rightCat = static_cast(rightItem); if (leftCat->isFavourites() && !rightCat->isFavourites()) { return true; } if (!leftCat->isFavourites() && rightCat->isFavourites()) { return false; } if (leftCat->isAll && !rightCat->isAll) { return true; } if (!leftCat->isAll && rightCat->isAll) { return false; } } return QSortFilterProxyModel::lessThan(left, right); } cantata-2.2.0/models/streamsproxymodel.h000066400000000000000000000023661316350454000203730ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMSPROXYMODEL_H #define STREAMSPROXYMODEL_H #include "proxymodel.h" class StreamsProxyModel : public ProxyModel { public: StreamsProxyModel(QObject *parent = 0); bool filterAcceptsItem(const void *i, QStringList strings) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; #endif cantata-2.2.0/mpd-interface/000077500000000000000000000000001316350454000156455ustar00rootroot00000000000000cantata-2.2.0/mpd-interface/cuefile.cpp000066400000000000000000000327021316350454000177710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #include "cuefile.h" #include "mpdconnection.h" #include "support/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include static const QString constFileLineRegExp = QLatin1String("(\\S+)\\s+(?:\"([^\"]+)\"|(\\S+))\\s*(?:\"([^\"]+)\"|(\\S+))?"); static const QString constIndexRegExp = QLatin1String("(\\d{2,3}):(\\d{2}):(\\d{2})"); static const QString constPerformer = QLatin1String("performer"); static const QString constTitle = QLatin1String("title"); static const QString constSongWriter = QLatin1String("songwriter"); static const QString constFile = QLatin1String("file"); static const QString constTrack = QLatin1String("track"); static const QString constDisc = QLatin1String("discnumber"); static const QString constIndex = QLatin1String("index"); static const QString constAudioTrackType = QLatin1String("audio"); static const QString constRem = QLatin1String("rem"); static const QString constGenre = QLatin1String("genre"); static const QString constDate = QLatin1String("date"); static const QString constCueProtocol = QLatin1String("cue:///"); bool CueFile::isCue(const QString &str) { return str.startsWith(constCueProtocol); } QByteArray CueFile::getLoadLine(const QString &str) { QUrl u(str); QUrlQuery q(u); if (q.hasQueryItem("pos")) { QString pos=q.queryItemValue("pos"); QString path=u.path(); if (path.startsWith("/")) { path=path.mid(1); } return MPDConnection::encodeName(path)+" \""+pos.toLatin1()+":"+QString::number(pos.toInt()+1).toLatin1()+"\""; } return MPDConnection::encodeName(str); } // A single TRACK entry in .cue file. struct CueEntry { QString file; QString index; int trackNo; int discNo; QString title; QString artist; QString albumArtist; QString album; QString composer; QString albumComposer; QString genre; QString date; CueEntry(QString &file, QString &index, int trackNo, int discNo, QString &title, QString &artist, QString &albumArtist, QString &album, QString &composer, QString &albumComposer, QString &genre, QString &date) { this->file = file; this->index = index; this->trackNo = trackNo; this->discNo = discNo; this->title = title; this->artist = artist; this->albumArtist = albumArtist; this->album = album; this->composer = composer; this->albumComposer = albumComposer; this->genre = genre; this->date = date; } }; static const qint64 constMsecPerSec = 1000; static qint64 indexToMarker(const QString& index) { QRegExp indexRegexp(constIndexRegExp); if (!indexRegexp.exactMatch(index)) { return -1; } QStringList splitted = indexRegexp.capturedTexts().mid(1, -1); qlonglong frames = splitted.at(0).toLongLong() * 60 * 75 + splitted.at(1).toLongLong() * 75 + splitted.at(2).toLongLong(); return (frames * constMsecPerSec) / 75; } // This and the constFileLineRegExp do most of the "dirty" work, namely: splitting the raw .cue // line into logical parts and getting rid of all the unnecessary whitespaces and quoting. static QStringList splitCueLine(const QString &line) { QRegExp lineRegexp(constFileLineRegExp); if (!lineRegexp.exactMatch(line.trimmed())) { return QStringList(); } // let's remove the empty entries while we're at it return lineRegexp.capturedTexts().filter(QRegExp(".+")).mid(1, -1); } // Updates the song with data from the .cue entry. This one mustn't be used for the // last song in the .cue file. static bool updateSong(const CueEntry &entry, const QString &nextIndex, Song &song) { qint64 beginning = indexToMarker(entry.index); qint64 end = indexToMarker(nextIndex); // incorrect indices (we won't be able to calculate beginning or end) if (-1==beginning || -1==end) { return false; } song.title=entry.title; song.artist=entry.artist; song.album=entry.album; song.albumartist=entry.albumArtist; song.addGenre(entry.genre); song.year=entry.date.toInt(); song.time=(end-beginning)/constMsecPerSec; if (!entry.composer.isEmpty()) { song.setComposer(entry.composer); } else if (!entry.albumComposer.isEmpty()) { song.setComposer(entry.albumComposer); } return true; } // Updates the song with data from the .cue entry. This one must be used only for the // last song in the .cue file. static bool updateLastSong(const CueEntry &entry, Song &song) { qint64 beginning = indexToMarker(entry.index); // incorrect index (we won't be able to calculate beginning) if (-1==beginning ) { return false; } song.title=entry.title; song.artist=entry.artist; song.album=entry.album; song.albumartist=entry.albumArtist; song.addGenre(entry.genre); song.year=entry.date.toInt(); if (!entry.composer.isEmpty()) { song.setComposer(entry.composer); } else if (!entry.albumComposer.isEmpty()) { song.setComposer(entry.albumComposer); } return true; } // Get list of text codecs used to decode CUE files... static const QList & codecList() { static QList codecs; if (codecs.isEmpty()) { codecs.append(QTextCodec::codecForName("UTF-8")); QTextCodec *codec=QTextCodec::codecForLocale(); if (codec && !codecs.contains(codec)) { codecs.append(codec); } codec=QTextCodec::codecForName("System"); if (codec && !codecs.contains(codec)) { codecs.append(codec); } } return codecs; } bool CueFile::parse(const QString &fileName, const QString &dir, QList &songList, QSet &files) { QScopedPointer textStream; QString decoded; QFile f(dir+fileName); if (f.open(QIODevice::ReadOnly)) { // First attempt to use QTextDecoder to decode cue file contents into a QString QByteArray contents=f.readAll(); foreach (QTextCodec *codec, codecList()) { QTextDecoder decoder(codec); decoded=decoder.toUnicode(contents); if (!decoder.hasFailure()) { textStream.reset(new QTextStream(&decoded, QIODevice::ReadOnly)); break; } } f.close(); if (!textStream) { decoded.clear(); // Failed to use text decoders, fall back to old method... f.open(QIODevice::ReadOnly|QIODevice::Text); textStream.reset(new QTextStream(&f)); textStream->setCodec(QTextCodec::codecForUtfText(f.peek(1024), QTextCodec::codecForName("UTF-8"))); } } if (!textStream) { return false; } // read the first line already QString line = textStream->readLine(); QList entries; QString fileDir=fileName.contains("/") ? Utils::getDir(fileName) : QString(); // -- whole file while (!textStream->atEnd()) { QString albumArtist; QString album; QString albumComposer; QString file; QString fileType; QString genre; QString date; int disc = 0; // -- FILE section do { QStringList splitted = splitCueLine(line); // uninteresting or incorrect line if (splitted.size() < 2) { continue; } QString lineName = splitted[0].toLower(); QString lineValue = splitted[1]; if (lineName == constPerformer) { albumArtist = lineValue; } else if (lineName == constTitle) { album = lineValue; } else if (lineName == constSongWriter) { albumComposer = lineValue; } else if (lineName == constFile) { file = lineValue; if (splitted.size() > 2) { fileType = splitted[2]; } } else if (lineName == constRem) { if (splitted.size() < 3) { break; } if (lineValue.toLower() == constGenre) { genre = splitted[2]; } else if (lineValue.toLower() == constDate) { date = splitted[2]; } else if (lineValue.toLower() == constDisc) { disc = splitted[2].toInt(); } // end of the header -> go into the track mode } else if (lineName == constTrack) { break; } // just ignore the rest of possible field types for now... } while (!(line = textStream->readLine()).isNull()); if (line.isNull()) { return false; } // if this is a data file, all of it's tracks will be ignored bool validFile = fileType.compare("BINARY", Qt::CaseInsensitive) && fileType.compare("MOTOROLA", Qt::CaseInsensitive); QString trackType; QString index; QString artist; QString composer; QString title; int trackNo=0; // TRACK section do { QStringList splitted = splitCueLine(line); // uninteresting or incorrect line if (splitted.size() < 2) { continue; } QString lineName = splitted[0].toLower(); QString lineValue = splitted[1]; QString lineAdditional = splitted.size() > 2 ? splitted[2].toLower() : QString(); if (lineName == constTrack) { // the beginning of another track's definition - we're saving the current one // for later (if it's valid of course) // please note that the same code is repeated just after this 'do-while' loop if (validFile && !index.isEmpty() && (trackType.isEmpty() || trackType == constAudioTrackType)) { entries.append(CueEntry(file, index, trackNo, disc, title, artist, albumArtist, album, composer, albumComposer, genre, date)); } // clear the state trackType = index = artist = title = QString(); if (!lineAdditional.isEmpty()) { trackType = lineAdditional; } trackNo = lineValue.toInt(); } else if (lineName == constIndex) { // we need the index's position field if (!lineAdditional.isEmpty()) { // if there's none "01" index, we'll just take the first one // also, we'll take the "01" index even if it's the last one if (QLatin1String("01")==lineValue || index.isEmpty()) { index = lineAdditional; } } } else if (lineName == constPerformer) { artist = lineValue; } else if (lineName == constTitle) { title = lineValue; } else if (lineName == constSongWriter) { composer = lineValue; // end of track's for the current file -> parse next one } else if (lineName == constFile) { break; } // just ignore the rest of possible field types for now... } while (!(line = textStream->readLine()).isNull()); // we didn't add the last song yet... if (validFile && !index.isEmpty() && (trackType.isEmpty() || trackType == constAudioTrackType)) { entries.append(CueEntry(file, index, trackNo, disc, title, artist, albumArtist, album, composer, albumComposer, genre, date)); } } // finalize parsing songs for(int i = 0; i < entries.length(); i++) { CueEntry entry = entries.at(i); Song song; song.file=constCueProtocol+fileName+"?pos="+QString::number(i); song.track=entry.trackNo; song.disc=entry.discNo; QString songFile=fileDir+entry.file; song.setName(songFile); // HACK!!! if (!files.contains(songFile)) { files.insert(songFile); } // the last TRACK for every FILE gets it's 'end' marker from the media file's // length if (i+1 < entries.size() && entries.at(i).file == entries.at(i+1).file) { // incorrect indices? if (!updateSong(entry, entries.at(i+1).index, song)) { continue; } } else { // incorrect index? if (!updateLastSong(entry, song)) { continue; } } songList.append(song); } return true; } cantata-2.2.0/mpd-interface/cuefile.h000066400000000000000000000025071316350454000174360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #ifndef CUEFILE_H #define CUEFILE_H #include #include struct Song; // This parser will try to detect the real encoding of a .cue file but there's // a great chance it will fail so it's probably best to assume that the parser // is UTF compatible only. namespace CueFile { extern bool isCue(const QString &str); extern QByteArray getLoadLine(const QString &str); extern bool parse(const QString &fileName, const QString &dir, QList &songList, QSet &files); } #endif // CUEFILE_H cantata-2.2.0/mpd-interface/httpstream.cpp000066400000000000000000000126711316350454000205530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "httpstream.h" #include "mpdconnection.h" #include "mpdstatus.h" #include "gui/settings.h" #ifndef LIBVLC_FOUND #include #endif #include static const int constPlayerCheckPeriod=250; static const int constMaxPlayStateChecks=2000/constPlayerCheckPeriod; #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void HttpStream::enableDebug() { debugEnabled=true; } HttpStream::HttpStream(QObject *p) : QObject(p) , enabled(false) , state(MPDState_Inactive) , playStateChecks(0) , playStateCheckTimer(0) , player(0) { } void HttpStream::setEnabled(bool e) { if (e==enabled) { return; } enabled=e; if (enabled) { connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString))); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus())); streamUrl(MPDConnection::self()->getDetails().streamUrl); } else { disconnect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString))); disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus())); if (player) { #ifdef LIBVLC_FOUND libvlc_media_player_stop(player); #else player->stop(); #endif } } } void HttpStream::streamUrl(const QString &url) { MPDStatus * const status = MPDStatus::self(); DBUG << url; #ifdef LIBVLC_FOUND if (player) { libvlc_media_player_stop(player); libvlc_media_player_release(player); libvlc_release(instance); player=0; } #else static const char *constUrlProperty="url"; if (player && player->property(constUrlProperty).toString()!=url) { player->stop(); player->deleteLater(); player=0; } #endif if (!url.isEmpty() && !player) { #ifdef LIBVLC_FOUND instance = libvlc_new(0, NULL); QByteArray byteArrayUrl = url.toUtf8(); media = libvlc_media_new_location(instance, byteArrayUrl.constData()); player = libvlc_media_player_new_from_media(media); libvlc_media_release(media); #else player=new QMediaPlayer(this); player->setMedia(QUrl(url)); player->setProperty(constUrlProperty, url); #endif } if (player) { state=0xFFFF; // Force an update... updateStatus(); } else { state=MPDState_Inactive; } } void HttpStream::updateStatus() { if (!player) { return; } MPDStatus * const status = MPDStatus::self(); DBUG << status->state() << state; if (status->state()==state) { return; } state=status->state(); switch (status->state()) { case MPDState_Playing: // Only start playback if not aready playing #ifdef LIBVLC_FOUND if (libvlc_Playing!=libvlc_media_player_get_state(player)) { libvlc_media_player_play(player); startTimer(); } #else if (QMediaPlayer::PlayingState!=player->state()) { player->play(); startTimer(); } #endif break; case MPDState_Paused: case MPDState_Inactive: case MPDState_Stopped: #ifdef LIBVLC_FOUND libvlc_media_player_stop(player); #else player->stop(); #endif stopTimer(); break; default: stopTimer(); break; } } void HttpStream::checkPlayer() { if (0==--playStateChecks) { stopTimer(); DBUG << "Max checks reached"; } #ifdef LIBVLC_FOUND if (libvlc_Playing==libvlc_media_player_get_state(player)) { DBUG << "Playing"; stopTimer(); } else { DBUG << "Try again"; libvlc_media_player_play(player); } #else if (QMediaPlayer::PlayingState==player->state()) { DBUG << "Playing"; stopTimer(); } else { DBUG << "Try again"; player->play(); } #endif } void HttpStream::startTimer() { if (!playStateCheckTimer) { playStateCheckTimer=new QTimer(this); playStateCheckTimer->setSingleShot(false); playStateCheckTimer->setInterval(constPlayerCheckPeriod); connect(playStateCheckTimer, SIGNAL(timeout()), SLOT(checkPlayer())); } playStateChecks = constMaxPlayStateChecks; DBUG << playStateChecks; playStateCheckTimer->start(); } void HttpStream::stopTimer() { if (playStateCheckTimer) { DBUG; playStateCheckTimer->stop(); } playStateChecks=0; } cantata-2.2.0/mpd-interface/httpstream.h000066400000000000000000000032131316350454000202100ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef HTTP_STREAM_H #define HTTP_STREAM_H #include #ifdef LIBVLC_FOUND #include #else #include #endif class QTimer; class HttpStream : public QObject { Q_OBJECT public: static void enableDebug(); HttpStream(QObject *p); virtual ~HttpStream() { } public Q_SLOTS: void setEnabled(bool e); private Q_SLOTS: void updateStatus(); void streamUrl(const QString &url); void checkPlayer(); private: void startTimer(); void stopTimer(); private: bool enabled; int state; int playStateChecks; QTimer *playStateCheckTimer; #ifdef LIBVLC_FOUND libvlc_instance_t *instance; libvlc_media_player_t *player; libvlc_media_t *media; #else QMediaPlayer *player; #endif }; #endif cantata-2.2.0/mpd-interface/mpd.conf.template000066400000000000000000000010041316350454000211010ustar00rootroot00000000000000music_directory "${HOME}/Music" playlist_directory "${DATA_DIR}/playlists" sticker_file "${DATA_DIR}/sticker.sql" bind_to_address "${DATA_DIR}/socket" db_file "${CACHE_DIR}/tag_cache" pid_file "${CACHE_DIR}/pid" state_file "${CACHE_DIR}/state" log_file "/dev/null" metadata_to_use "artist,album,title,track,name,genre,date,disc,albumartist,composer,musicbrainz_albumid" audio_output { type "pulse" name "Output" } mixer_type "software" audio_buffer_size "8192" filesystem_charset "UTF-8" id3v1_encoding "UTF-8" cantata-2.2.0/mpd-interface/mpdconnection.cpp000066400000000000000000002367221316350454000212250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "mpdconnection.h" #include "mpdparseutils.h" #include "models/streamsmodel.h" #ifdef ENABLE_SIMPLE_MPD_SUPPORT #include "mpduser.h" #endif #include "gui/settings.h" #include "support/globalstatic.h" #include "support/configuration.h" #include #include #include #include #include #include #include #include #include #include "support/thread.h" #include "cuefile.h" #if defined Q_OS_LINUX && defined QT_QTDBUS_FOUND #include "dbus/powermanagement.h" #elif defined Q_OS_MAC && defined IOKIT_FOUND #include "mac/powermanagement.h" #endif #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << "MPDConnection" << QThread::currentThreadId() void MPDConnection::enableDebug() { debugEnabled=true; } // Uncomment the following to report error strings in MPDStatus to the UI // ...disabled, as stickers (for ratings) can cause lots of errors to be reported - and these all need clearing, etc. // #define REPORT_MPD_ERRORS static const int constSocketCommsTimeout=2000; static const int constMaxReadAttempts=4; static const int constMaxFilesPerAddCommand=2000; static const int constConnTimer=5000; static const QByteArray constOkValue("OK"); static const QByteArray constOkMpdValue("OK MPD"); static const QByteArray constOkNlValue("OK\n"); static const QByteArray constAckValue("ACK"); static const QByteArray constIdleChangedKey("changed: "); static const QByteArray constIdleDbValue("database"); static const QByteArray constIdleUpdateValue("update"); static const QByteArray constIdleStoredPlaylistValue("stored_playlist"); static const QByteArray constIdlePlaylistValue("playlist"); static const QByteArray constIdlePlayerValue("player"); static const QByteArray constIdleMixerValue("mixer"); static const QByteArray constIdleOptionsValue("options"); static const QByteArray constIdleOutputValue("output"); static const QByteArray constIdleStickerValue("sticker"); static const QByteArray constIdleSubscriptionValue("subscription"); static const QByteArray constIdleMessageValue("message"); static const QByteArray constDynamicIn("cantata-dynamic-in"); static const QByteArray constDynamicOut("cantata-dynamic-out"); static const QByteArray constRatingSticker("rating"); static inline int socketTimeout(int dataSize) { static const int constDataBlock=256; return ((dataSize/constDataBlock)+((dataSize%constDataBlock) ? 1 : 0))*constSocketCommsTimeout; } static QByteArray log(const QByteArray &data) { if (data.length()<256) { return data; } else { return data.left(96) + "... ..." + data.right(96) + " (" + QByteArray::number(data.length()) + " bytes)"; } } GLOBAL_STATIC(MPDConnection, instance) const QString MPDConnection::constModifiedSince=QLatin1String("modified-since"); const int MPDConnection::constMaxPqChanges=1000; const QString MPDConnection::constStreamsPlayListName=QLatin1String("[Radio Streams]"); const QString MPDConnection::constPlaylistPrefix=QLatin1String("playlist:"); const QString MPDConnection::constDirPrefix=QLatin1String("dir:"); QByteArray MPDConnection::quote(int val) { return '\"'+QByteArray::number(val)+'\"'; } QByteArray MPDConnection::encodeName(const QString &name) { return '\"'+name.toUtf8().replace("\\", "\\\\").replace("\"", "\\\"")+'\"'; } static QByteArray readFromSocket(MpdSocket &socket, int timeout=constSocketCommsTimeout) { QByteArray data; int attempt=0; while (QAbstractSocket::ConnectedState==socket.state()) { while (0==socket.bytesAvailable() && QAbstractSocket::ConnectedState==socket.state()) { DBUG << (void *)(&socket) << "Waiting for read data, attempt" << attempt; if (socket.waitForReadyRead(timeout)) { break; } DBUG << (void *)(&socket) << "Wait for read failed - " << socket.errorString(); if (++attempt>=constMaxReadAttempts) { DBUG << "ERROR: Timedout waiting for response"; socket.close(); return QByteArray(); } } data.append(socket.readAll()); if (data.endsWith(constOkNlValue) || data.startsWith(constOkValue) || data.startsWith(constAckValue)) { break; } } DBUG << (void *)(&socket) << "Read:" << log(data) << ", socket state:" << socket.state(); return data; } static MPDConnection::Response readReply(MpdSocket &socket, int timeout=constSocketCommsTimeout) { QByteArray data = readFromSocket(socket, timeout); return MPDConnection::Response(data.endsWith(constOkNlValue), data); } MPDConnection::Response::Response(bool o, const QByteArray &d) : ok(o) , data(d) { } QString MPDConnection::Response::getError(const QByteArray &command) { if (ok || data.isEmpty()) { return QString(); } if (!ok && data.size()>0) { int cmdEnd=data.indexOf("} "); if (-1==cmdEnd) { return tr("Unknown")+QLatin1String(" (")+command+QLatin1Char(')'); } else { cmdEnd+=2; QString rv=data.mid(cmdEnd, data.length()-(data.endsWith('\n') ? cmdEnd+1 : cmdEnd)); if (data.contains("{listplaylists}")) { // NOTE: NOT translated, as refers to config item return QLatin1String("playlist_directory - ")+rv; } // If we are reporting a stream error, remove any stream name added by Cantata... int start=rv.indexOf(QLatin1String("http://")); if (start>0) { int pos=rv.indexOf(QChar('#'), start+6); if (-1!=pos) { rv=rv.left(pos); } } return rv; } } return data; } MPDConnectionDetails::MPDConnectionDetails() : port(6600) , dirReadable(false) { } QString MPDConnectionDetails::getName() const { #ifdef ENABLE_SIMPLE_MPD_SUPPORT return name.isEmpty() ? QObject::tr("Default") : (name==MPDUser::constName ? MPDUser::translatedName() : name); #else return name.isEmpty() ? QObject::tr("Default") : name; #endif } QString MPDConnectionDetails::description() const { if (hostname.isEmpty()) { return getName(); } else if (hostname.startsWith('/') || hostname.startsWith('~')) { return getName(); } else { return QObject::tr("\"%1\" (%2:%3)", "name (host:port)").arg(getName()).arg(hostname).arg(QString::number(port)); } } void MPDConnectionDetails::setDirReadable() { dirReadable=Utils::isDirReadable(dir); } MPDConnection::MPDConnection() : isInitialConnect(true) , thread(0) , ver(0) , canUseStickers(false) , sock(this) , idleSocket(this) , lastStatusPlayQueueVersion(0) , lastUpdatePlayQueueVersion(0) , state(State_Blank) , isListingMusic(false) , reconnectTimer(0) , reconnectStart(0) , stopAfterCurrent(false) , currentSongId(-1) , songPos(0) , unmuteVol(-1) , mopidy(false) , isUpdatingDb(false) , volumeFade(0) , fadeDuration(0) , restoreVolume(-1) { qRegisterMetaType("time_t"); qRegisterMetaType("Song"); qRegisterMetaType("Output"); qRegisterMetaType("Playlist"); qRegisterMetaType >("QList"); qRegisterMetaType >("QList"); qRegisterMetaType >("QList"); qRegisterMetaType >("QList"); qRegisterMetaType >("QList"); qRegisterMetaType >("QList"); qRegisterMetaType >("QSet"); qRegisterMetaType >("QSet"); qRegisterMetaType("QAbstractSocket::SocketState"); qRegisterMetaType("MPDStatsValues"); qRegisterMetaType("MPDStatusValues"); qRegisterMetaType("MPDConnectionDetails"); qRegisterMetaType >("QMap"); qRegisterMetaType("Stream"); qRegisterMetaType >("QList"); #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND) connect(PowerManagement::self(), SIGNAL(resuming()), this, SLOT(reconnect())); #endif MPDParseUtils::setSingleTracksFolders(Configuration().get("singleTracksFolders", QStringList()).toSet()); } MPDConnection::~MPDConnection() { if (State_Connected==state && fadingVolume()) { sendCommand("stop"); stopVolumeFade(); } // disconnect(&sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); disconnect(&idleSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); disconnect(&idleSocket, SIGNAL(readyRead()), this, SLOT(idleDataReady())); sock.disconnectFromHost(); idleSocket.disconnectFromHost(); } void MPDConnection::start() { if (!thread) { thread=new Thread(metaObject()->className()); connTimer=thread->createTimer(this); connTimer->setSingleShot(false); moveToThread(thread); connect(thread, SIGNAL(finished()), connTimer, SLOT(stop())); connect(connTimer, SIGNAL(timeout()), SLOT(getStatus())); thread->start(); } } void MPDConnection::stop() { stopPlaying(); #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (details.name==MPDUser::constName && Settings::self()->stopOnExit()) { MPDUser::self()->stop(); } #endif if (thread) { thread->deleteTimer(connTimer); connTimer=0; thread->stop(); thread=0; } } bool MPDConnection::localFilePlaybackSupported() const { return details.isLocal() || (ver>=CANTATA_MAKE_VERSION(0, 19, 0) && /*handlers.contains(QLatin1String("file")) &&*/ (QLatin1String("127.0.0.1")==details.hostname || QLatin1String("localhost")==details.hostname)); } MPDConnection::ConnectionReturn MPDConnection::connectToMPD(MpdSocket &socket, bool enableIdle) { if (QAbstractSocket::ConnectedState!=socket.state()) { DBUG << (void *)(&socket) << "Connecting" << (enableIdle ? "(idle)" : "(std)"); if (details.isEmpty()) { DBUG << "no hostname and/or port supplied."; return Failed; } socket.connectToHost(details.hostname, details.port); if (socket.waitForConnected(constSocketCommsTimeout)) { DBUG << (void *)(&socket) << "established"; QByteArray recvdata = readFromSocket(socket); if (recvdata.isEmpty()) { DBUG << (void *)(&socket) << "Couldn't connect"; return Failed; } if (recvdata.startsWith(constOkMpdValue)) { DBUG << (void *)(&socket) << "Received identification string"; } lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=0; playQueueIds.clear(); emit cantataStreams(QList(), false); int min, maj, patch; if (3==sscanf(&(recvdata.constData()[7]), "%3d.%3d.%3d", &maj, &min, &patch)) { long v=((maj&0xFF)<<16)+((min&0xFF)<<8)+(patch&0xFF); if (v!=ver) { ver=v; } } recvdata.clear(); if (!details.password.isEmpty()) { DBUG << (void *)(&socket) << "setting password..."; socket.write("password "+details.password.toUtf8()+'\n'); socket.waitForBytesWritten(constSocketCommsTimeout); if (!readReply(socket).ok) { DBUG << (void *)(&socket) << "password rejected"; socket.close(); return IncorrectPassword; } } if (enableIdle) { dynamicId.clear(); setupRemoteDynamic(); connect(&socket, SIGNAL(readyRead()), this, SLOT(idleDataReady()), Qt::QueuedConnection); connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState)), Qt::QueuedConnection); DBUG << (void *)(&socket) << "Enabling idle"; socket.write("idle\n"); socket.waitForBytesWritten(); } return Success; } else { DBUG << (void *)(&socket) << "Couldn't connect - " << socket.errorString() << socket.error(); return convertSocketCode(socket); } } // DBUG << "Already connected" << (enableIdle ? "(idle)" : "(std)"); return Success; } MPDConnection::ConnectionReturn MPDConnection::convertSocketCode(MpdSocket &socket) { switch (socket.error()) { case QAbstractSocket::ProxyAuthenticationRequiredError: case QAbstractSocket::ProxyConnectionRefusedError: case QAbstractSocket::ProxyConnectionClosedError: case QAbstractSocket::ProxyConnectionTimeoutError: case QAbstractSocket::ProxyNotFoundError: case QAbstractSocket::ProxyProtocolError: return MPDConnection::ProxyError; default: if (QNetworkProxy::DefaultProxy!=socket.proxyType() && QNetworkProxy::NoProxy!=socket.proxyType()) { return MPDConnection::ProxyError; } if (socket.errorString().contains(QLatin1String("proxy"), Qt::CaseInsensitive)) { return MPDConnection::ProxyError; } return MPDConnection::Failed; } } QString MPDConnection::errorString(ConnectionReturn status) const { switch (status) { default: case Success: return QString(); case Failed: return tr("Connection to %1 failed").arg(details.description()); case ProxyError: return tr("Connection to %1 failed - please check your proxy settings").arg(details.description()); case IncorrectPassword: return tr("Connection to %1 failed - incorrect password").arg(details.description()); } } MPDConnection::ConnectionReturn MPDConnection::connectToMPD() { connTimer->stop(); if (State_Connected==state && (QAbstractSocket::ConnectedState!=sock.state() || QAbstractSocket::ConnectedState!=idleSocket.state())) { DBUG << "Something has gone wrong with sockets, so disconnect"; disconnectFromMPD(); } #ifdef ENABLE_SIMPLE_MPD_SUPPORT int maxConnAttempts=MPDUser::constName==details.name ? 2 : 1; #else int maxConnAttempts=1; #endif ConnectionReturn status=Failed; for (int connAttempt=0; connAttemptstop(); MPDUser::self()->start(); } #endif } } connTimer->start(constConnTimer); return status; } void MPDConnection::disconnectFromMPD() { DBUG << "disconnectFromMPD"; connTimer->stop(); disconnect(&idleSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); disconnect(&idleSocket, SIGNAL(readyRead()), this, SLOT(idleDataReady())); if (QAbstractSocket::ConnectedState==sock.state()) { sock.disconnectFromHost(); } if (QAbstractSocket::ConnectedState==idleSocket.state()) { idleSocket.disconnectFromHost(); } sock.close(); idleSocket.close(); state=State_Disconnected; ver=0; playQueueIds.clear(); streamIds.clear(); lastStatusPlayQueueVersion=0; lastUpdatePlayQueueVersion=0; currentSongId=0; songPos=0; mopidy=false; isUpdatingDb=false; emit socketAddress(QString()); } // This function is mainly intended to be called after the computer (laptop) has been 'resumed' void MPDConnection::reconnect() { if (reconnectTimer && reconnectTimer->isActive()) { return; } qWarning() << reconnectStart; if (0==reconnectStart && isConnected()) { disconnectFromMPD(); } if (isConnected()) { // Perhaps the user pressed a button which caused the reconnect??? reconnectStart=0; return; } qWarning() << "TRY TO CONNECT"; time_t now=time(NULL); ConnectionReturn status=connectToMPD(); switch (status) { case Success: // Issue #1041 - MPD does not seem to persist user/client made replaygain changes, so use the values read from Cantata's config. if (replaygainSupported() && !details.replayGain.isEmpty()) { sendCommand("replay_gain_mode "+details.replayGain.toLatin1()); } getStatus(); getStats(); getUrlHandlers(); getTagTypes(); getStickerSupport(); playListInfo(); outputs(); reconnectStart=0; determineIfaceIp(); emit stateChanged(true); break; case Failed: if (0==reconnectStart || std::abs(now-reconnectStart)<15) { if (0==reconnectStart) { reconnectStart=now; } if (!reconnectTimer) { reconnectTimer=new QTimer(this); reconnectTimer->setSingleShot(true); connect(reconnectTimer, SIGNAL(timeout()), this, SLOT(reconnect()), Qt::QueuedConnection); } if (std::abs(now-reconnectStart)>1) { emit info(tr("Connecting to %1").arg(details.description())); } reconnectTimer->start(500); } else { emit stateChanged(false); emit error(errorString(Failed), true); reconnectStart=0; } break; default: emit stateChanged(false); emit error(errorString(status), true); reconnectStart=0; break; } } void MPDConnection::setDetails(const MPDConnectionDetails &d) { // Can't change connection whilst listing music collection... if (isListingMusic) { emit connectionNotChanged(details.name); return; } #ifdef ENABLE_SIMPLE_MPD_SUPPORT bool isUser=d.name==MPDUser::constName; const MPDConnectionDetails &det=isUser ? MPDUser::self()->details(true) : d; #else const MPDConnectionDetails &det=d; #endif bool changedDir=det.dir!=details.dir; bool diffName=det.name!=details.name; bool diffDetails=det!=details; #ifdef ENABLE_HTTP_STREAM_PLAYBACK bool diffStreamUrl=det.streamUrl!=details.streamUrl; #endif details=det; // Issue #1041 - If this is a user MPD, then the call to MPDUser::self()->details() will clear the replayGain setting // We can safely use that of the passed in details. details.replayGain=d.replayGain; if (diffDetails || State_Connected!=state) { emit connectionChanged(details); DBUG << "setDetails" << details.hostname << details.port << (details.password.isEmpty() ? false : true); if (State_Connected==state && fadingVolume()) { sendCommand("stop"); stopVolumeFade(); } disconnectFromMPD(); DBUG << "call connectToMPD"; unmuteVol=-1; toggleStopAfterCurrent(false); mopidy=false; #ifdef ENABLE_SIMPLE_MPD_SUPPORT if (isUser) { MPDUser::self()->start(); } #endif if (isUpdatingDb) { isUpdatingDb=false; emit updatedDatabase(); // Stop any spinners... } ConnectionReturn status=connectToMPD(); switch (status) { case Success: // Issue #1041 - MPD does not seem to persist user/client made replaygain changes, so use the values read from Cantata's config. if (replaygainSupported() && !details.replayGain.isEmpty()) { sendCommand("replay_gain_mode "+details.replayGain.toLatin1()); } getStatus(); getStats(); getUrlHandlers(); getTagTypes(); getStickerSupport(); playListInfo(); outputs(); determineIfaceIp(); emit stateChanged(true); break; default: emit stateChanged(false); emit error(errorString(status), true); if (isInitialConnect) { reconnect(); } } } else if (diffName) { emit stateChanged(true); } #ifdef ENABLE_HTTP_STREAM_PLAYBACK if (diffStreamUrl) { emit streamUrl(details.streamUrl); } #endif if (changedDir) { emit dirChanged(); } isInitialConnect = false; } //void MPDConnection::disconnectMpd() //{ // if (State_Connected==state) { // disconnectFromMPD(); // emit stateChanged(false); // } //} MPDConnection::Response MPDConnection::sendCommand(const QByteArray &command, bool emitErrors, bool retry) { connTimer->stop(); static bool reconnected=false; // If we reconnect, and send playlistinfo - dont want that call causing reconnects, and recursion! DBUG << (void *)(&sock) << "sendCommand:" << log(command) << emitErrors << retry; if (!isConnected()) { if ("stop"!=command) { emit error(tr("Failed to send command to %1 - not connected").arg(details.description()), true); } return Response(false); } if (QAbstractSocket::ConnectedState!=sock.state() && !reconnected) { DBUG << (void *)(&sock) << "Socket (state:" << sock.state() << ") need to reconnect"; sock.close(); ConnectionReturn status=connectToMPD(); if (Success!=status) { disconnectFromMPD(); emit stateChanged(false); emit error(errorString(status), true); return Response(false); } else { // Refresh playqueue... reconnected=true; playListInfo(); getStatus(); reconnected=false; } } Response response; if (-1==sock.write(command+'\n')) { DBUG << "Failed to write"; // If we fail to write, dont wait for bytes to be written!! response=Response(false); sock.close(); } else { int timeout=socketTimeout(command.length()); DBUG << "Timeout (ms):" << timeout; sock.waitForBytesWritten(timeout); DBUG << "Socket state after write:" << (int)sock.state(); response=readReply(sock, timeout); } if (!response.ok) { DBUG << log(command) << "failed"; if (response.data.isEmpty() && retry && QAbstractSocket::ConnectedState!=sock.state() && !reconnected) { // Try one more time... // This scenario, where socket seems to be closed during/after 'write' seems to occur more often // when dynamizer is running. However, simply reconnecting seems to resolve the issue. return sendCommand(command, emitErrors, false); } clearError(); if (emitErrors) { bool emitError=true; // Mopidy returns "incorrect arguments" for commands it does not support. The docs state that crossfade and replaygain mode // setting commands are not supported. So, if we get this error then just ignore it. if (mopidy && (command.startsWith("crossfade ") || command.startsWith("replay_gain_mode "))) { emitError=false; } if (emitError) { if ((command.startsWith("add ") || command.startsWith("command_list_begin\nadd ")) && -1!=command.indexOf("\"file:///")) { if (details.isLocal() && -1!=response.data.indexOf("Permission denied")) { emit error(tr("Failed to load. Please check user \"mpd\" has read permission.")); } else if (!details.isLocal() && -1!=response.data.indexOf("Access denied")) { emit error(tr("Failed to load. MPD can only play local files if connected via a local socket.")); } else if (!response.getError(command).isEmpty()) { emit error(tr("MPD reported the following error: %1").arg(response.getError(command))); } else { disconnectFromMPD(); emit stateChanged(false); emit error(tr("Failed to send command. Disconnected from %1").arg(details.description()), true); } } else if (!response.getError(command).isEmpty()) { emit error(tr("MPD reported the following error: %1").arg(response.getError(command))); } /*else if ("listallinfo"==command && ver>=CANTATA_MAKE_VERSION(0,18,0)) { disconnectFromMPD(); emit stateChanged(false); emit error(tr("Failed to load library. Please increase \"max_output_buffer_size\" in MPD's config file.")); } */ else { disconnectFromMPD(); emit stateChanged(false); emit error(tr("Failed to send command. Disconnected from %1").arg(details.description()), true); } } } } DBUG << (void *)(&sock) << "sendCommand - sent"; if (QAbstractSocket::ConnectedState==sock.state()) { connTimer->start(constConnTimer); } else { connTimer->stop(); } return response; } /* * Playlist commands */ bool MPDConnection::isPlaylist(const QString &file) { return file.endsWith(QLatin1String(".asx"), Qt::CaseInsensitive) || file.endsWith(QLatin1String(".cue"), Qt::CaseInsensitive) || file.endsWith(QLatin1String(".m3u"), Qt::CaseInsensitive) || file.endsWith(QLatin1String(".pls"), Qt::CaseInsensitive) || file.endsWith(QLatin1String(".xspf"), Qt::CaseInsensitive); } void MPDConnection::add(const QStringList &files, int action, quint8 priority, bool decreasePriority) { add(files, 0, 0, action, priority, decreasePriority); } void MPDConnection::add(const QStringList &files, quint32 pos, quint32 size, int action, quint8 priority, bool decreasePriority) { QList prioList; if (priority>0) { prioList << priority; } add(files, pos, size, action, prioList, decreasePriority); } void MPDConnection::add(const QStringList &origList, quint32 pos, quint32 size, int action, const QList &priority) { add(origList, pos, size, action, priority, false); } void MPDConnection::add(const QStringList &origList, quint32 pos, quint32 size, int action, QList priority, bool decreasePriority) { quint32 playPos=0; if (0==pos && 0==size && (AddAfterCurrent==action || AppendAndPlay==action || AddAndPlay==action)) { Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); if (AppendAndPlay==action) { playPos=sv.playlistLength; } else if (AddAndPlay==action) { pos=0; size=sv.playlistLength; } else { pos=sv.song+1; size=sv.playlistLength; } } } toggleStopAfterCurrent(false); if (Replace==action || ReplaceAndplay==action) { clear(); getStatus(); } QStringList files; foreach (const QString &file, origList) { if (file.startsWith(constDirPrefix)) { files+=getAllFiles(file.mid(constDirPrefix.length())); } else if (file.startsWith(constPlaylistPrefix)) { files+=getPlaylistFiles(file.mid(constPlaylistPrefix.length())); } else { files.append(file); } } QList fileLists; if (priority.count()<=1 && files.count()>constMaxFilesPerAddCommand) { int numChunks=(files.count()/constMaxFilesPerAddCommand)+(files.count()%constMaxFilesPerAddCommand ? 1 : 0); for (int i=0; i1) { prio--; } } } bool usePrio=!priority.isEmpty() && canUsePriority() && (1==priority.count() || priority.count()==files.count()); quint8 singlePrio=usePrio && 1==priority.count() ? priority.at(0) : 0; QStringList cStreamFiles; bool sentOk=false; if (usePrio && Append==action && 0==curPos) { curPos=playQueueIds.size(); } foreach (const QStringList &files, fileLists) { QByteArray send = "command_list_begin\n"; for (int i = 0; i < files.size(); i++) { QString fileName=files.at(i); if (fileName.startsWith(QLatin1String("http://")) && fileName.contains(QLatin1String("cantata=song"))) { cStreamFiles.append(fileName); } if (CueFile::isCue(fileName)) { send += "load "+CueFile::getLoadLine(fileName)+'\n'; } else { if (isPlaylist(fileName)) { send+="load "; havePlaylist=true; } else { // addedFile=true; send += "add "; } send += encodeName(fileName)+'\n'; } if (!havePlaylist) { if (0!=size) { send += "move "+quote(curSize)+" "+quote(curPos)+'\n'; } if (usePrio && !havePlaylist) { send += "prio "+quote(singlePrio || i>=priority.count() ? singlePrio : priority.at(i))+" "+quote(curPos)+" "+quote(curPos)+'\n'; } } curSize++; curPos++; } send += "command_list_end"; sentOk=sendCommand(send).ok; if (!sentOk) { break; } } if (sentOk) { if (!cStreamFiles.isEmpty()) { emit cantataStreams(cStreamFiles); } if ((ReplaceAndplay==action || AddAndPlay==action) /*&& addedFile */&& !files.isEmpty()) { // Dont emit error if play fails, might be that playlist was not loaded... playFirstTrack(false); } if (AppendAndPlay==action) { startPlayingSong(playPos); } emit added(files); } } void MPDConnection::populate(const QStringList &files, const QList &priority) { add(files, 0, 0, Replace, priority); } void MPDConnection::addAndPlay(const QString &file) { toggleStopAfterCurrent(false); Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); QByteArray send = "command_list_begin\n"; send+="add "+encodeName(file)+'\n'; send+="play "+quote(sv.playlistLength)+'\n'; send+="command_list_end"; sendCommand(send); } } void MPDConnection::clear() { toggleStopAfterCurrent(false); if (sendCommand("clear").ok) { lastUpdatePlayQueueVersion=0; playQueueIds.clear(); emit cantataStreams(QList(), false); } } void MPDConnection::removeSongs(const QList &items) { toggleStopAfterCurrent(false); QByteArray send = "command_list_begin\n"; foreach (qint32 i, items) { send += "deleteid "+quote(i)+'\n'; } send += "command_list_end"; sendCommand(send); } void MPDConnection::move(quint32 from, quint32 to) { toggleStopAfterCurrent(false); sendCommand("move "+quote(from)+' '+quote(to)); } void MPDConnection::move(const QList &items, quint32 pos, quint32 size) { doMoveInPlaylist(QString(), items, pos, size); #if 0 QByteArray send = "command_list_begin\n"; QList moveItems; moveItems.append(items); qSort(moveItems); int posOffset = 0; //first move all items (starting with the biggest) to the end so we don't have to deal with changing rownums for (int i = moveItems.size() - 1; i >= 0; i--) { if (moveItems.at(i) < pos && moveItems.at(i) != size - 1) { // we are moving away an item that resides before the destinatino row, manipulate destination row posOffset++; } send += "move "; send += quote(moveItems.at(i)); send += " "; send += quote(size - 1); send += '\n'; } //now move all of them to the destination position for (int i = moveItems.size() - 1; i >= 0; i--) { send += "move "; send += quote(size - 1 - i); send += " "; send += quote(pos - posOffset); send += '\n'; } send += "command_list_end"; sendCommand(send); #endif } void MPDConnection::setOrder(const QList &items) { QByteArray cmd("move "); QByteArray send; QList positions; quint32 numChanges=0; for (qint32 i=0; iconstMaxPqChanges) { lastUpdatePlayQueueVersion=0; } } } void MPDConnection::shuffle() { toggleStopAfterCurrent(false); sendCommand("shuffle"); } void MPDConnection::shuffle(quint32 from, quint32 to) { toggleStopAfterCurrent(false); sendCommand("shuffle "+quote(from)+':'+quote(to+1)); } void MPDConnection::currentSong() { Response response=sendCommand("currentsong"); if (response.ok) { emit currentSongUpdated(MPDParseUtils::parseSong(response.data, MPDParseUtils::Loc_PlayQueue)); } } /* * Call "plchangesposid" to recieve a list of positions+ids that have been changed since the last update. * If we have ids in this list that we don't know about, then these are new songs - so we call * "playlistinfo " to get the song information. * * Any songs that are know about, will actually be sent with empty data - as the playqueue model will * already hold these songs. */ void MPDConnection::playListChanges() { DBUG << "playListChanges" << lastUpdatePlayQueueVersion << playQueueIds.size(); if (0==lastUpdatePlayQueueVersion || 0==playQueueIds.size()) { playListInfo(); return; } QByteArray data = "plchangesposid "+quote(lastUpdatePlayQueueVersion); Response status=sendCommand("status"); // We need an updated status so as to detect deletes at end of list... Response response=sendCommand(data, false); if (response.ok && status.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(status.data); lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=sv.playlist; emitStatusUpdated(sv); QList changes=MPDParseUtils::parseChanges(response.data); if (!changes.isEmpty()) { if (changes.count()>constMaxPqChanges) { playListInfo(); return; } bool first=true; quint32 firstPos=0; QList songs; QList newCantataStreams; QList ids; QSet prevIds=playQueueIds.toSet(); QSet strmIds; foreach (const MPDParseUtils::IdPos &idp, changes) { if (first) { first=false; firstPos=idp.pos; if (idp.pos!=0) { for (quint32 i=0; i=(unsigned int)playQueueIds.count()) { // Just for safety... playListInfo(); return; } s.id=playQueueIds.at(i); songs.append(s); ids.append(s.id); if (streamIds.contains(s.id)) { strmIds.insert(s.id); } } } } if (prevIds.contains(idp.id) && !streamIds.contains(idp.id)) { Song s; s.id=idp.id; // s.pos=idp.pos; songs.append(s); } else { // New song! data = "playlistinfo "; data += quote(idp.pos); response=sendCommand(data); if (!response.ok) { playListInfo(); return; } Song s=MPDParseUtils::parseSong(response.data, MPDParseUtils::Loc_PlayQueue); s.id=idp.id; // s.pos=idp.pos; songs.append(s); if (s.isCdda()) { newCantataStreams.append(s); } else if (s.isStream()) { if (s.isCantataStream()) { newCantataStreams.append(s); } else { strmIds.insert(s.id); } } } ids.append(idp.id); } // Dont think this section is ever called, but leave here to be safe!!! // For some reason if we have 10 songs in our playlist and we move pos 2 to pos 1, MPD sends all ids from pos 1 onwards if (firstPos+changes.size()<=sv.playlistLength && (sv.playlistLength<=(unsigned int)playQueueIds.length())) { for (quint32 i=firstPos+changes.size(); i removed=prevIds-playQueueIds.toSet(); if (!removed.isEmpty()) { emit removedIds(removed); } emit playlistUpdated(songs, false); if (songs.isEmpty()) { stopVolumeFade(); } return; } } playListInfo(); } void MPDConnection::playListInfo() { Response response=sendCommand("playlistinfo"); QList songs; if (response.ok) { lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion; songs=MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_PlayQueue); playQueueIds.clear(); streamIds.clear(); QList cStreams; foreach (const Song &s, songs) { playQueueIds.append(s.id); if (s.isCdda()) { cStreams.append(s); } else if (s.isStream()) { if (s.isCantataStream()) { cStreams.append(s); } else { streamIds.insert(s.id); } } } emit cantataStreams(cStreams, false); if (songs.isEmpty()) { stopVolumeFade(); } Response status=sendCommand("status"); if (status.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(status.data); lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=sv.playlist; emitStatusUpdated(sv); } } emit playlistUpdated(songs, true); } /* * Playback commands */ void MPDConnection::setCrossFade(int secs) { sendCommand("crossfade "+quote(secs)); } void MPDConnection::setReplayGain(const QString &v) { if (replaygainSupported()) { // Issue #1041 - MPD does not seem to persist user/client made replaygain changes, so store in Cantata's config file. Settings::self()->saveReplayGain(details.name, v); sendCommand("replay_gain_mode "+v.toLatin1()); } } void MPDConnection::getReplayGain() { if (replaygainSupported()) { QStringList lines=QString(sendCommand("replay_gain_status").data).split('\n', QString::SkipEmptyParts); if (2==lines.count() && "OK"==lines[1] && lines[0].startsWith(QLatin1String("replay_gain_mode: "))) { QString mode=lines[0].mid(18); // Issue #1041 - MPD does not seem to persist user/client made replaygain changes, so store in Cantata's config file. Settings::self()->saveReplayGain(details.name, mode); emit replayGain(mode); } else { emit replayGain(QString()); } } } void MPDConnection::goToNext() { toggleStopAfterCurrent(false); stopVolumeFade(); sendCommand("next"); } static inline QByteArray value(bool b) { return MPDConnection::quote(b ? 1 : 0); } void MPDConnection::setPause(bool toggle) { toggleStopAfterCurrent(false); stopVolumeFade(); sendCommand("pause "+value(toggle)); } void MPDConnection::play() { playFirstTrack(true); } void MPDConnection::playFirstTrack(bool emitErrors) { toggleStopAfterCurrent(false); stopVolumeFade(); sendCommand("play 0", emitErrors); } void MPDConnection::seek() { QObject *s=sender(); int offset=s ? s->property("offset").toInt() : 0; if (0==offset) { return; } toggleStopAfterCurrent(false); Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); if (-1==sv.songId) { return; } if (offset>0) { if (sv.timeElapsed+offset=0) { setSeek(sv.song, sv.timeElapsed+offset); } else { // Not sure about this!!! /*goToPrevious();*/ setSeek(sv.song, 0); } } } } void MPDConnection::startPlayingSong(quint32 song) { toggleStopAfterCurrent(false); sendCommand("play "+quote(song)); } void MPDConnection::startPlayingSongId(qint32 songId) { toggleStopAfterCurrent(false); stopVolumeFade(); sendCommand("playid "+quote(songId)); } void MPDConnection::goToPrevious() { toggleStopAfterCurrent(false); stopVolumeFade(); sendCommand("previous"); } void MPDConnection::setConsume(bool toggle) { sendCommand("consume "+value(toggle)); } void MPDConnection::setRandom(bool toggle) { sendCommand("random "+value(toggle)); } void MPDConnection::setRepeat(bool toggle) { sendCommand("repeat "+value(toggle)); } void MPDConnection::setSingle(bool toggle) { sendCommand("single "+value(toggle)); } void MPDConnection::setSeek(quint32 song, quint32 time) { sendCommand("seek "+quote(song)+' '+quote(time)); } void MPDConnection::setSeekId(qint32 songId, quint32 time) { if (-1==songId) { songId=currentSongId; } if (-1==songId) { return; } if (songId!=currentSongId || 0==time) { toggleStopAfterCurrent(false); } if (sendCommand("seekid "+quote(songId)+' '+quote(time)).ok) { if (stopAfterCurrent && songId==currentSongId && songPos>time) { songPos=time; } } } void MPDConnection::setVolume(int vol) //Range accepted by MPD: 0-100 { if (-1==vol) { if (restoreVolume>=0) { sendCommand("setvol "+quote(restoreVolume), false); } if (volumeFade) { sendCommand("stop"); } restoreVolume=-1; } else if (vol>=0) { unmuteVol=-1; sendCommand("setvol "+quote(vol), false); } } void MPDConnection::toggleMute() { if (unmuteVol>0) { sendCommand("setvol "+quote(unmuteVol), false); unmuteVol=-1; } else { Response status=sendCommand("status"); if (status.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(status.data); if (sv.volume>0) { unmuteVol=sv.volume; sendCommand("setvol "+quote(0), false); } } } } void MPDConnection::stopPlaying(bool afterCurrent) { toggleStopAfterCurrent(afterCurrent); if (!stopAfterCurrent) { if (!startVolumeFade()) { sendCommand("stop"); } } } void MPDConnection::clearStopAfter() { toggleStopAfterCurrent(false); } void MPDConnection::getStats() { Response response=sendCommand("stats"); if (response.ok) { MPDStatsValues stats=MPDParseUtils::parseStats(response.data); dbUpdate=stats.dbUpdate; mopidy=0==stats.artists && 0==stats.albums && 0==stats.songs && 0==stats.uptime && 0==stats.playtime && 0==stats.dbPlaytime && 0==dbUpdate; if (mopidy) { // Set version to 1 so that SQL cache is updated - it uses 0 as intial value dbUpdate=stats.dbUpdate=1; } emit statsUpdated(stats); } } void MPDConnection::getStatus() { Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); lastStatusPlayQueueVersion=sv.playlist; if (currentSongId!=sv.songId) { stopVolumeFade(); } if (stopAfterCurrent && (currentSongId!=sv.songId || (songPos>0 && sv.timeElapsed<(qint32)songPos))) { stopVolumeFade(); if (sendCommand("stop").ok) { sv.state=MPDState_Stopped; } toggleStopAfterCurrent(false); } currentSongId=sv.songId; if (!isUpdatingDb && -1!=sv.updatingDb) { isUpdatingDb=true; emit updatingDatabase(); } else if (isUpdatingDb && -1==sv.updatingDb) { isUpdatingDb=false; emit updatedDatabase(); } emitStatusUpdated(sv); // If playlist length does not match number of IDs, then refresh if (sv.playlistLength!=playQueueIds.length()) { playListInfo(); } } } void MPDConnection::getUrlHandlers() { Response response=sendCommand("urlhandlers"); if (response.ok) { handlers=MPDParseUtils::parseList(response.data, QByteArray("handler: ")).toSet(); DBUG << handlers; } } void MPDConnection::getTagTypes() { Response response=sendCommand("tagtypes"); if (response.ok) { tagTypes=MPDParseUtils::parseList(response.data, QByteArray("tagtype: ")).toSet(); } } /* * Data is written during idle. * Retrieve it and parse it */ void MPDConnection::idleDataReady() { DBUG << "idleDataReady"; if (0==idleSocket.bytesAvailable()) { return; } parseIdleReturn(readFromSocket(idleSocket)); } /* * Socket state has changed, currently we only use this to gracefully * handle disconnects. */ void MPDConnection::onSocketStateChanged(QAbstractSocket::SocketState socketState) { if (socketState == QAbstractSocket::ClosingState){ bool wasConnected=State_Connected==state; disconnect(&idleSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); DBUG << "onSocketStateChanged"; if (QAbstractSocket::ConnectedState==idleSocket.state()) { idleSocket.disconnectFromHost(); } idleSocket.close(); ConnectionReturn status=Success; if (wasConnected && Success!=(status=connectToMPD(idleSocket, true))) { // Failed to connect idle socket - so close *both* disconnectFromMPD(); emit stateChanged(false); emit error(errorString(status), true); } if (QAbstractSocket::ConnectedState==idleSocket.state()) { connect(&idleSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState)), Qt::QueuedConnection); } } } /* * Parse data returned by the mpd deamon on an idle commond. */ void MPDConnection::parseIdleReturn(const QByteArray &data) { DBUG << "parseIdleReturn:" << data; Response response(data.endsWith(constOkNlValue), data); if (!response.ok) { DBUG << "idle failed? reconnect"; disconnect(&idleSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); if (QAbstractSocket::ConnectedState==idleSocket.state()) { idleSocket.disconnectFromHost(); } idleSocket.close(); ConnectionReturn status=connectToMPD(idleSocket, true); if (Success!=status) { // Failed to connect idle socket - so close *both* disconnectFromMPD(); emit stateChanged(false); emit error(errorString(status), true); } return; } QList lines = data.split('\n'); /* * See http://www.musicpd.org/doc/protocol/ch02.html */ bool playListUpdated=false; bool statusUpdated=false; foreach(const QByteArray &line, lines) { if (line.startsWith(constIdleChangedKey)) { QByteArray value=line.mid(constIdleChangedKey.length()); if (constIdleDbValue==value) { getStats(); getStatus(); playListInfo(); playListUpdated=true; } else if (constIdleUpdateValue==value) { getStats(); getStatus(); } else if (constIdleStoredPlaylistValue==value) { listPlaylists(); listStreams(); } else if (constIdlePlaylistValue==value) { if (!playListUpdated) { playListChanges(); } } else if (!statusUpdated && (constIdlePlayerValue==value || constIdleMixerValue==value || constIdleOptionsValue==value)) { getStatus(); getReplayGain(); statusUpdated=true; } else if (constIdleOutputValue==value) { outputs(); } else if (constIdleStickerValue==value) { emit stickerDbChanged(); } else if (constIdleSubscriptionValue==value) { //if (dynamicId.isEmpty()) { setupRemoteDynamic(); //} } else if (constIdleMessageValue==value) { readRemoteDynamicMessages(); } } } DBUG << (void *)(&idleSocket) << "write idle"; idleSocket.write("idle\n"); idleSocket.waitForBytesWritten(); } void MPDConnection::outputs() { Response response=sendCommand("outputs"); if (response.ok) { emit outputsUpdated(MPDParseUtils::parseOuputs(response.data)); } } void MPDConnection::enableOutput(int id, bool enable) { if (sendCommand((enable ? "enableoutput " : "disableoutput ")+quote(id)).ok) { outputs(); } } /* * Admin commands */ void MPDConnection::update() { if (mopidy) { // Mopidy does not support MPD's update command. So, when user presses update DB, what we // just reload the library. loadLibrary(); } else { sendCommand("update"); } } /* * Database commands */ void MPDConnection::loadLibrary() { DBUG << "loadLibrary"; isListingMusic=true; emit updatingLibrary(dbUpdate); QList songs; recursivelyListDir("/", songs); emit updatedLibrary(); isListingMusic=false; } void MPDConnection::listFolder(const QString &folder) { DBUG << "listFolder" << folder; bool topLevel="/"==folder || ""==folder; Response response=sendCommand(topLevel ? "lsinfo" : ("lsinfo "+encodeName(folder))); QStringList subFolders; QList songs; if (response.ok) { MPDParseUtils::parseDirItems(response.data, QString(), ver, songs, folder, subFolders, MPDParseUtils::Loc_Browse); } emit folderContents(folder, subFolders, songs); } /* * Playlists commands */ // void MPDConnection::listPlaylist(const QString &name) // { // QByteArray data = "listplaylist "; // data += encodeName(name); // sendCommand(data); // } void MPDConnection::listPlaylists() { Response response=sendCommand("listplaylists"); if (response.ok) { QList playlists=MPDParseUtils::parsePlaylists(response.data); playlists.removeAll((Playlist(constStreamsPlayListName))); emit playlistsRetrieved(playlists); } } void MPDConnection::playlistInfo(const QString &name) { Response response=sendCommand("listplaylistinfo "+encodeName(name)); if (response.ok) { emit playlistInfoRetrieved(name, MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_Playlists)); } } void MPDConnection::loadPlaylist(const QString &name, bool replace) { if (replace) { clear(); getStatus(); } if (sendCommand("load "+encodeName(name)).ok) { if (replace) { playFirstTrack(false); } emit playlistLoaded(name); } } void MPDConnection::renamePlaylist(const QString oldName, const QString newName) { if (sendCommand("rename "+encodeName(oldName)+' '+encodeName(newName), false).ok) { emit playlistRenamed(oldName, newName); } else { emit error(tr("Failed to rename %1 to %2").arg(oldName).arg(newName)); } } void MPDConnection::removePlaylist(const QString &name) { sendCommand("rm "+encodeName(name)); } void MPDConnection::savePlaylist(const QString &name) { if (!sendCommand("save "+encodeName(name), false).ok) { emit error(tr("Failed to save %1").arg(name)); } } void MPDConnection::addToPlaylist(const QString &name, const QStringList &songs, quint32 pos, quint32 size) { if (songs.isEmpty()) { return; } if (!name.isEmpty()) { foreach (const QString &song, songs) { if (CueFile::isCue(song)) { emit error(tr("You cannot add parts of a cue sheet to a playlist!")+QLatin1String(" (")+song+QLatin1Char(')')); return; } else if (isPlaylist(song)) { emit error(tr("You cannot add a playlist to another playlist!")+QLatin1String(" (")+song+QLatin1Char(')')); return; } } } QStringList files; foreach (const QString &file, songs) { if (file.startsWith(constDirPrefix)) { files+=getAllFiles(file.mid(constDirPrefix.length())); } else if (file.startsWith(constPlaylistPrefix)) { files+=getPlaylistFiles(file.mid(constPlaylistPrefix.length())); } else { files.append(file); } } QByteArray encodedName=encodeName(name); QByteArray send = "command_list_begin\n"; foreach (const QString &s, files) { send += "playlistadd "+encodedName+" "+encodeName(s)+'\n'; } send += "command_list_end"; if (sendCommand(send).ok) { if (size>0) { QList items; for(int i=0; i &positions) { if (positions.isEmpty()) { return; } QByteArray encodedName=encodeName(name); QList sorted=positions; QList removed; qSort(sorted); for (int i=sorted.count()-1; i>=0; --i) { quint32 idx=sorted.at(i); QByteArray data = "playlistdelete "; data += encodedName; data += " "; data += quote(idx); if (sendCommand(data).ok) { removed.prepend(idx); } else { break; } } if (removed.count()) { emit removedFromPlaylist(name, removed); } // playlistInfo(name); } void MPDConnection::setPriority(const QList &ids, quint8 priority, bool decreasePriority) { if (canUsePriority()) { QMap tracks; QByteArray send = "command_list_begin\n"; foreach (quint32 id, ids) { tracks.insert(id, priority); send += "prioid "+quote(priority)+" "+quote(id)+'\n'; if (decreasePriority && priority>0) { priority--; } } send += "command_list_end"; if (sendCommand(send).ok) { emit prioritySet(tracks); } } } void MPDConnection::search(const QString &field, const QString &value, int id) { QList songs; QByteArray cmd; if (field==constModifiedSince) { time_t v=0; if (QRegExp("\\d*").exactMatch(value)) { v=QDateTime(QDateTime::currentDateTime().date()).toTime_t()-(value.toInt()*24*60*60); } else if (QRegExp("^((19|20)\\d\\d)[-/](0[1-9]|1[012])[-/](0[1-9]|[12][0-9]|3[01])$").exactMatch(value)) { QDateTime dt=QDateTime::fromString(QString(value).replace("/", "-"), Qt::ISODate); if (dt.isValid()) { v=dt.toTime_t(); } } if (v>0) { cmd="find "+field.toLatin1()+" "+quote(v); } } else { cmd="search "+field.toLatin1()+" "+encodeName(value); } if (!cmd.isEmpty()) { Response response=sendCommand(cmd); if (response.ok) { songs=MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_Search); qSort(songs); } } emit searchResponse(id, songs); } void MPDConnection::search(const QByteArray &query, const QString &id) { QList songs; if (query.isEmpty()) { Response response=sendCommand("list albumartist", false, false); if (response.ok) { QList lines = response.data.split('\n'); foreach (const QByteArray &line, lines) { if (line.startsWith("AlbumArtist: ")) { Response resp = sendCommand("find albumartist " + encodeName(QString::fromUtf8(line.mid(13))) , false, false); if (resp.ok) { songs += MPDParseUtils::parseSongs(resp.data, MPDParseUtils::Loc_Search); } } } } } else if (query.startsWith("RATING:")) { QList parts = query.split(':'); if (3==parts.length()) { Response response=sendCommand("sticker find song \"\" rating", false, false); if (response.ok) { int min = parts.at(1).toInt(); int max = parts.at(2).toInt(); QList stickers=MPDParseUtils::parseStickers(response.data, constRatingSticker); if (!stickers.isEmpty()) { foreach (const MPDParseUtils::Sticker &sticker, stickers) { if (!sticker.file.isEmpty() && !sticker.value.isEmpty()) { int val = sticker.value.toInt(); if (val>=min && val<=max) { Response resp = sendCommand("find file " + encodeName(QString::fromUtf8(sticker.file)) , false, false); if (resp.ok) { songs.append(MPDParseUtils::parseSong(response.data, MPDParseUtils::Loc_Search)); } } } } } } } } else { Response response=sendCommand(query); if (response.ok) { songs=MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_Search); } } emit searchResponse(id, songs); } void MPDConnection::listStreams() { Response response=sendCommand("listplaylistinfo "+encodeName(constStreamsPlayListName), false); QList streams; if (response.ok) { QList songs=MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_Streams); foreach (const Song &song, songs) { streams.append(Stream(song.file, song.name())); } } clearError(); emit streamList(streams); } void MPDConnection::saveStream(const QString &url, const QString &name) { if (sendCommand("playlistadd "+encodeName(constStreamsPlayListName)+" "+encodeName(MPDParseUtils::addStreamName(url, name))).ok) { emit savedStream(url, name); } } void MPDConnection::removeStreams(const QList &positions) { if (positions.isEmpty()) { return; } QByteArray encodedName=encodeName(constStreamsPlayListName); QList sorted=positions; QList removed; qSort(sorted); for (int i=sorted.count()-1; i>=0; --i) { quint32 idx=sorted.at(i); QByteArray data = "playlistdelete "; data += encodedName; data += " "; data += quote(idx); if (sendCommand(data).ok) { removed.prepend(idx); } else { break; } } emit removedStreams(removed); } void MPDConnection::editStream(const QString &url, const QString &name, quint32 position) { QByteArray encodedName=encodeName(constStreamsPlayListName); if (sendCommand("playlistdelete "+encodedName+" "+quote(position)).ok && sendCommand("playlistadd "+encodedName+" "+encodeName(MPDParseUtils::addStreamName(url, name))).ok) { // emit editedStream(url, name, position); listStreams(); } } void MPDConnection::sendClientMessage(const QString &channel, const QString &msg, const QString &clientName) { if (!sendCommand("sendmessage "+channel.toUtf8()+" "+msg.toUtf8(), false).ok) { emit error(tr("Failed to send '%1' to %2. Please check %2 is registered with MPD.").arg(msg).arg(clientName.isEmpty() ? channel : clientName)); emit clientMessageFailed(channel, msg); } } void MPDConnection::sendDynamicMessage(const QStringList &msg) { // Check whether cantata-dynamic is still alive, by seeing if its channel is still open... if (1==msg.count() && QLatin1String("ping")==msg.at(0)) { Response response=sendCommand("channels"); if (!response.ok || !MPDParseUtils::parseList(response.data, QByteArray("channel: ")).toSet().contains(constDynamicIn)) { emit dynamicSupport(false); } return; } QByteArray data; foreach (QString part, msg) { if (data.isEmpty()) { data+='\"'+part.toUtf8()+':'+dynamicId; } else { part=part.replace('\"', "{q}"); part=part.replace("{", "{ob}"); part=part.replace("}", "{cb}"); part=part.replace("\n", "{n}"); part=part.replace(":", "{c}"); data+=':'+part.toUtf8(); } } data+='\"'; if (!sendCommand("sendmessage "+constDynamicIn+" "+data).ok) { emit dynamicSupport(false); } } void MPDConnection::moveInPlaylist(const QString &name, const QList &items, quint32 pos, quint32 size) { if (doMoveInPlaylist(name, items, pos, size)) { emit movedInPlaylist(name, items, pos); } // playlistInfo(name); } bool MPDConnection::doMoveInPlaylist(const QString &name, const QList &items, quint32 pos, quint32 size) { if (name.isEmpty()) { toggleStopAfterCurrent(false); } QByteArray cmd = name.isEmpty() ? "move " : ("playlistmove "+encodeName(name)+" "); QByteArray send = "command_list_begin\n"; QList moveItems=items; int posOffset = 0; qSort(moveItems); //first move all items (starting with the biggest) to the end so we don't have to deal with changing rownums for (int i = moveItems.size() - 1; i >= 0; i--) { if (moveItems.at(i) < pos && moveItems.at(i) != size - 1) { // we are moving away an item that resides before the destination row, manipulate destination row posOffset++; } send += cmd; send += quote(moveItems.at(i)); send += " "; send += quote(size - 1); send += '\n'; } //now move all of them to the destination position for (int i = moveItems.size() - 1; i >= 0; i--) { send += cmd; send += quote(size - 1 - i); send += " "; send += quote(pos - posOffset); send += '\n'; } send += "command_list_end"; return sendCommand(send).ok; } void MPDConnection::toggleStopAfterCurrent(bool afterCurrent) { if (afterCurrent!=stopAfterCurrent) { stopAfterCurrent=afterCurrent; songPos=0; if (stopAfterCurrent && 1==playQueueIds.count()) { Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); songPos=sv.timeElapsed; } } emit stopAfterCurrentChanged(stopAfterCurrent); } } bool MPDConnection::recursivelyListDir(const QString &dir, QList &songs) { bool topLevel="/"==dir || ""==dir; if (topLevel && !mopidy) { // UPnP database backend does not list separate metadata items, so if "list genre" returns // empty response assume this is a UPnP backend and dont attempt to get rest of data... // Although we dont use "list XXX", lsinfo will return duplciate items (due to the way most // UPnP servers returing directories of classifications - Genre/Album/Tracks, Artist/Album/Tracks, // etc... Response response=sendCommand("list genre", false, false); if (!response.ok || response.data.split('\n').length()<3) { // 2 lines - OK and blank // ..just to be 100% sure, check no artists either... response=sendCommand("list artist", false, false); if (!response.ok || response.data.split('\n').length()<3) { // 2 lines - OK and blank return false; } } } Response response=sendCommand(topLevel ? QByteArray(mopidy ? "lsinfo \"Local media\"" : "lsinfo") : ("lsinfo "+encodeName(dir))); if (response.ok) { QStringList subDirs; MPDParseUtils::parseDirItems(response.data, details.dir, ver, songs, dir, subDirs, MPDParseUtils::Loc_Library); if (songs.count()>=200){ QCoreApplication::processEvents(); QList *copy=new QList(); *copy << songs; emit librarySongs(copy); songs.clear(); } foreach (const QString &sub, subDirs) { if (!recursivelyListDir(sub, songs)) { return false; } } if (topLevel && !songs.isEmpty()) { QList *copy=new QList(); *copy << songs; emit librarySongs(copy); } return true; } else { return false; } } QStringList MPDConnection::getPlaylistFiles(const QString &name) { QStringList files; Response response=sendCommand("listplaylistinfo "+encodeName(name)); if (response.ok) { QList songs=MPDParseUtils::parseSongs(response.data, MPDParseUtils::Loc_Playlists); emit playlistInfoRetrieved(name, songs); foreach (const Song &s, songs) { files.append(s.file); } } return files; } QStringList MPDConnection::getAllFiles(const QString &dir) { QStringList files; Response response=sendCommand("lsinfo "+encodeName(dir)); if (response.ok) { QStringList subDirs; QList songs; MPDParseUtils::parseDirItems(response.data, details.dir, ver, songs, dir, subDirs, MPDParseUtils::Loc_Browse); foreach (const Song &song, songs) { if (Song::Playlist!=song.type) { files.append(song.file); } } foreach (const QString &sub, subDirs) { files+=getAllFiles(sub); } } return files; } bool MPDConnection::checkRemoteDynamicSupport() { if (ver>=CANTATA_MAKE_VERSION(0,17,0)) { Response response; if (-1!=idleSocket.write("channels\n")) { idleSocket.waitForBytesWritten(constSocketCommsTimeout); response=readReply(idleSocket); if (response.ok) { return MPDParseUtils::parseList(response.data, QByteArray("channel: ")).toSet().contains(constDynamicIn); } } } return false; } bool MPDConnection::subscribe(const QByteArray &channel) { if (-1!=idleSocket.write("subscribe \""+channel+"\"\n")) { idleSocket.waitForBytesWritten(constSocketCommsTimeout); Response response=readReply(idleSocket); if (response.ok || response.data.startsWith("ACK [56@0]")) { // ACK => already subscribed... DBUG << "Created subscription to " << channel; return true; } else { DBUG << "Failed to subscribe to " << channel; } } else { DBUG << "Failed to create subscribe to " << channel; } return false; } void MPDConnection::setupRemoteDynamic() { if (checkRemoteDynamicSupport()) { DBUG << "cantata-dynamic is running"; if (subscribe(constDynamicOut)) { if (dynamicId.isEmpty()) { dynamicId=QHostInfo::localHostName().toLatin1()+'.'+QHostInfo::localDomainName().toLatin1()+'-'+QByteArray::number(QCoreApplication::applicationPid()); if (!subscribe(constDynamicOut+'-'+dynamicId)) { dynamicId.clear(); } } } } else { DBUG << "remote dynamic is not supported"; } emit dynamicSupport(!dynamicId.isEmpty()); } void MPDConnection::readRemoteDynamicMessages() { if (-1!=idleSocket.write("readmessages\n")) { idleSocket.waitForBytesWritten(constSocketCommsTimeout); Response response=readReply(idleSocket); if (response.ok) { MPDParseUtils::MessageMap messages=MPDParseUtils::parseMessages(response.data); if (!messages.isEmpty()) { QList channels=QList() << constDynamicOut << constDynamicOut+'-'+dynamicId; foreach (const QByteArray &channel, channels) { if (messages.contains(channel)) { foreach (const QString &m, messages[channel]) { if (!m.isEmpty()) { DBUG << "Received message " << m; QStringList parts=m.split(':', QString::SkipEmptyParts); QStringList message; foreach (QString part, parts) { part=part.replace("{c}", ":"); part=part.replace("{n}", "\n"); part=part.replace("{cb}", "}"); part=part.replace("{ob}", "{"); part=part.replace("{q}", "\""); message.append(part); } emit dynamicResponse(message); } } } } } } } } int MPDConnection::getVolume() { Response response=sendCommand("status"); if (response.ok) { MPDStatusValues sv=MPDParseUtils::parseStatus(response.data); return sv.volume; } return -1; } void MPDConnection::setRating(const QString &file, quint8 val) { if (val>Song::Rating_Max) { return; } if (!canUseStickers) { emit error(tr("Cannot store ratings, as the 'sticker' MPD command is not supported.")); return; } bool ok=0==val ? sendCommand("sticker delete song "+encodeName(file)+' '+constRatingSticker, 0!=val).ok : sendCommand("sticker set song "+encodeName(file)+' '+constRatingSticker+' '+quote(val)).ok; if (!ok && 0==val) { clearError(); } if (ok) { emit rating(file, val); } else { getRating(file); } } void MPDConnection::setRating(const QStringList &files, quint8 val) { if (1==files.count()) { setRating(files.at(0), val); return; } if (!canUseStickers) { emit error(tr("Cannot store ratings, as the 'sticker' MPD command is not supported.")); return; } QList fileLists; if (files.count()>constMaxFilesPerAddCommand) { int numChunks=(files.count()/constMaxFilesPerAddCommand)+(files.count()%constMaxFilesPerAddCommand ? 1 : 0); for (int i=0; iSong::Rating_Max) { r=0; } } emit rating(file, r); } void MPDConnection::getStickerSupport() { Response response=sendCommand("commands"); canUseStickers=response.ok && MPDParseUtils::parseList(response.data, QByteArray("command: ")).toSet().contains("sticker"); } bool MPDConnection::fadingVolume() { return volumeFade && QPropertyAnimation::Running==volumeFade->state(); } bool MPDConnection::startVolumeFade() { if (fadeDuration<=MinFade) { return false; } restoreVolume=getVolume(); if (restoreVolume<5) { return false; } if (!volumeFade) { volumeFade = new QPropertyAnimation(this, "volume"); volumeFade->setDuration(fadeDuration); } if (QPropertyAnimation::Running!=volumeFade->state()) { volumeFade->setStartValue(restoreVolume); volumeFade->setEndValue(-1); volumeFade->start(); } return true; } void MPDConnection::stopVolumeFade() { if (fadingVolume()) { volumeFade->stop(); setVolume(restoreVolume); restoreVolume=-1; } } void MPDConnection::emitStatusUpdated(MPDStatusValues &v) { if (restoreVolume>=0) { v.volume=restoreVolume; } #ifndef REPORT_MPD_ERRORS v.error=QString(); #endif emit statusUpdated(v); if (!v.error.isEmpty()) { clearError(); } } void MPDConnection::clearError() { #ifdef REPORT_MPD_ERRORS if (isConnected()) { DBUG << __FUNCTION__; if (-1!=sock.write("clearerror\n")) { sock.waitForBytesWritten(500); readReply(sock); } } #endif } void MPDConnection::determineIfaceIp() { static const QLatin1String ip4Local("127.0.0.1"); if (!details.isLocal() && !details.hostname.isEmpty() && ip4Local!=details.hostname && QLatin1String("localhost")!=details.hostname) { QUdpSocket testSocket(this); testSocket.connectToHost(details.hostname, 1, QIODevice::ReadOnly); QString addr=testSocket.localAddress().toString(); testSocket.close(); if (!addr.isEmpty()) { DBUG << addr; emit ifaceIp(addr); return; } } DBUG << ip4Local; emit ifaceIp(ip4Local); } MpdSocket::MpdSocket(QObject *parent) : QObject(parent) , tcp(0) , local(0) { } MpdSocket::~MpdSocket() { deleteTcp(); deleteLocal(); } void MpdSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode mode) { // qWarning() << "connectToHost" << hostName << port; if (hostName.startsWith('/') || hostName.startsWith('~')) { deleteTcp(); if (!local) { local = new QLocalSocket(this); connect(local, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(localStateChanged(QLocalSocket::LocalSocketState))); connect(local, SIGNAL(readyRead()), this, SIGNAL(readyRead())); } // qWarning() << "Connecting to LOCAL socket"; local->connectToServer(Utils::tildaToHome(hostName), mode); } else { deleteLocal(); if (!tcp) { tcp = new QTcpSocket(this); connect(tcp, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SIGNAL(stateChanged(QAbstractSocket::SocketState))); connect(tcp, SIGNAL(readyRead()), this, SIGNAL(readyRead())); } // qWarning() << "Connecting to TCP socket"; tcp->connectToHost(hostName, port, mode); } } void MpdSocket::localStateChanged(QLocalSocket::LocalSocketState state) { emit stateChanged((QAbstractSocket::SocketState)state); } void MpdSocket::deleteTcp() { if (tcp) { disconnect(tcp, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SIGNAL(stateChanged(QAbstractSocket::SocketState))); disconnect(tcp, SIGNAL(readyRead()), this, SIGNAL(readyRead())); tcp->deleteLater(); tcp=0; } } void MpdSocket::deleteLocal() { if (local) { disconnect(local, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(localStateChanged(QLocalSocket::LocalSocketState))); disconnect(local, SIGNAL(readyRead()), this, SIGNAL(readyRead())); local->deleteLater(); local=0; } } cantata-2.2.0/mpd-interface/mpdconnection.h000066400000000000000000000400541316350454000206610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MPDCONNECTION_H #define MPDCONNECTION_H #include #include #include #include #include #include #include "mpdstats.h" #include "mpdstatus.h" #include "song.h" #include "output.h" #include "playlist.h" #include "stream.h" #include "config.h" #include class QTimer; class Thread; class QPropertyAnimation; class MpdSocket : public QObject { Q_OBJECT public: MpdSocket(QObject *parent); virtual ~MpdSocket(); void connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode mode = QIODevice::ReadWrite); void disconnectFromHost() { if (tcp) { tcp->disconnectFromHost(); } else if(local) { local->disconnectFromServer(); } } void close() { if (tcp) { tcp->close(); } else if(local) { local->close(); } } int write(const QByteArray &data) { if (tcp) { return tcp->write(data); } else if(local) { return local->write(data); } return 0; } void waitForBytesWritten(int msecs = 30000) { if (tcp) { tcp->waitForBytesWritten(msecs); } else if(local) { local->waitForBytesWritten(msecs); } } bool waitForReadyRead(int msecs = 30000) { return tcp ? tcp->waitForReadyRead(msecs) : local ? local->waitForReadyRead(msecs) : false; } bool waitForConnected(int msecs = 30000) { return tcp ? tcp->waitForConnected(msecs) : local ? local->waitForConnected(msecs) : false; } qint64 bytesAvailable() { return tcp ? tcp->bytesAvailable() : local ? local->bytesAvailable() : 0; } QByteArray readAll() { return tcp ? tcp->readAll() : local ? local->readAll() : QByteArray(); } QAbstractSocket::SocketState state() const { return tcp ? tcp->state() : local ? (QAbstractSocket::SocketState)local->state() : QAbstractSocket::UnconnectedState; } QNetworkProxy::ProxyType proxyType() const { return tcp ? tcp->proxy().type() : QNetworkProxy::NoProxy; } bool isLocal() const { return 0!=local; } QString address() const { return tcp ? tcp->peerAddress().toString() : QString(); } QString errorString() const { return tcp ? tcp->errorString() : local ? local->errorString() : QLatin1String("No socket object?"); } QAbstractSocket::SocketError error() const { return tcp ? tcp->error() : local ? (QAbstractSocket::SocketError)local->error() : QAbstractSocket::UnknownSocketError; } Q_SIGNALS: void stateChanged(QAbstractSocket::SocketState state); void readyRead(); private Q_SLOTS: void localStateChanged(QLocalSocket::LocalSocketState state); private: void deleteTcp(); void deleteLocal(); private: QTcpSocket *tcp; QLocalSocket *local; }; struct MPDConnectionDetails { MPDConnectionDetails(); QString getName() const; QString description() const; bool isLocal() const { return hostname.startsWith('/'); } bool isEmpty() const { return hostname.isEmpty() || (!isLocal() && 0==port); } bool operator==(const MPDConnectionDetails &o) const { return hostname==o.hostname && isLocal()==o.isLocal() && (isLocal() || port==o.port) && password==o.password; } bool operator!=(const MPDConnectionDetails &o) const { return !(*this==o); } bool operator<(const MPDConnectionDetails &o) const { return name.localeAwareCompare(o.name)<0; } static QString configGroupName(const QString &n=QString()) { return n.isEmpty() ? "Connection" : ("Connection-"+n); } void setDirReadable(); QString name; QString hostname; quint16 port; QString password; QString dir; bool dirReadable; QString coverName; #ifdef ENABLE_HTTP_STREAM_PLAYBACK QString streamUrl; #endif QString replayGain; }; class MPDConnection : public QObject { Q_OBJECT Q_PROPERTY(int volume READ getVolume WRITE setVolume) public: enum AddAction { Append, Replace, ReplaceAndplay, AppendAndPlay, AddAndPlay, AddAfterCurrent }; enum VolumeFade { MinFade = 400, MaxFade = 4000, DefaultFade = MinFade // disable volume fade by default. prev:2000 }; static const QString constModifiedSince; static const int constMaxPqChanges; static const QString constStreamsPlayListName; static const QString constPlaylistPrefix; static const QString constDirPrefix; static MPDConnection * self(); static QByteArray quote(int val); static QByteArray encodeName(const QString &name); struct Response { Response(bool o=true, const QByteArray &d=QByteArray()); QString getError(const QByteArray &command); bool ok; QByteArray data; }; static void enableDebug(); MPDConnection(); ~MPDConnection(); void start(); const MPDConnectionDetails & getDetails() const { return details; } void setDirReadable() { details.setDirReadable(); } bool isConnected() const { return State_Connected==state; } bool canUsePriority() const { return ver>=CANTATA_MAKE_VERSION(0, 17, 0) && !mopidy; } const QSet & urlHandlers() const { return handlers; } const QSet & tags() const { return tagTypes; } bool composerTagSupported() const { return tagTypes.contains(QLatin1String("Composer")); } bool commentTagSupported() const { return tagTypes.contains(QLatin1String("Comment")); } bool performerTagSupported() const { return tagTypes.contains(QLatin1String("Performer")); } bool originalDateTagSupported() const { return tagTypes.contains(QLatin1String("OriginalDate")); } bool modifiedFindSupported() const { return ver>=CANTATA_MAKE_VERSION(0, 19, 0); } bool replaygainSupported() const { return ver>=CANTATA_MAKE_VERSION(0, 16, 0); } bool localFilePlaybackSupported() const; bool stickersSupported() const { return canUseStickers; } long version() const { return ver; } static bool isPlaylist(const QString &file); int unmuteVolume() { return unmuteVol; } bool isMuted() { return -1!=unmuteVol; } bool isMopidy() const { return mopidy; } void setVolumeFadeDuration(int f) { fadeDuration=f; } QString ipAddress() const { return details.isLocal() ? QString() : sock.address(); } public Q_SLOTS: void stop(); void reconnect(); void setDetails(const MPDConnectionDetails &d); // void disconnectMpd(); // Current Playlist void add(const QStringList &files, int action, quint8 priority, bool decreasePriority); void add(const QStringList &files, quint32 pos, quint32 size, int action, quint8 priority, bool decreasePriority); void add(const QStringList &files, quint32 pos, quint32 size, int action, const QList &priority); void add(const QStringList &files, quint32 pos, quint32 size, int action, QList priority, bool decreasePriority); void populate(const QStringList &files, const QList &priority); void addAndPlay(const QString &file); void currentSong(); void playListChanges(); void playListInfo(); void removeSongs(const QList &items); void move(quint32 from, quint32 to); void move(const QList &items, quint32 pos, quint32 size); void setOrder(const QList &items); void shuffle(quint32 from, quint32 to); void clear(); void shuffle(); // Playback void setCrossFade(int secs); void setReplayGain(const QString &v); void getReplayGain(); void goToNext(); void setPause(bool toggle); void play(); void startPlayingSong(quint32 song = 0); void startPlayingSongId(qint32 songId = 0); void goToPrevious(); void setConsume(bool toggle); void setRandom(bool toggle); void setRepeat(bool toggle); void setSingle(bool toggle); void setSeek(quint32 song, quint32 time); void setSeekId(qint32 songId, quint32 time); void setVolume(int vol); void toggleMute(); void stopPlaying(bool afterCurrent=false); void clearStopAfter(); // Output void outputs(); void enableOutput(int id, bool enable); // Miscellaneous void getStats(); void getStatus(); void getUrlHandlers(); void getTagTypes(); // Database void loadLibrary(); void listFolder(const QString &folder); // Admin void update(); // Playlists // void listPlaylist(const QString &name); void listPlaylists(); void playlistInfo(const QString &name); void loadPlaylist(const QString &name, bool replace); void renamePlaylist(const QString oldName, const QString newName); void removePlaylist(const QString &name); void savePlaylist(const QString &name); void addToPlaylist(const QString &name, const QStringList &songs) { addToPlaylist(name, songs, 0, 0); } void addToPlaylist(const QString &name, const QStringList &songs, quint32 pos, quint32 size); void removeFromPlaylist(const QString &name, const QList &positions); void moveInPlaylist(const QString &name, const QList &items, quint32 row, quint32 size); void setPriority(const QList &ids, quint8 priority, bool decreasePriority); void search(const QString &field, const QString &value, int id); void search(const QByteArray &query, const QString &id); void listStreams(); void saveStream(const QString &url, const QString &name); void removeStreams(const QList &positions); void editStream(const QString &url, const QString &name, quint32 position); void sendClientMessage(const QString &channel, const QString &msg, const QString &clientName); void sendDynamicMessage(const QStringList &msg); int getVolume(); void setRating(const QString &file, quint8 val); void setRating(const QStringList &files, quint8 val); void getRating(const QString &file); void seek(); Q_SIGNALS: void connectionChanged(const MPDConnectionDetails &details); void connectionNotChanged(const QString &name); void stateChanged(bool connected); void passwordError(); void currentSongUpdated(const Song &song); void playlistUpdated(const QList &songs, bool isComplete); void statsUpdated(const MPDStatsValues &stats); void statusUpdated(const MPDStatusValues &status); void outputsUpdated(const QList &outputs); void librarySongs(QList *songs); void folderContents(const QString &folder, const QStringList &subFolders, const QList &songs); void playlistsRetrieved(const QList &data); void playlistInfoRetrieved(const QString &name, const QList &songs); void playlistRenamed(const QString &from, const QString &to); void removedFromPlaylist(const QString &name, const QList &positions); void movedInPlaylist(const QString &name, const QList &items, quint32 pos); void updatingDatabase(); void updatedDatabase(); void playlistLoaded(const QString &playlist); void added(const QStringList &files); void replayGain(const QString &); void updatingLibrary(time_t dbUpdate); void updatedLibrary(); void updatingFileList(); void updatedFileList(); void error(const QString &err, bool showActions=false); void info(const QString &msg); void dirChanged(); void prioritySet(const QMap &tracks); void stopAfterCurrentChanged(bool afterCurrent); void streamUrl(const QString &url); void searchResponse(int id, const QList &songs); void searchResponse(const QString &id, const QList &songs); void socketAddress(const QString &addr); void cantataStreams(const QStringList &files); void cantataStreams(const QList &songs, bool isUpdate); void removedIds(const QSet &ids); void savedStream(const QString &url, const QString &name); void removedStreams(const QList &removed); void editedStream(const QString &url, const QString &name, quint32 position); void streamList(const QList &streams); void clientMessageFailed(const QString &client, const QString &msg); void dynamicSupport(bool e); void dynamicResponse(const QStringList &resp); void rating(const QString &file, quint8 val); void stickerDbChanged(); void ifaceIp(const QString &addr); private Q_SLOTS: void idleDataReady(); void onSocketStateChanged(QAbstractSocket::SocketState socketState); private: enum ConnectionReturn { Success, Failed, ProxyError, IncorrectPassword }; static ConnectionReturn convertSocketCode(MpdSocket &socket); QString errorString(ConnectionReturn status) const; ConnectionReturn connectToMPD(); void disconnectFromMPD(); ConnectionReturn connectToMPD(MpdSocket &socket, bool enableIdle=false); Response sendCommand(const QByteArray &command, bool emitErrors=true, bool retry=true); void initialize(); void parseIdleReturn(const QByteArray &data); bool doMoveInPlaylist(const QString &name, const QList &items, quint32 pos, quint32 size); void toggleStopAfterCurrent(bool afterCurrent); bool recursivelyListDir(const QString &dir, QList &songs); QStringList getPlaylistFiles(const QString &name); QStringList getAllFiles(const QString &dir); bool checkRemoteDynamicSupport(); bool subscribe(const QByteArray &channel); void setupRemoteDynamic(); void readRemoteDynamicMessages(); bool fadingVolume(); bool startVolumeFade(); void stopVolumeFade(); void emitStatusUpdated(MPDStatusValues &v); void clearError(); void getRatings(QList &songs); void getStickerSupport(); void playFirstTrack(bool emitErrors); void determineIfaceIp(); private: bool isInitialConnect; Thread *thread; long ver; QSet handlers; QSet tagTypes; bool canUseStickers; MPDConnectionDetails details; time_t dbUpdate; // Use 2 sockets, 1 for commands and 1 to receive MPD idle events. // Cant use 1, as we could write a command just as an idle event is ready to read MpdSocket sock; MpdSocket idleSocket; QTimer *connTimer; QByteArray dynamicId; // The three items are used so that we can do quick playqueue updates... QList playQueueIds; QSet streamIds; quint32 lastStatusPlayQueueVersion; quint32 lastUpdatePlayQueueVersion; enum State { State_Blank, State_Connected, State_Disconnected }; State state; bool isListingMusic; QTimer *reconnectTimer; time_t reconnectStart; bool stopAfterCurrent; qint32 currentSongId; quint32 songPos; // USe for stop-after-current when we only have 1 songin playqueue! int unmuteVol; bool mopidy; bool isUpdatingDb; QPropertyAnimation *volumeFade; int fadeDuration; int restoreVolume; }; #endif cantata-2.2.0/mpd-interface/mpdparseutils.cpp000066400000000000000000001051161316350454000212510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include #include #include #include #include #include "online/onlineservice.h" #include "online/podcastservice.h" #include "mpdparseutils.h" #include "mpdstatus.h" #include "mpdstats.h" #include "playlist.h" #include "song.h" #include "output.h" #ifdef ENABLE_HTTP_SERVER #include "http/httpserver.h" #endif #include "support/utils.h" #include "cuefile.h" #include "mpdconnection.h" #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << "MPDParseUtils" void MPDParseUtils::enableDebug() { debugEnabled=true; } static const QByteArray constTimeKey("Time: "); static const QByteArray constAlbumKey("Album: "); static const QByteArray constArtistKey("Artist: "); static const QByteArray constAlbumArtistKey("AlbumArtist: "); static const QByteArray constAlbumSortKey("AlbumSort: "); static const QByteArray constArtistSortKey("ArtistSort: "); static const QByteArray constAlbumArtistSortKey("AlbumArtistSort: "); static const QByteArray constComposerKey("Composer: "); static const QByteArray constPerformerKey("Performer: "); static const QByteArray constCommentKey("Comment: "); static const QByteArray constTitleKey("Title: "); static const QByteArray constTrackKey("Track: "); static const QByteArray constIdKey("Id: "); static const QByteArray constDiscKey("Disc: "); static const QByteArray constDateKey("Date: "); static const QByteArray constOriginalDateKey("OriginalDate: "); static const QByteArray constGenreKey("Genre: "); static const QByteArray constNameKey("Name: "); static const QByteArray constPriorityKey("Prio: "); static const QByteArray constAlbumId("MUSICBRAINZ_ALBUMID: "); static const QByteArray constFileKey("file: "); static const QByteArray constPlaylistKey("playlist: "); static const QByteArray constDirectoryKey("directory: "); static const QByteArray constOutputIdKey("outputid: "); static const QByteArray constOutputNameKey("outputname: "); static const QByteArray constOutputEnabledKey("outputenabled: "); static const QByteArray constChangePosKey("cpos"); static const QByteArray constChangeIdKey("Id"); static const QByteArray constLastModifiedKey("Last-Modified: "); static const QByteArray constStatsArtistsKey("artists: "); static const QByteArray constStatsAlbumsKey("albums: "); static const QByteArray constStatsSongsKey("songs: "); static const QByteArray constStatsUptimeKey("uptime: "); static const QByteArray constStatsPlaytimeKey("playtime: "); static const QByteArray constStatsDbPlaytimeKey("db_playtime: "); static const QByteArray constStatsDbUpdateKey("db_update: "); static const QByteArray constStatusVolumeKey("volume: "); static const QByteArray constStatusConsumeKey("consume: "); static const QByteArray constStatusRepeatKey("repeat: "); static const QByteArray constStatusSingleKey("single: "); static const QByteArray constStatusRandomKey("random: "); static const QByteArray constStatusPlaylistKey("playlist: "); static const QByteArray constStatusPlaylistLengthKey("playlistlength: "); static const QByteArray constStatusCrossfadeKey("xfade: "); static const QByteArray constStatusStateKey("state: "); static const QByteArray constStatusSongKey("song: "); static const QByteArray constStatusSongIdKey("songid: "); static const QByteArray constStatusNextSongKey("nextsong: "); static const QByteArray constStatusNextSongIdKey("nextsongid: "); static const QByteArray constStatusTimeKey("time: "); static const QByteArray constStatusBitrateKey("bitrate: "); static const QByteArray constStatusAudioKey("audio: "); static const QByteArray constStatusUpdatingDbKey("updating_db: "); static const QByteArray constStatusErrorKey("error: "); static const QByteArray constChannel("channel: "); static const QByteArray constMessage("message: "); static const QByteArray constOkValue("OK"); static const QByteArray constSetValue("1"); static const QByteArray constPlayValue("play"); static const QByteArray constStopValue("stop"); static const QString constProtocol=QLatin1String("://"); static const QString constHttpProtocol=QLatin1String("http://"); static inline bool toBool(const QByteArray &v) { return v==constSetValue; } static QSet singleTracksFolders; static MPDParseUtils::CueSupport cueSupport=MPDParseUtils::Cue_Parse; MPDParseUtils::CueSupport MPDParseUtils::toCueSupport(const QString &str) { for (int i=0; i &folders) { singleTracksFolders=folders; } QList MPDParseUtils::parsePlaylists(const QByteArray &data) { QList playlists; QList lines = data.split('\n'); int amountOfLines = lines.size(); for (int i = 0; i < amountOfLines; i++) { const QByteArray &line=lines.at(i); if (line.startsWith(constPlaylistKey)) { Playlist playlist; playlist.name = QString::fromUtf8(line.mid(constPlaylistKey.length())); i++; if (i < amountOfLines) { const QByteArray &line=lines.at(i); if (line.startsWith(constLastModifiedKey)) { playlist.lastModified=QDateTime::fromString(QString::fromUtf8(line.mid(constLastModifiedKey.length())), Qt::ISODate); playlists.append(playlist); } } } } return playlists; } MPDStatsValues MPDParseUtils::parseStats(const QByteArray &data) { MPDStatsValues v; QList lines = data.split('\n'); foreach (const QByteArray &line, lines) { if (line.startsWith(constStatsArtistsKey)) { v.artists=line.mid(constStatsArtistsKey.length()).toUInt(); } else if (line.startsWith(constStatsAlbumsKey)) { v.albums=line.mid(constStatsAlbumsKey.length()).toUInt(); } else if (line.startsWith(constStatsSongsKey)) { v.songs=line.mid(constStatsSongsKey.length()).toUInt(); } else if (line.startsWith(constStatsUptimeKey)) { v.uptime=line.mid(constStatsUptimeKey.length()).toUInt(); } else if (line.startsWith(constStatsPlaytimeKey)) { v.playtime=line.mid(constStatsPlaytimeKey.length()).toUInt(); } else if (line.startsWith(constStatsDbPlaytimeKey)) { v.dbPlaytime=line.mid(constStatsDbPlaytimeKey.length()).toUInt(); } else if (line.startsWith(constStatsDbUpdateKey)) { v.dbUpdate=line.mid(constStatsDbUpdateKey.length()).toUInt(); } } return v; } MPDStatusValues MPDParseUtils::parseStatus(const QByteArray &data) { MPDStatusValues v; QList lines = data.split('\n'); foreach (const QByteArray &line, lines) { if (line.startsWith(constStatusVolumeKey)) { v.volume=line.mid(constStatusVolumeKey.length()).toInt(); } else if (line.startsWith(constStatusConsumeKey)) { v.consume=toBool(line.mid(constStatusConsumeKey.length())); } else if (line.startsWith(constStatusRepeatKey)) { v.repeat=toBool(line.mid(constStatusRepeatKey.length())); } else if (line.startsWith(constStatusSingleKey)) { v.single=toBool(line.mid(constStatusSingleKey.length())); } else if (line.startsWith(constStatusRandomKey)) { v.random=toBool(line.mid(constStatusRandomKey.length())); } else if (line.startsWith(constStatusPlaylistKey)) { v.playlist=line.mid(constStatusPlaylistKey.length()).toUInt(); } else if (line.startsWith(constStatusPlaylistLengthKey)) { v.playlistLength=line.mid(constStatusPlaylistLengthKey.length()).toInt(); } else if (line.startsWith(constStatusCrossfadeKey)) { v.crossFade=line.mid(constStatusCrossfadeKey.length()).toInt(); } else if (line.startsWith(constStatusStateKey)) { QByteArray value=line.mid(constStatusStateKey.length()); if (constPlayValue==value) { v.state=MPDState_Playing; } else if (constStopValue==value) { v.state=MPDState_Stopped; } else { v.state=MPDState_Paused; } } else if (line.startsWith(constStatusSongKey)) { v.song=line.mid(constStatusSongKey.length()).toInt(); } else if (line.startsWith(constStatusSongIdKey)) { v.songId=line.mid(constStatusSongIdKey.length()).toInt(); } else if (line.startsWith(constStatusNextSongKey)) { v.nextSong=line.mid(constStatusNextSongKey.length()).toInt(); } else if (line.startsWith(constStatusNextSongIdKey)) { v.nextSongId=line.mid(constStatusNextSongIdKey.length()).toInt(); } else if (line.startsWith(constStatusTimeKey)) { QList values=line.mid(constStatusTimeKey.length()).split(':'); if (values.length()>1) { v.timeElapsed=values.at(0).toInt(); v.timeTotal=values.at(1).toInt(); } } else if (line.startsWith(constStatusBitrateKey)) { v.bitrate=line.mid(constStatusBitrateKey.length()).toUInt(); } else if (line.startsWith(constStatusAudioKey)) { QList values=line.mid(constStatusAudioKey.length()).split(':'); if (3==values.length()) { v.samplerate=values.at(0).toUInt(); v.bits=values.at(1).toUInt(); v.channels=values.at(2).toUInt(); } } else if (line.startsWith(constStatusUpdatingDbKey)) { v.updatingDb=line.mid(constStatusUpdatingDbKey.length()).toInt(); } else if (line.startsWith(constStatusErrorKey)) { v.error=QString::fromUtf8(line.mid(constStatusErrorKey.length())); // If we are reporting a stream error, remove any stream name added by Cantata... int start=v.error.indexOf(constHttpProtocol); if (start>0) { int end=v.error.indexOf(QChar('\"'), start+6); int pos=v.error.indexOf(QChar('#'), start+6); if (pos>start && pos constStdProtocols=QSet() << constHttpProtocol << QLatin1String("https://") << QLatin1String("mms://") << QLatin1String("mmsh://") << QLatin1String("mmst://") << QLatin1String("mmsu://") << QLatin1String("gopher://") << QLatin1String("rtp://") << QLatin1String("rtsp://") << QLatin1String("rtmp://") << QLatin1String("rtmpt://") << QLatin1String("rtmps://"); Song MPDParseUtils::parseSong(const QList &lines, Location location) { Song song; foreach (const QByteArray &line, lines) { if (line.startsWith(constFileKey)) { song.file = QString::fromUtf8(line.mid(constFileKey.length())); } else if (line.startsWith(constTimeKey) ){ song.time = line.mid(constTimeKey.length()).toUInt(); } else if (line.startsWith(constAlbumKey)) { song.album = QString::fromUtf8(line.mid(constAlbumKey.length())); } else if (line.startsWith(constArtistKey)) { song.artist = QString::fromUtf8(line.mid(constArtistKey.length())); } else if (line.startsWith(constAlbumArtistKey)) { song.albumartist = QString::fromUtf8(line.mid(constAlbumArtistKey.length())); } else if (line.startsWith(constComposerKey)) { song.setComposer(QString::fromUtf8(line.mid(constComposerKey.length()))); } else if (line.startsWith(constTitleKey)) { song.title =QString::fromUtf8(line.mid(constTitleKey.length())); } else if (line.startsWith(constTrackKey)) { int v=line.mid(constTrackKey.length()).split('/').at(0).toInt(); song.track = v<0 ? 0 : v; } else if (Loc_Library!=location && Loc_Search!=location && line.startsWith(constIdKey)) { song.id = line.mid(constIdKey.length()).toUInt(); } else if (line.startsWith(constDiscKey)) { int v = line.mid(constDiscKey.length()).split('/').at(0).toInt(); song.disc = v<0 ? 0 : v; } else if (line.startsWith(constDateKey)) { QByteArray value=line.mid(constDateKey.length()); int v=value.length()>4 ? value.left(4).toUInt() : value.toUInt(); song.year=v<0 ? 0 : v; } else if (line.startsWith(constOriginalDateKey)) { QByteArray value=line.mid(constOriginalDateKey.length()); int v=value.length()>4 ? value.left(4).toUInt() : value.toUInt(); song.origYear=v<0 ? 0 : v; } else if (line.startsWith(constGenreKey)) { song.addGenre(QString::fromUtf8(line.mid(constGenreKey.length()))); } else if (line.startsWith(constNameKey)) { song.setName(QString::fromUtf8(line.mid(constNameKey.length()))); } else if (line.startsWith(constPlaylistKey)) { song.file = QString::fromUtf8(line.mid(constPlaylistKey.length())); song.title=Utils::getFile(song.file); song.type=Song::Playlist; } else if (line.startsWith(constAlbumId)) { song.setMbAlbumId(line.mid(constAlbumId.length())); } else if ((Loc_Search==location || Loc_Library==location) && line.startsWith(constLastModifiedKey)) { song.lastModified=QDateTime::fromString(QString::fromUtf8(line.mid(constLastModifiedKey.length())), Qt::ISODate).toTime_t(); } else if ((Loc_Search==location || Loc_Playlists==location || Loc_PlayQueue==location) && line.startsWith(constPerformerKey)) { if (song.hasPerformer()) { song.setPerformer(song.performer()+QLatin1String(", ")+QString::fromUtf8(line.mid(constPerformerKey.length()))); } else { song.setPerformer(QString::fromUtf8(line.mid(constPerformerKey.length()))); } } else if (Loc_PlayQueue==location) { if (line.startsWith(constPriorityKey)) { song.priority = line.mid(constPriorityKey.length()).toUInt(); } else if (line.startsWith(constCommentKey)) { song.setComment(QString::fromUtf8(line.mid(constCommentKey.length()))); } } else if (Loc_Library==location) { if (line.startsWith(constAlbumSortKey)) { song.setAlbumSort(QString::fromUtf8(line.mid(constAlbumSortKey.length()))); } else if (line.startsWith(constArtistSortKey)) { song.setArtistSort(QString::fromUtf8(line.mid(constArtistSortKey.length()))); } else if (line.startsWith(constAlbumArtistSortKey)) { song.setAlbumArtistSort(QString::fromUtf8(line.mid(constAlbumArtistSortKey.length()))); } } } if (Song::Playlist!=song.type && song.genres[0].isEmpty()) { song.addGenre(Song::unknown()); } if (Loc_Library==location) { song.guessTags(); song.fillEmptyFields(); } else if (Loc_Streams==location) { song.setName(getAndRemoveStreamName(song.file)); } else { QString origFile=song.file; bool modifiedFile=false; #ifdef ENABLE_HTTP_SERVER if (!song.file.isEmpty() && song.file.startsWith(constHttpProtocol) && HttpServer::self()->isOurs(song.file)) { song.type=Song::CantataStream; Song mod=HttpServer::self()->decodeUrl(song.file); if (!mod.title.isEmpty()) { mod.id=song.id; song=mod; } modifiedFile=true; } else #endif if (song.file.contains(Song::constCddaProtocol)) { song.type=Song::Cdda; } else if (song.file.contains(constProtocol)) { foreach (const QString &protocol, constStdProtocols) { if (song.file.startsWith(protocol)) { song.type=Song::Stream; break; } } } if (!song.file.isEmpty()) { if (song.isStream()) { if (song.isCantataStream()) { if (song.title.isEmpty()) { QStringList parts=QUrl(song.file).path().split('/'); if (!parts.isEmpty()) { song.title=parts.last(); song.fillEmptyFields(); } } } else { if (OnlineService::decode(song)) { modifiedFile=true; } else { QString name=getAndRemoveStreamName(song.file); if (!name.isEmpty()) { song.setName(name); } if (song.title.isEmpty() && song.name().isEmpty()) { song.title=Utils::getFile(QUrl(song.file).path()); } } } } else { song.guessTags(); song.fillEmptyFields(); } } // HTTP server, and OnlineServices, modify the path. But this then messes up // undo/restore of playqueue. Therefore, set path back to original value... if (modifiedFile) { song.setDecodedPath(song.file); } song.file=origFile; song.setKey(location); // Check for downloaded podcasts played via local file playback if (Song::OnlineSvrTrack!=song.type && PodcastService::isPodcastFile(song.file)) { song.setIsFromOnlineService(PodcastService::constName); song.albumartist=song.artist=PodcastService::constName; } } return song; } QList MPDParseUtils::parseSongs(const QByteArray &data, Location location) { QList songs; QList currentItem; QList lines = data.split('\n'); int amountOfLines = lines.size(); for (int i = 0; i < amountOfLines; i++) { const QByteArray &line=lines.at(i); // Skip the "OK" line, this is NOT a song!!! if (constOkValue==line) { continue; } if (!line.isEmpty()) { currentItem.append(line); } if (i == lines.size() - 1 || lines.at(i + 1).startsWith(constFileKey)) { Song song=parseSong(currentItem, location); if (!song.file.isEmpty()) { songs.append(song); } currentItem.clear(); } } return songs; } QList MPDParseUtils::parseChanges(const QByteArray &data) { QList changes; QList lines = data.split('\n'); int amountOfLines = lines.size(); quint32 cpos=0; bool foundCpos=false; for (int i = 0; i < amountOfLines; i++) { const QByteArray &line = lines.at(i); // Skip the "OK" line, this is NOT a song!!! if (constOkValue==line || line.length()<1) { continue; } QList tokens = line.split(':'); if (2!=tokens.count()) { return QList(); } QByteArray element = tokens.takeFirst(); QByteArray value = tokens.takeFirst(); if (constChangePosKey==element) { if (foundCpos) { return QList(); } foundCpos=true; cpos = value.toInt(); } else if (constChangeIdKey==element) { if (!foundCpos) { return QList(); } foundCpos=false; qint32 id=value.toInt(); changes.append(IdPos(id, cpos)); } } return changes; } QStringList MPDParseUtils::parseList(const QByteArray &data, const QByteArray &key) { QStringList items; QList lines = data.split('\n'); int keyLen=key.length(); foreach (const QByteArray &line, lines) { // Skip the "OK" line, this is NOT a valid item!!! if (constOkValue==line) { continue; } if (line.startsWith(key)) { items.append(QString::fromUtf8(line.mid(keyLen).replace("://", ""))); } } return items; } MPDParseUtils::MessageMap MPDParseUtils::parseMessages(const QByteArray &data) { MPDParseUtils::MessageMap messages; QList lines = data.split('\n'); QByteArray channel; foreach (const QByteArray &line, lines) { // Skip the "OK" line, this is NOT a valid item!!! if (constOkValue==line) { continue; } if (line.startsWith(constChannel)) { channel=line.mid(constChannel.length()); } else if (line.startsWith(constMessage)) { messages[channel].append(QString::fromUtf8(line.mid(constMessage.length()))); } } return messages; } void MPDParseUtils::parseDirItems(const QByteArray &data, const QString &mpdDir, long mpdVersion, QList &songList, const QString &dir, QStringList &subDirs, Location loc) { QList currentItem; QList lines = data.split('\n'); int amountOfLines = lines.size(); bool parsePlaylists="/"!=dir && ""!=dir; bool setSingleTracks=parsePlaylists && singleTracksFolders.contains(dir) && Loc_Browse!=loc; QList songs; for (int i = 0; i < amountOfLines; i++) { const QByteArray &line=lines.at(i); if (line.startsWith(constDirectoryKey)) { subDirs.append(QString::fromUtf8(line.mid(constDirectoryKey.length()))); } currentItem.append(line); if (i == amountOfLines - 1 || lines.at(i + 1).startsWith(constFileKey) || lines.at(i + 1).startsWith(constPlaylistKey)) { Song currentSong = parseSong(currentItem, Loc_Library); currentItem.clear(); if (currentSong.file.isEmpty()) { continue; } if (Song::Playlist==currentSong.type) { // lsinfo will return all stored playlists - but this is deprecated. if (!parsePlaylists) { continue; } if (!currentSong.isCueFile()) { // In Folders/Browse, we can list all playlists if (Loc_Browse==loc) { songs.append(currentSong); } // Only add CUE files to library listing... continue; } switch (cueSupport) { case Cue_Ignore: continue; break; case Cue_Parse: if (Loc_Browse==loc) { songs.append(currentSong); } if (Loc_Library!=loc) { continue; } break; case Cue_ListButDontParse: if (Loc_Browse==loc) { songs.append(currentSong); } default: continue; break; } // No source files for CUE file.. if (songs.isEmpty()) { continue; } Song firstSong=songs.at(0); QList cueSongs; // List of songs from cue file QSet cueFiles; // List of source (flac, mp3, etc) files referenced in cue file DBUG << "Got playlist item" << currentSong.file; bool canSplitCue=mpdVersion>=CANTATA_MAKE_VERSION(0,17,0); bool parseCue=canSplitCue && currentSong.isCueFile() && !mpdDir.startsWith(constHttpProtocol) && QFile::exists(mpdDir+currentSong.file); bool cueParseStatus=false; if (parseCue) { DBUG << "Parsing cue file:" << currentSong.file << "mpdDir:" << mpdDir; cueParseStatus=CueFile::parse(currentSong.file, mpdDir, cueSongs, cueFiles); if (!cueParseStatus) { DBUG << "Failed to parse cue file!"; continue; } else DBUG << "Parsed cue file, songs:" << cueSongs.count() << "files:" << cueFiles; } if (cueParseStatus && cueSongs.count()>=songs.count() && (cueFiles.count() fixedCueSongs; // Songs taken from cueSongs that have been updated... if (songs.size()==cueFiles.size()) { quint32 albumTime=0; QMap origFiles; foreach (const Song &s, songs) { origFiles.insert(s.file, s); albumTime+=s.time; } DBUG << "Original files:" << origFiles.keys(); bool setTimeFromSource=origFiles.size()==cueSongs.size(); DBUG << "setTimeFromSource" << setTimeFromSource << "at" << albumTime << "#c" << cueFiles.size(); quint32 usedAlbumTime=0; foreach (const Song &orig, cueSongs) { Song s=orig; Song albumSong=origFiles[s.name()]; s.setName(QString()); // CueFile has placed source file name here! if (s.artist.isEmpty() && !albumSong.artist.isEmpty()) { s.artist=albumSong.artist; DBUG << "Get artist from album" << albumSong.artist; } if (s.composer().isEmpty() && !albumSong.composer().isEmpty()) { s.setComposer(albumSong.composer()); DBUG << "Get composer from album" << albumSong.composer(); } if (s.album.isEmpty() && !albumSong.album.isEmpty()) { s.album=albumSong.album; DBUG << "Get album from album" << albumSong.album; } if (s.albumartist.isEmpty() && !albumSong.albumartist.isEmpty()) { s.albumartist=albumSong.albumartist; DBUG << "Get albumartist from album" << albumSong.albumartist; } if (0==s.year && 0!=albumSong.year) { s.year=albumSong.year; DBUG << "Get year from album" << albumSong.year; } if (0==s.time && setTimeFromSource) { s.time=albumSong.time; } else if (0!=albumTime && 1==cueFiles.size()) { DBUG << s.title << s.time << albumTime << usedAlbumTime; // Try to set duration of last track by subtracting previous track durations from album duration... if (0==s.time) { s.time=albumTime-usedAlbumTime; } else { usedAlbumTime+=s.time; } } DBUG << s.title << s.time; fixedCueSongs.append(s); } canUseCueFileTracks=true; } else DBUG << "ERROR: file count mismatch" << songs.size() << cueFiles.size(); if (!canUseCueFileTracks) { // Album had a different number of source files to the CUE file. If so, then we need to ensure // all tracks have meta data - otherwise just fallback to listing file + cue foreach (const Song &orig, cueSongs) { Song s=orig; s.setName(QString()); // CueFile has placed source file name here! if (s.artist.isEmpty() || s.album.isEmpty()) { break; } fixedCueSongs.append(s); } if (fixedCueSongs.count()==cueSongs.count()) { canUseCueFileTracks=true; } else DBUG << "ERROR: Not all cue tracks had meta data"; } if (canUseCueFileTracks) { songs = fixedCueSongs; } continue; } if (!firstSong.albumArtist().isEmpty() && !firstSong.album.isEmpty()) { currentSong.albumartist=firstSong.albumArtist(); currentSong.album=firstSong.album; songs.append(currentSong); } } else { if (setSingleTracks) { currentSong.albumartist=Song::variousArtists(); currentSong.album=QObject::tr("Single Tracks"); } currentSong.fillEmptyFields(); songs.append(currentSong); } } } songList+=songs; } QList MPDParseUtils::parseOuputs(const QByteArray &data) { QList outputs; QList lines = data.split('\n'); Output output; foreach (const QByteArray &line, lines) { if (constOkValue==line) { break; } if (line.startsWith(constOutputIdKey)) { if (!output.name.isEmpty()) { outputs << output; output.name=QString(); } output.id=line.mid(constOutputIdKey.length()).toUInt(); } else if (line.startsWith(constOutputNameKey)) { output.name=line.mid(constOutputNameKey.length()); } else if (line.startsWith(constOutputEnabledKey)) { output.enabled=toBool(line.mid(constOutputEnabledKey.length())); } } if (!output.name.isEmpty()) { outputs << output; } return outputs; } static const QByteArray constSticker("sticker: "); QByteArray MPDParseUtils::parseSticker(const QByteArray &data, const QByteArray &sticker) { QList lines = data.split('\n'); QByteArray key=constSticker+sticker+'='; foreach (const QByteArray &line, lines) { if (line.startsWith(key)) { return line.mid(key.length()); } } return QByteArray(); } QList MPDParseUtils::parseStickers(const QByteArray &data, const QByteArray &sticker) { QList stickers; QList lines = data.split('\n'); Sticker s; QByteArray key=constSticker+sticker+'='; foreach (const QByteArray &line, lines) { if (constOkValue==line) { break; } if (line.startsWith(constFileKey)) { s.file=line.mid(constFileKey.length()); } else if (line.startsWith(key)) { s.value=line.mid(key.length()); stickers.append(s); } } return stickers; } QString MPDParseUtils::addStreamName(const QString &url, const QString &name) { return name.isEmpty() ? url : (url+(QUrl(url).path().isEmpty() ? "/#" : "#")+name); } // Previous versions replaced '#' in a stream's name with ${hash}. // This no longer occurs, but we need to do the replacement here, // just in case the stream name was saved this way. static const QString constHashReplacement=QLatin1String("${hash}"); QString MPDParseUtils::getStreamName(const QString &url) { int idx=url.indexOf('#'); QString name=-1==idx ? QString() : url.mid(idx+1); while (name.contains(constHashReplacement)) { name.replace(constHashReplacement, "#"); } return name; } QString MPDParseUtils::getAndRemoveStreamName(QString &url) { int idx=url.indexOf('#'); if (-1==idx) { return QString(); } QString name=url.mid(idx+1); while (name.contains(constHashReplacement)) { name.replace(constHashReplacement, "#"); } url=url.left(idx); return name; } cantata-2.2.0/mpd-interface/mpdparseutils.h000066400000000000000000000063011316350454000207120ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MPD_PARSE_UTILS_H #define MPD_PARSE_UTILS_H #include #include #include "config.h" #include "song.h" struct Playlist; struct Output; struct MPDStatsValues; struct MPDStatusValues; namespace MPDParseUtils { extern void enableDebug(); struct IdPos { IdPos(qint32 i, quint32 p) : id(i) , pos(p) { } qint32 id; quint32 pos; }; enum Location { Loc_Library, Loc_Playlists, Loc_PlayQueue, Loc_Streams, Loc_Search, Loc_Browse }; enum CueSupport { Cue_Parse, Cue_ListButDontParse, Cue_Ignore, Cue_Count }; struct Sticker { QByteArray file; QByteArray value; }; extern QString toStr(CueSupport cs); extern CueSupport toCueSupport(const QString &cs); extern void setCueFileSupport(CueSupport cs); extern CueSupport cueFileSupport(); extern void setSingleTracksFolders(const QSet &folders); extern QList parsePlaylists(const QByteArray &data); extern MPDStatsValues parseStats(const QByteArray &data); extern MPDStatusValues parseStatus(const QByteArray &data); extern Song parseSong(const QList &lines, Location location); inline Song parseSong(const QByteArray &data, Location location) { return parseSong(data.split('\n'), location); } extern QList parseSongs(const QByteArray &data, Location location); extern QList parseChanges(const QByteArray &data); extern QStringList parseList(const QByteArray &data, const QByteArray &key); typedef QMap MessageMap; extern MessageMap parseMessages(const QByteArray &data); extern void parseDirItems(const QByteArray &data, const QString &mpdDir, long mpdVersion, QList &songList, const QString &dir, QStringList &subDirs, Location loc); extern QList parseOuputs(const QByteArray &data); extern QByteArray parseSticker(const QByteArray &data, const QByteArray &sticker); extern QList parseStickers(const QByteArray &data, const QByteArray &sticker); extern QString addStreamName(const QString &url, const QString &name); extern QString getStreamName(const QString &url); extern QString getAndRemoveStreamName(QString &url); }; #endif cantata-2.2.0/mpd-interface/mpdstats.cpp000066400000000000000000000024141316350454000202110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "mpdstats.h" #include "mpdconnection.h" MPDStats * MPDStats::self() { static MPDStats instance; return &instance; } MPDStats::MPDStats() { connect(MPDConnection::self(), SIGNAL(statsUpdated(const MPDStatsValues &)), this, SLOT(update(const MPDStatsValues &)), Qt::QueuedConnection); } void MPDStats::update(const MPDStatsValues &v) { values=v; emit updated(); } cantata-2.2.0/mpd-interface/mpdstats.h000066400000000000000000000042031316350454000176540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MPD_STATS_H #define MPD_STATS_H #include #include #include struct MPDStatsValues { MPDStatsValues() : artists(0) , albums(0) , songs(0) , uptime(0) , playtime(0) , dbPlaytime(0) { } quint32 artists; quint32 albums; quint32 songs; quint32 uptime; quint32 playtime; quint32 dbPlaytime; time_t dbUpdate; }; class MPDStats : public QObject { Q_OBJECT public: static MPDStats * self(); // NOTE: There are no read/write locks aroud these values as they are read/written only from the GUI thread... quint32 artists() const { return values.artists; } quint32 albums() const { return values.albums; } quint32 songs() const { return values.songs; } quint32 uptime() const { return values.uptime; } quint32 playtime() const { return values.playtime; } quint32 dbPlaytime() const { return values.dbPlaytime; } time_t dbUpdate() const { return values.dbUpdate; } public Q_SLOTS: void update(const MPDStatsValues &v); Q_SIGNALS: void updated(); private: MPDStats(); ~MPDStats() {} MPDStats(const MPDStats&); MPDStats& operator=(const MPDStats& other); private: MPDStatsValues values; }; #endif cantata-2.2.0/mpd-interface/mpdstatus.cpp000066400000000000000000000024751316350454000204050ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include "mpdstatus.h" #include "mpdconnection.h" MPDStatus * MPDStatus::self() { static MPDStatus instance; return &instance; } MPDStatus::MPDStatus() { connect(MPDConnection::self(), SIGNAL(statusUpdated(const MPDStatusValues &)), this, SLOT(update(const MPDStatusValues &)), Qt::QueuedConnection); } void MPDStatus::update(const MPDStatusValues &v) { values=v; setGuessedElapsed(v.timeElapsed); emit updated(); } cantata-2.2.0/mpd-interface/mpdstatus.h000066400000000000000000000076551316350454000200570ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef MPD_STATUS_H #define MPD_STATUS_H #include enum MPDState { MPDState_Inactive, MPDState_Playing, MPDState_Stopped, MPDState_Paused }; struct MPDStatusValues { MPDStatusValues() : volume(0) , consume(false) , repeat(false) , single(false) , random(false) , playlist(0) , playlistLength(0) , crossFade(0) , state(MPDState_Inactive) , song(-1) , songId(-1) , nextSong(-1) , nextSongId(-1) , timeElapsed(-1) , timeTotal(-1) , bitrate(0) , samplerate(0) , bits(0) , channels(0) , updatingDb(-1) { } qint8 volume; bool consume; bool repeat; bool single; bool random; quint32 playlist; quint32 playlistLength; qint32 crossFade; MPDState state; qint32 song; qint32 songId; qint32 nextSong; qint32 nextSongId; qint32 timeElapsed; qint32 timeTotal; quint32 bitrate; quint32 samplerate; quint8 bits; quint8 channels; qint32 updatingDb; QString error; }; class MPDStatus : public QObject { Q_OBJECT public: MPDStatus(); ~MPDStatus() {} static MPDStatus * self(); // NOTE: There are no read/write locks aroud these values as they are read/written only fro the GUI thread... qint8 volume() const { return values.volume; } bool consume() const { return values.consume; } bool repeat() const { return values.repeat; } bool single() const { return values.single; } bool random() const { return values.random; } quint32 playlist() const { return values.playlist; } quint32 playlistLength() const { return values.playlistLength; } qint32 crossFade() const { return values.crossFade; } MPDState state() const { return values.state; } qint32 song() const { return values.song; } qint32 songId() const { return values.songId; } qint32 nextSong() const { return values.nextSong; } qint32 nextSongId() const { return values.nextSongId; } qint32 timeElapsed() const { return values.timeElapsed; } qint32 timeTotal() const { return values.timeTotal; } quint32 bitrate() const { return values.bitrate; } quint32 samplerate() const { return values.samplerate; } quint8 bits() const { return values.bits; } quint8 channels() const { return values.channels; } qint32 updatingDb() const { return values.updatingDb; } const QString & error() const { return values.error; } MPDStatusValues getValues() const { return values; } // Cantata does not poll MPD for current position, but instead used a timer // This timer will update its value here - so this can be used elsewhere... void setGuessedElapsed(qint32 v) { guessed=v; } qint32 guessedElapsed() const { return guessed; } public Q_SLOTS: void update(const MPDStatusValues &v); Q_SIGNALS: void updated(); private: MPDStatus(const MPDStatus&); MPDStatus& operator=(const MPDStatus& other); private: qint32 guessed; MPDStatusValues values; }; #endif cantata-2.2.0/mpd-interface/mpduser.cpp000066400000000000000000000262221316350454000200340ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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 det. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mpduser.h" #include "config.h" #include "support/utils.h" #include "gui/settings.h" #include "support/globalstatic.h" #include #include #include #include #include #include #include #include const QString MPDUser::constName=QLatin1String("-"); static const QString constDir=QLatin1String("mpd"); static const QString constConfigFile=QLatin1String("mpd.conf"); static const QString constMusicFolderKey=QLatin1String("music_directory"); static const QString constSocketKey=QLatin1String("bind_to_address"); static const QString constPlaylistsKey=QLatin1String("playlist_directory"); static const QString constPidKey=QLatin1String("pid_file"); QString MPDUser::translatedName() { return QObject::tr("Personal"); } GLOBAL_STATIC(MPDUser, instance) #if !defined Q_OS_WIN && !defined Q_OS_MAC static void moveConfig() { QString oldName=QDir::homePath()+"/.config/cantata/"+constDir+"/"+constConfigFile; if (QFile::exists(oldName)) { QString newName=Utils::dataDir(constDir, true)+constConfigFile; if (QFile::exists(newName)) { QFile::remove(oldName); } else { QFile::rename(oldName, newName); } } } #endif MPDUser::MPDUser() { #if !defined Q_OS_WIN && !defined Q_OS_MAC moveConfig(); #endif // For now, per-user MPD support is disabled for windows builds // - as I'm unsure how/if MPD works in windows!!! // - If enable, also need to fix isRunning!! #ifndef Q_OS_WIN mpdExe=Utils::findExe("mpd"); #endif det.name=constName; } bool MPDUser::isSupported() { return !mpdExe.isEmpty(); } bool MPDUser::isRunning() { #ifdef Q_OS_WIN return false; #else int pid=getPid(); return pid ? 0==::kill(pid, 0) : false; #endif } static QString readValue(const QString &line) { int start=line.indexOf("\""); int end=-1==start ? -1 : line.indexOf("\"", start+1); return -1==end ? QString() : line.mid(start+1, (end-start)-1); } void MPDUser::start() { if (isRunning() || mpdExe.isEmpty()) { return; } init(true); if (!det.dir.isEmpty() && !det.hostname.isEmpty()) { controlMpd(false); } } void MPDUser::stop() { if (!isRunning() || mpdExe.isEmpty()) { return; } init(false); controlMpd(true); } void MPDUser::setMusicFolder(const QString &folder) { if (folder==det.dir) { return; } init(true); QFile cfgFile(Utils::dataDir(constDir, true)+constConfigFile); QStringList lines; if (cfgFile.open(QIODevice::ReadOnly|QIODevice::Text)) { while (!cfgFile.atEnd()) { QString line = QString::fromUtf8(cfgFile.readLine()); if (line.startsWith(constMusicFolderKey)) { lines.append(constMusicFolderKey+" \""+folder+"\"\n"); } else { lines.append(line); } } } if (!lines.isEmpty()) { cfgFile.close(); if (cfgFile.open(QIODevice::WriteOnly|QIODevice::Text)) { QTextStream out(&cfgFile); foreach (const QString &line, lines) { out << line; } } } det.dir=folder; det.setDirReadable(); if (0!=getPid()) { controlMpd(true); // Stop controlMpd(false); // Start } } void MPDUser::setDetails(const MPDConnectionDetails &d) { setMusicFolder(d.dir); bool dirReadable=det.dirReadable; det=d; det.dirReadable=dirReadable; } static void removeDir(const QString &d) { if (d.isEmpty()) { return; } QDir dir(d); if (dir.exists()) { QString dirName=dir.dirName(); if (!dirName.isEmpty()) { dir.cdUp(); dir.rmdir(dirName); } } } void MPDUser::cleanup() { QString cfgFileName(Utils::dataDir(constDir, false)+constConfigFile); QFile cfgFile(cfgFileName); QSet files; QSet dirs; QString playlistDir; if (cfgFile.open(QIODevice::ReadOnly|QIODevice::Text)) { QStringList fileKeys=QStringList() << constPidKey << constSocketKey << QLatin1String("db_file") << QLatin1String("state_file") << QLatin1String("sticker_file"); QStringList dirKeys=QStringList() << constPlaylistsKey; while (!cfgFile.atEnd()) { QString line = QString::fromUtf8(cfgFile.readLine()); foreach (const QString &key, fileKeys) { if (line.startsWith(key)) { QString file=readValue(line); if (!file.isEmpty()) { QString dir=Utils::getDir(file); if (!dir.isEmpty()) { dirs.insert(dir); } files.insert(file); fileKeys.removeAll(key); } } } if (playlistDir.isEmpty() && line.startsWith(constPlaylistsKey)) { playlistDir=readValue(line); } } files.insert(cfgFileName); dirs.insert(Utils::getDir(cfgFileName)); } if (!dirs.isEmpty() && !files.isEmpty()) { foreach (const QString &f, files) { QFile::remove(f); } if (!playlistDir.isEmpty()) { QFileInfoList files=QDir(playlistDir).entryInfoList(QStringList() << "*.m3u", QDir::Files|QDir::NoDotAndDotDot); foreach (const QFileInfo &file, files) { QFile::remove(file.absoluteFilePath()); } removeDir(playlistDir); } foreach (const QString &d, dirs) { removeDir(d); } removeDir(Utils::dataDir(constDir, false)); removeDir(Utils::cacheDir(constDir, false)); } } void MPDUser::init(bool create) { if (create || det.dir.isEmpty() || det.hostname.isEmpty() || pidFileName.isEmpty()) { // Read coverFileName from Cantata settings... det.coverName=Settings::self()->connectionDetails(constName).coverName; det.dirReadable=false; // Read music folder and socket from MPD conf file... QString cfgDir=Utils::dataDir(constDir, create); QString cfgName(cfgDir+constConfigFile); QString playlists; if (create && !QFile::exists(cfgName)) { // Conf file does not exist, so we need to create one... QFile cfgTemplate(CANTATA_SYS_MPD_DIR+constConfigFile+".template"); if (cfgTemplate.open(QIODevice::ReadOnly|QIODevice::Text)) { QFile cfgFile(cfgName); if (cfgFile.open(QIODevice::WriteOnly|QIODevice::Text)) { QString homeDir=QDir::homePath(); QString cacheDir=Utils::cacheDir(constDir, create); QString dataDir=Utils::dataDir(constDir, create); QTextStream out(&cfgFile); while (!cfgTemplate.atEnd()) { QString line = cfgTemplate.readLine(); line=line.replace(QLatin1String("${HOME}"), homeDir); line=line.replace(QLatin1String("${CONFIG_DIR}"), cfgDir); line=line.replace(QLatin1String("${DATA_DIR}"), dataDir); line=line.replace(QLatin1String("${CACHE_DIR}"), cacheDir); line=line.replace("//", "/"); out << line; if (det.dir.isEmpty() && line.startsWith(constMusicFolderKey)) { det.dir=Utils::fixPath(readValue(line)); } if (det.hostname.isEmpty() && line.startsWith(constSocketKey)) { det.hostname=readValue(line); } if (pidFileName.isEmpty() && line.startsWith(constPidKey)) { pidFileName=readValue(line); } // Create playlists dir... if (playlists.isEmpty() && line.startsWith(constPlaylistsKey)) { playlists=readValue(line); if (!playlists.isEmpty()) { Utils::createWorldReadableDir(playlists, QString()); } } } } } } if (det.dir.isEmpty() || det.hostname.isEmpty() || pidFileName.isEmpty()) { QFile cfgFile(cfgName); if (cfgFile.open(QIODevice::ReadOnly|QIODevice::Text)) { while (!cfgFile.atEnd() && (det.dir.isEmpty() || det.hostname.isEmpty() || pidFileName.isEmpty())) { QString line = QString::fromUtf8(cfgFile.readLine()); if (det.dir.isEmpty() && line.startsWith(constMusicFolderKey)) { det.dir=Utils::fixPath(readValue(line)); } if (det.hostname.isEmpty() && line.startsWith(constSocketKey)) { det.hostname=readValue(line); } if (pidFileName.isEmpty() && line.startsWith(constPidKey)) { pidFileName=readValue(line); } } } det.setDirReadable(); } det.name=constName; } } int MPDUser::getPid() { int pid=0; init(false); if (!pidFileName.isEmpty()) { QFile pidFile(pidFileName); if (pidFile.open(QIODevice::ReadOnly|QIODevice::Text)) { QTextStream str(&pidFile); str >> pid; } } return pid; } bool MPDUser::controlMpd(bool stop) { QString confFile=Utils::dataDir(constDir, true)+constConfigFile; if (!QFile::exists(confFile)) { return false; } QStringList args=QStringList() << confFile; if (stop) { args+="--kill"; } else { // Ensure cache dir exists before starting MPD Utils::cacheDir(constDir, true); if (!pidFileName.isEmpty() && QFile::exists(pidFileName)) { QFile::remove(pidFileName); } } bool started=QProcess::startDetached(mpdExe, args); if (started && !stop) { for (int i=0; i<8; ++i) { Utils::msleep(250); if (0!=getPid()) { return true; } } } return started; } cantata-2.2.0/mpd-interface/mpduser.h000066400000000000000000000032021316350454000174720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MPD_USER_H #define MPD_USER_H #include #include "mpdconnection.h" class MPDUser { public: static MPDUser * self(); static const QString constName; static QString translatedName(); MPDUser(); bool isSupported(); bool isRunning(); void start(); void stop(); void setMusicFolder(const QString &folder); // Remove all files and folders (apart from Music folder!) associated with use MPD instance... void cleanup(); const MPDConnectionDetails & details(bool createFiles=false) { init(createFiles); return det; } void setDetails(const MPDConnectionDetails &d); private: void init(bool create); int getPid(); bool controlMpd(bool stop); private: QString mpdExe; QString pidFileName; MPDConnectionDetails det; }; #endif cantata-2.2.0/mpd-interface/output.h000066400000000000000000000026611316350454000173630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef OUTPUT_H #define OUTPUT_H #include struct Output { Output(quint8 i, bool en, QString n) : id(i), enabled(en), name(n) { } Output() : id(0xFF), enabled(false) { } Output(const Output &o) { *this=o; } Output & operator=(const Output &o) { id=o.id; enabled=o.enabled; name=o.name; return *this; } bool operator<(const Output &o) const { return name.localeAwareCompare(o.name)<0; } virtual ~Output() { } quint8 id; bool enabled; QString name; }; #endif cantata-2.2.0/mpd-interface/playlist.h000066400000000000000000000026101316350454000176560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef PLAYLIST_H #define PLAYLIST_H #include #include struct Playlist { Playlist() { } Playlist(const QString &n) : name(n) { } Playlist(const Playlist &o) { *this=o; } bool operator==(const Playlist &o) const { return name==o.name; } Playlist & operator=(const Playlist &o) { name=o.name; lastModified=o.lastModified; return *this; } virtual ~Playlist() { } QString name; QDateTime lastModified; }; #endif cantata-2.2.0/mpd-interface/song.cpp000066400000000000000000000532031316350454000173220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #include #include "config.h" #include "song.h" #if !defined CANTATA_NO_UI_FUNCTIONS #include "online/onlineservice.h" #endif #include #include #include #include #include #include //static const quint8 constOnlineDiscId=0xEE; const QString Song::constCddaProtocol=QLatin1String("/[cantata-cdda]/"); const QString Song::constMopidyLocal=QLatin1String("local:track:"); static QString unknownStr; static QString variousArtistsStr; const QString & Song::unknown() { return unknownStr; } const QString & Song::variousArtists() { return variousArtistsStr; } void Song::initTranslations() { unknownStr=QObject::tr("Unknown"); variousArtistsStr=QObject::tr("Various Artists"); } // When displaying albums, we use the 1st track's year as the year of the album. // The map below stores the mapping from artist+album to year. // This way the grouped view can find this quickly... static QHash albumYears; void Song::storeAlbumYear(const Song &s) { albumYears.insert(s.albumKey(), s.year); } int Song::albumYear(const Song &s) { QHash::ConstIterator it=albumYears.find(s.albumKey()); return it==albumYears.end() ? s.year : it.value(); } static int songType(const Song &s) { static QStringList extensions=QStringList() << QLatin1String(".flac") << QLatin1String(".wav") << QLatin1String(".dff") << QLatin1String(".dsf") << QLatin1String(".aac") << QLatin1String(".m4a") << QLatin1String(".m4b") << QLatin1String(".m4p") << QLatin1String(".mp4") << QLatin1String(".ogg") << QLatin1String(".opus") << QLatin1String(".mp3") << QLatin1String(".wma"); for (int i=0; i &songs) { qSort(songs.begin(), songs.end(), songTypeSort); } QString Song::decodePath(const QString &file, bool cdda) { if (cdda) { return QString(file).replace("/", "_").replace(":", "_"); } return file.startsWith(constMopidyLocal) ? QUrl::fromPercentEncoding(file.mid(constMopidyLocal.length()).toLatin1()) : file; } QString Song::encodePath(const QString &file) { return constMopidyLocal+QString(QUrl::toPercentEncoding(file, "/")); } static QSet compGenres; const QSet &Song::composerGenres() { return compGenres; } void Song::setComposerGenres(const QSet &g) { compGenres=g; } Song::Song() : extraFields(0) , priority(0) , disc(0) , blank(0) , time(0) , track(0) , origYear(0) , year(0) , type(Standard) , guessed(false) , id(-1) , size(0) , rating(Rating_Null) , lastModified(0) , key(Null_Key) { } Song & Song::operator=(const Song &s) { id = s.id; file = s.file; time = s.time; album = s.album; artist = s.artist; albumartist = s.albumartist; title = s.title; track = s.track; // pos = s.pos; disc = s.disc; blank = s.blank; priority = s.priority; origYear = s.origYear; year = s.year; for (int i=0; i0 && dot constSeparators=QSet() << QLatin1Char(' ') << QLatin1Char('-') << QLatin1Char('_') << QLatin1Char('.'); int separator=0; foreach (const QChar &sep, constSeparators) { separator=title.indexOf(sep); if (1==separator || 2==separator) { break; } } if ( (1==separator && title[separator-1].isDigit()) || (2==separator && title[separator-2].isDigit() && title[separator-1].isDigit()) ) { if (0==track) { track=title.left(separator).toInt(); } title=title.mid(separator+1); while (!title.isEmpty() && constSeparators.contains(title[0])) { title=title.mid(1); } } } } } void Song::revertGuessedTags() { title=artist=album=unknownStr; } void Song::fillEmptyFields() { if (artist.isEmpty()) { artist = unknownStr; blank |= BlankArtist; } if (album.isEmpty()) { album = unknownStr; blank |= BlankAlbum; } if (title.isEmpty()) { title = unknownStr; blank |= BlankTitle; } if (genres[0].isEmpty()) { genres[0]=unknownStr; } } struct KeyStore { KeyStore() : currentKey(0) { } quint16 currentKey; QHash keys; }; static QHash storeMap; void Song::clearKeyStore(int location) { storeMap.remove(location); } QString Song::displayAlbum(const QString &albumName, quint16 albumYear) { QString d=albumYear>0 ? albumName+QLatin1String(" (")+QString::number(albumYear)+QLatin1Char(')') : albumName; while (d.contains(") (")) { d=d.replace(") (", ", "); } return d; } static QSet prefixesToIngore=QSet() << QLatin1String("The"); QSet Song::ignorePrefixes() { return prefixesToIngore; } void Song::setIgnorePrefixes(const QSet &prefixes) { prefixesToIngore=prefixes; } static QString ignorePrefix(const QString &str) { foreach (const QString &p, prefixesToIngore) { if (str.startsWith(p+QLatin1Char(' '))) { return str.mid(p.length()+1)+QLatin1String(", ")+p; } } return QString(); } QString Song::sortString(const QString &str) { QString sort=ignorePrefix(str); if (sort.isEmpty()) { sort=str; } sort=sort.remove('.'); return sort==str ? QString() : sort; } quint16 Song::setKey(int location) { if (isStandardStream()) { key=0; return 0; } KeyStore &store=storeMap[location]; QString songKey(albumKey()); QHash::ConstIterator it=store.keys.find(songKey); if (it!=store.keys.end()) { key=it.value(); } else { store.currentKey++; // Key 0 is for streams, so we need to increment before setting... store.keys.insert(songKey, store.currentKey); key=store.currentKey; } return key; } bool Song::isUnknownAlbum() const { return (album.isEmpty() || album==unknownStr) && (albumArtist().isEmpty() || albumArtist()==unknownStr); } void Song::clear() { id = -1; file.clear(); time = 0; album.clear(); artist.clear(); title.clear(); track = 0; // pos = 0; disc = 0; blank = 0; year = 0; origYear = 0; for (int i=0; i0 && disc!=constOnlineDiscId ? (QString::number(disc)+QLatin1Char('.')) : QString())+ // (track>0 ? (track>9 ? QString::number(track) : (QLatin1Char('0')+QString::number(track))) : QString())+ // QLatin1Char(' ')+(addArtist ? artistSong() : title); // } return //(disc>0 ? (QString::number(disc)+QLatin1Char('.')) : QString())+ (track>0 ? (track>9 ? QString::number(track)+QLatin1Char(' ') : (QLatin1Char('0')+QString::number(track)+QLatin1Char(' '))) : QString())+ (showArtistIfDifferent && diffArtist() ? artistSong() : title) + (origYear>0 && origYear != year ? QLatin1String(" (")+QString::number(origYear)+QLatin1Char(')') : QString()); } #ifndef CANTATA_NO_UI_FUNCTIONS static void addField(const QString &name, const QString &val, QString &tt) { if (!val.isEmpty()) { tt+=QString("%1:  %2").arg(name).arg(val); } } #endif QString Song::toolTip() const { #ifdef CANTATA_NO_UI_FUNCTIONS return QString(); #else QString toolTip=QLatin1String(""); addField(QObject::tr("Title"), title, toolTip); addField(QObject::tr("Artist"), artist, toolTip); if (albumartist!=artist) { addField(QObject::tr("Album artist"), albumartist, toolTip); } addField(QObject::tr("Composer"), composer(), toolTip); addField(QObject::tr("Performer"), performer(), toolTip); addField(QObject::tr("Album"), album, toolTip); if (track>0) { addField(QObject::tr("Track number"), QString::number(track), toolTip); } if (disc>0) { addField(QObject::tr("Disc number"), QString::number(disc), toolTip); } addField(QObject::tr("Genre"), displayGenre(), toolTip); if (year>0) { addField(QObject::tr("Year"), QString::number(year), toolTip); } if (origYear>0) { addField(QObject::tr("Orignal Year"), QString::number(origYear), toolTip); } if (time>0) { addField(QObject::tr("Length"), Utils::formatTime(time, true), toolTip); } toolTip+=QLatin1String("
    "); if (isNonMPD()) { return toolTip; } return toolTip+QLatin1String("

    ")+filePath()+QLatin1String(""); #endif } QString Song::displayGenre() const { QString g=genres[0]; for (int i=1; i0 && sepPos")+albumText+QLatin1String("")); } QString descr=artist.isEmpty() ? QObject::tr("%1 on %2", "Song on Album").arg(title).arg(albumText) : QObject::tr("%1 by %2 on %3", "Song by Artist on Album").arg(title).arg(artist).arg(albumText); if (!withMarkup) { descr=descr.replace("", ""); descr=descr.replace("", ""); } return descr; } bool Song::useComposer() const { if (compGenres.isEmpty()) { return false; } for (int i=0; i>(QDataStream &stream, Song &song) { quint16 type; quint16 year; quint8 disc; bool guessed; stream >> song.id >> song.file >> song.album >> song.artist >> song.albumartist >> song.title >> disc >> song.priority >> song.time >> song.track >> year // >> song.origYear >> type >> guessed >> song.size >> song.extra >> song.extraFields; song.type=(Song::Type)type; song.year=year; song.guessed=guessed; song.disc=disc; for (int i=0; i> song.genres[i]; } return stream; } cantata-2.2.0/mpd-interface/song.h000066400000000000000000000263751316350454000170010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC is distributed in the hope that 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 QtMPC. If not, see . */ #ifndef SONG_H #define SONG_H #include #include #include #include #include "config.h" #include "support/utils.h" #include "cuefile.h" struct Song { enum Constants { Null_Key = 0xFFFF, Rating_Step = 2, Rating_Max = 10, Rating_Requested = 0xFE, Rating_Null = 0xFF }; static const int constNumGenres = 4; static const QLatin1Char constFieldSep; static const QSet & composerGenres(); static void setComposerGenres(const QSet &g); enum ExtraTags { Composer = 0x0001, Performer = 0x0002, Comment = 0x0004, MusicBrainzAlbumId = 0x0008, Name = 0x0010, AlbumSort = 0x0020, ArtistSort = 0x0040, AlbumArtistSort = 0x0080, // These are not real tags - but fields used elsewhere in the code... PodcastPublishedDate = 0x0100, PodcastLocalPath = 0x0200, PodcastImage = 0x0400, OnlineServiceName = 0x0800, OnlineImageUrl = 0x1000, OnlineImageCacheName = 0x2000, DeviceId = 0x4000, DecodedPath = 0x8000 }; enum Type { Standard = 0, SingleTracks = 1, Playlist = 2, Stream = 3, CantataStream = 4, Cdda = 5, OnlineSvrTrack = 6 }; enum Blank { BlankTitle = 0x01, BlankArtist = 0x02, BlankAlbum = 0x04 }; QString file; QString album; QString artist; QString albumartist; QString title; QString genres[constNumGenres]; QHash extra; quint16 extraFields; mutable quint8 priority; quint8 disc:5; quint8 blank:3; // Which field were blank, and Cantata set to Unknown quint16 time; quint16 track; quint16 origYear; quint16 year : 12; mutable Type type : 3; mutable bool guessed : 1; qint32 id; qint32 size; mutable quint8 rating; uint lastModified; // Only used in PlayQueue/PlayLists... quint16 key; static const QString & unknown(); static const QString & variousArtists(); static void initTranslations(); static const QString constCddaProtocol; static const QString constMopidyLocal; static void storeAlbumYear(const Song &s); static int albumYear(const Song &s); static void sortViaType(QList &songs); static QString decodePath(const QString &file, bool cdda=false); static QString encodePath(const QString &file); static void clearKeyStore(int location); static QString displayAlbum(const QString &albumName, quint16 albumYear); static bool isComposerGenre(const QString &genre) { return composerGenres().contains(genre); } static QSet ignorePrefixes(); static void setIgnorePrefixes(const QSet &prefixes); static QString sortString(const QString &str); Song(); Song(const Song &o) { *this=o; } Song & operator=(const Song &o); bool operator==(const Song &o) const; bool operator!=(const Song &o) const { return !(*this==o); } bool operator<(const Song &o) const; int compareTo(const Song &o) const; virtual ~Song() { } bool isEmpty() const; bool isDifferent(const Song &s) const { return file!=s.file || year!=s.year || artist!=s.artist || album!=s.album || title!=s.title || name()!=s.name(); } void guessTags(); void revertGuessedTags(); void fillEmptyFields(); quint16 setKey(int location); virtual void clear(); void addGenre(const QString &g); QString entryName() const; QString artistOrComposer() const; QString albumName() const; QString albumId() const; QString artistSong() const; const QString & albumArtist() const { return albumartist.isEmpty() ? artist : albumartist; } QString displayTitle() const { return !albumartist.isEmpty() && albumartist!=artist ? artistSong() : title; } QString trackAndTitleStr(bool showArtistIfDifferent=true) const; QString toolTip() const; QString displayGenre() const; const QString & firstGenre() const { return genres[0]; } int compareGenres(const Song &o) const; QString extraField(quint16 f) const { return hasExtraField(f) ? extra[f] : QString(); } bool hasExtraField(quint16 f) const { return extraFields&f; } void setExtraField(quint16 f, const QString &v); QString name() const { return extraField(Name); } void setName(const QString &v) { setExtraField(Name, v); } bool hasName() const { return hasExtraField(Name); } QString mbAlbumId() const { return extraField(MusicBrainzAlbumId); } void setMbAlbumId(const QString &v) { setExtraField(MusicBrainzAlbumId, v); } bool hasMbAlbumId() const { return hasExtraField(MusicBrainzAlbumId); } QString composer() const { return extraField(Composer); } void setComposer(const QString &v) { setExtraField(Composer, v); } bool hasComposer() const { return hasExtraField(Composer); } QString performer() const { return extraField(Performer); } void setPerformer(const QString &v) { setExtraField(Performer, v); } bool hasPerformer() const { return hasExtraField(Performer); } QString comment() const { return extraField(Comment); } void setComment(const QString &v) { setExtraField(Comment, v); } bool hasComment() const { return hasExtraField(Comment); } QString albumSort() const { return extraField(AlbumSort); } void setAlbumSort(const QString &v) { setExtraField(AlbumSort, v); } bool hasAlbumSort() const { return hasExtraField(AlbumSort); } QString artistSort() const { return extraField(ArtistSort); } void setArtistSort(const QString &v) { setExtraField(ArtistSort, v); } bool hasArtistSort() const { return hasExtraField(ArtistSort); } QString albumArtistSort() const { return extraField(AlbumArtistSort); } void setAlbumArtistSort(const QString &v) { setExtraField(AlbumArtistSort, v); } bool hasAlbumArtistSort() const { return hasExtraField(AlbumArtistSort); } QString artistSortString() const { return hasAlbumArtistSort() ? albumArtistSort() : hasArtistSort() ? artistSort() : QString(); } void clearExtra() { extra.clear(); } static bool isVariousArtists(const QString &str); bool isVariousArtists() const { return isVariousArtists(albumArtist()); } bool diffArtist() const; bool isUnknownAlbum() const; bool fixVariousArtists(); bool revertVariousArtists(); bool setAlbumArtist(); static QString capitalize(const QString &s); bool capitalise(); bool isStream() const { return Stream==type || CantataStream==type; } bool isStandardStream() const { return Stream==type && !isDlnaStream(); } bool isDlnaStream() const { return Stream==type && !albumArtist().isEmpty() && !album.isEmpty() && track>0; } bool isNonMPD() const { return isStream() || OnlineSvrTrack==type || Cdda==type || (!file.isEmpty() && file.startsWith(Utils::constDirSep)); } bool isCantataStream() const { return CantataStream==type; } bool isCdda() const { return Cdda==type; } QString albumKey() const; bool isCueFile() const { return Playlist==type && file.endsWith(QLatin1String(".cue"), Qt::CaseInsensitive); } bool isFromCue() const { return CueFile::isCue(file); } QString basicArtist() const; QString filePath() const { return decodePath(file, isCdda()); } QString displayAlbum(bool useComp=true) const { return displayAlbum(useComp ? albumName() : album, year); } QString describe(bool withMarkup=false) const; bool useComposer() const; void populateSorts(); // QString basicDescription() const; // // The following sections contain various 'hacks' - where fields of Song are abused for other // purposes. This is to keep the overall size of Song lower, as its used all over the place... // // We pass 'Song' around to cover requester. When we want the artist image, and not album image, // then we blank certain fields to indicate this! void setArtistImageRequest() { album=QString(); priority=0xFF; disc=0x1F; key=0xFFFF; } bool isArtistImageRequest() const { return 0x1F==disc && 0xFF==priority && 0xFFFF==key && album.isEmpty(); } void setComposerImageRequest() { album=QString(); priority=0xFE; disc=0x1E; key=0xFEFE; } bool isComposerImageRequest() const { return 0x1E==disc && 0xFE==priority && 0xFEFE==key && album.isEmpty(); } // In Covers, the following is used to indicate that a specfic size is requested... void setSpecificSizeRequest(int sz) { size=track=id=sz; time=0xFFFF; } bool isSpecificSizeRequest() const { return size>4 && size<1024 && track==size && id==size && 0xFFFF==time; } // podcast functions... bool hasBeenPlayed() const { return 0!=id; } void setPlayed(bool p) { id=p ? 1 : 0; } void setPodcastImage(const QString &i) { setExtraField(PodcastImage, i); } QString podcastImage() const { return extraField(PodcastImage); } void setPodcastPublishedDate(const QString &pd) { setExtraField(PodcastPublishedDate, pd); } QString podcastPublishedDate() const { return extraField(PodcastPublishedDate); } QString podcastLocalPath() const { return extraField(PodcastLocalPath); } void setPodcastLocalPath(const QString &l) { setExtraField(PodcastLocalPath, l); } QString decodedPath() const { return extraField(DecodedPath); } void setDecodedPath(const QString &v) { setExtraField(DecodedPath, v); } bool hasDecodedPath() const { return hasExtraField(DecodedPath); } // podcast/soundcloud functions... void setIsFromOnlineService(const QString &service) { setExtraField(OnlineServiceName, service); } bool isFromOnlineService() const { return hasExtraField(OnlineServiceName); } QString onlineService() const { return extraField(OnlineServiceName); } // device functions... void setIsFromDevice(const QString &id) { setExtraField(DeviceId, id); } bool isFromDevice() const { return hasExtraField(DeviceId); } QString deviceId() const { return extraField(DeviceId); } }; Q_DECLARE_METATYPE(Song) QDataStream & operator<<(QDataStream &stream, const Song &song); QDataStream & operator>>(QDataStream &stream, Song &song); inline uint qHash(const Song &key) { return qHash(key.albumArtist()+key.album+key.title+key.file); } #endif cantata-2.2.0/mpd-interface/stream.h000066400000000000000000000024521316350454000173140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAM_H #define STREAM_H #include struct Stream { Stream() { } Stream(const QString &u, const QString &n) : url(u), name(n) { } Stream(const Stream &o) { *this=o; } bool operator==(const Stream &o) const { return url==o.url; } Stream & operator=(const Stream &o) { url=o.url; name=o.name; return *this; } virtual ~Stream() { } QString url; QString name; }; #endif cantata-2.2.0/network/000077500000000000000000000000001316350454000146205ustar00rootroot00000000000000cantata-2.2.0/network/networkaccessmanager.cpp000066400000000000000000000177541316350454000215500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "networkaccessmanager.h" #include "networkproxyfactory.h" #include "gui/settings.h" #include "config.h" #include "support/globalstatic.h" #include #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void NetworkAccessManager::enableDebug() { debugEnabled=true; } static const int constMaxRedirects=5; NetworkJob::NetworkJob(NetworkAccessManager *p, const QUrl &u) : QObject(p) , numRedirects(0) , lastDownloadPc(0) , job(0) , origU(u) { QTimer::singleShot(0, this, SLOT(jobFinished())); } NetworkJob::NetworkJob(QNetworkReply *j) : QObject(j->parent()) , numRedirects(0) , lastDownloadPc(0) , job(j) { origU=j->url(); connectJob(); DBUG << (void *)this << (void *)job; } NetworkJob::~NetworkJob() { DBUG << (void *)this << (void *)job; cancelJob(); } void NetworkJob::cancelAndDelete() { DBUG << (void *)this << (void *)job; cancelJob(); deleteLater(); } void NetworkJob::connectJob() { if (!job) { return; } connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); connect(job, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); connect(job, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError))); connect(job, SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(uploadProgress(qint64, qint64))); connect(job, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProg(qint64, qint64))); connect(job, SIGNAL(destroyed(QObject *)), this, SLOT(jobDestroyed(QObject *))); } void NetworkJob::cancelJob() { DBUG << (void *)this << (void *)job; if (job) { disconnect(job, SIGNAL(finished()), this, SLOT(jobFinished())); disconnect(job, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); disconnect(job, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError))); disconnect(job, SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(uploadProgress(qint64, qint64))); disconnect(job, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProg(qint64, qint64))); disconnect(job, SIGNAL(destroyed(QObject *)), this, SLOT(jobDestroyed(QObject *))); job->abort(); job->close(); job->deleteLater(); job=0; } } void NetworkJob::abortJob() { DBUG << (void *)this << (void *)job; if (job) { job->abort(); } } void NetworkJob::jobFinished() { DBUG << (void *)this << (void *)job; if (!job) { emit finished(); } QNetworkReply *j=qobject_cast(sender()); if (!j || j!=job) { return; } QVariant redirect = j->header(QNetworkRequest::LocationHeader); if (redirect.isValid() && ++numRedirectsrequest()); // Copy headers... const QList &headers=origReq.rawHeaderList();; foreach (const QByteArray &header, headers) { newReq.setRawHeader(header, origReq.rawHeader(header)); } QNetworkReply *newJob=static_cast(j->manager())->get(newReq); DBUG << j->url().toString() << "redirected to" << newJob->url().toString(); cancelJob(); job=newJob; connectJob(); return; } DBUG << job->url().toString() << job->error() << (0==job->error() ? QLatin1String("OK") : job->errorString()); emit finished(); } void NetworkJob::jobDestroyed(QObject *o) { DBUG << (void *)this << (void *)job; if (o==job) { job=0; } } void NetworkJob::downloadProg(qint64 bytesReceived, qint64 bytesTotal) { int pc=((bytesReceived*1.0)/(bytesTotal*1.0)*100.0)+0.5; pc=pc<0 ? 0 : (pc>100 ? 100 : pc); if (pc!=lastDownloadPc) { emit downloadPercent(pc); } emit downloadProgress(bytesReceived, bytesTotal); } void NetworkJob::handleReadyRead() { DBUG << (void *)this << (void *)job; QNetworkReply *j=dynamic_cast(sender()); if (!j || j!=job) { return; } if (j->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) { return; } emit readyRead(); } GLOBAL_STATIC(NetworkAccessManager, instance) NetworkAccessManager::NetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) { enabled=Settings::self()->networkAccessEnabled(); if (enabled) { NetworkProxyFactory::self(); } } NetworkJob * NetworkAccessManager::get(const QNetworkRequest &req, int timeout) { DBUG << req.url().toString() << enabled; if (!enabled) { return new NetworkJob(this, req.url()); } // Set User-Agent - required for some Podcasts... QByteArray userAgent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8(); if (req.hasRawHeader("User-Agent")) { userAgent+=' '+req.rawHeader("User-Agent"); } QNetworkRequest request=req; request.setRawHeader("User-Agent", userAgent); // Windows builds do not support HTTPS - unless QtNetwork is recompiled... NetworkJob *reply=0; bool supportsSsl = false; #ifndef QT_NO_SSL supportsSsl = QSslSocket::supportsSsl(); #endif if (QLatin1String("https")==req.url().scheme() && !supportsSsl) { QUrl httpUrl=request.url(); httpUrl.setScheme(QLatin1String("http")); request.setUrl(httpUrl); DBUG << "no ssl, use" << httpUrl.toString(); reply = new NetworkJob(QNetworkAccessManager::get(request)); reply->setOrigUrl(req.url()); } else { reply = new NetworkJob(QNetworkAccessManager::get(request)); } if (0!=timeout) { connect(reply, SIGNAL(destroyed()), SLOT(replyFinished())); connect(reply, SIGNAL(finished()), SLOT(replyFinished())); timers[reply] = startTimer(timeout); } return reply; } struct FakeNetworkReply : public QNetworkReply { FakeNetworkReply() : QNetworkReply(0) { setError(QNetworkReply::ConnectionRefusedError, QString()); QTimer::singleShot(0, this, SIGNAL(finished())); } void abort() { } qint64 readData(char *, qint64) { return 0; } qint64 writeData(const char *, qint64) { return 0; } }; QNetworkReply * NetworkAccessManager::postFormData(QNetworkRequest req, const QByteArray &data) { DBUG << req.url().toString() << enabled << data.length(); if (enabled) { if (!data.isEmpty()) { req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); } return QNetworkAccessManager::post(req, data); } else { return new FakeNetworkReply(); } } void NetworkAccessManager::replyFinished() { NetworkJob *job = static_cast(sender()); DBUG << (void *)job; if (timers.contains(job)) { killTimer(timers.take(job)); } } void NetworkAccessManager::timerEvent(QTimerEvent *e) { NetworkJob *job = timers.key(e->timerId()); DBUG << (void *)job; if (job) { job->abortJob(); } } cantata-2.2.0/network/networkaccessmanager.h000066400000000000000000000072721316350454000212070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NETWORK_ACCESS_MANAGER_H #define NETWORK_ACCESS_MANAGER_H #include #include #include class QTimerEvent; class NetworkAccessManager; class NetworkJob : public QObject { Q_OBJECT public: NetworkJob(QNetworkReply *j); virtual ~NetworkJob(); QNetworkReply * actualJob() const { return job; } void cancelAndDelete(); bool open(QIODevice::OpenMode mode) { return job && job->open(mode); } void close() { if (job) job->close(); } QUrl url() const { return job ? job->url() : origU; } QUrl origUrl() const { return origU; } void setOrigUrl(const QUrl &u) { origU=u; } QNetworkReply::NetworkError error() const { return job ? job->error() : QNetworkReply::UnknownNetworkError; } QString errorString() const { return job ? job->errorString() : QString(); } QByteArray readAll() { return job ? job->readAll() : QByteArray(); } bool ok() const { return job && QNetworkReply::NoError==job->error(); } QVariant attribute(QNetworkRequest::Attribute code) const { return job ? job->attribute(code) : QVariant(); } qint64 bytesAvailable() const { return job ? job->bytesAvailable() : -1; } QByteArray read(qint64 maxlen) { return job ? job->read(maxlen) : QByteArray(); } Q_SIGNALS: void finished(); void error(QNetworkReply::NetworkError); void uploadProgress(qint64 bytesSent, qint64 bytesTotal); void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); void downloadPercent(int pc); void readyRead(); private Q_SLOTS: void jobFinished(); void jobDestroyed(QObject *o); void downloadProg(qint64 bytesReceived, qint64 bytesTotal); void handleReadyRead(); private: NetworkJob(NetworkAccessManager *p, const QUrl &u); void connectJob(); void cancelJob(); void abortJob(); private: int numRedirects; int lastDownloadPc; QNetworkReply *job; QUrl origU; friend class NetworkAccessManager; }; class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: static void enableDebug(); static NetworkAccessManager * self(); NetworkAccessManager(QObject *parent=0); virtual ~NetworkAccessManager() { } NetworkJob * get(const QNetworkRequest &req, int timeout=0); NetworkJob * get(const QUrl &url, int timeout=0) { return get(QNetworkRequest(url), timeout); } QNetworkReply * postFormData(QNetworkRequest req, const QByteArray &data); QNetworkReply * postFormData(const QUrl &url, const QByteArray &data) { return postFormData(QNetworkRequest(url), data); } void setEnabled(bool e) { enabled=e; } bool isEnabled() const { return enabled; } protected: void timerEvent(QTimerEvent *e); private Q_SLOTS: void replyFinished(); private: bool enabled; QMap timers; friend class NetworkJob; }; #endif // NETWORK_ACCESS_MANAGER_H cantata-2.2.0/network/networkproxyfactory.cpp000066400000000000000000000056351316350454000215200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "networkproxyfactory.h" #include #include #include #include const char * NetworkProxyFactory::constSettingsGroup = "Proxy"; static QList getSystemProxyForQuery(const QNetworkProxyQuery &query) { return QNetworkProxyFactory::systemProxyForQuery(query); } #ifdef ENABLE_PROXY_CONFIG NetworkProxyFactory::NetworkProxyFactory() : mode(Mode_System) , type(QNetworkProxy::HttpProxy) , port(8080) { QNetworkProxyFactory::setApplicationProxyFactory(this); reloadSettings(); } #else NetworkProxyFactory::NetworkProxyFactory() { QNetworkProxyFactory::setApplicationProxyFactory(this); } #endif NetworkProxyFactory * NetworkProxyFactory::self() { static NetworkProxyFactory *instance=0; if (!instance) { instance = new NetworkProxyFactory; } return instance; } #ifdef ENABLE_PROXY_CONFIG void NetworkProxyFactory::reloadSettings() { QMutexLocker l(&mutex); QSettings s; s.beginGroup(constSettingsGroup); mode = Mode(s.value("mode", Mode_System).toInt()); type = QNetworkProxy::ProxyType(s.value("type", QNetworkProxy::HttpProxy).toInt()); hostname = s.value("hostname").toString(); port = s.value("port", 8080).toInt(); username = s.value("username").toString(); password = s.value("password").toString(); } #endif QList NetworkProxyFactory::queryProxy(const QNetworkProxyQuery& query) { #ifdef ENABLE_PROXY_CONFIG QMutexLocker l(&mutex); QNetworkProxy ret; switch (mode) { case Mode_System: return getSystemProxyForQuery(query); case Mode_Direct: ret.setType(QNetworkProxy::NoProxy); break; case Mode_Manual: ret.setType(type); ret.setHostName(hostname); ret.setPort(port); if (!username.isEmpty()) { ret.setUser(username); } if (!password.isEmpty()) { ret.setPassword(password); } break; } return QList() << ret; #else return getSystemProxyForQuery(query); #endif } cantata-2.2.0/network/networkproxyfactory.h000066400000000000000000000033101316350454000211510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NETWORKPROXYFACTORY_H #define NETWORKPROXYFACTORY_H #include #include #include #include "config.h" class NetworkProxyFactory : public QNetworkProxyFactory { public: // These values are persisted enum Mode { Mode_System = 0, Mode_Direct = 1, Mode_Manual = 2 }; static NetworkProxyFactory * self(); static const char * constSettingsGroup; // These methods are thread-safe #ifdef ENABLE_PROXY_CONFIG void reloadSettings(); #endif QList queryProxy(const QNetworkProxyQuery& query); private: NetworkProxyFactory(); private: #ifdef ENABLE_PROXY_CONFIG QMutex mutex; Mode mode; QNetworkProxy::ProxyType type; QString hostname; int port; QString username; QString password; #endif }; #endif // NETWORKPROXYFACTORY_H cantata-2.2.0/network/proxysettings.cpp000066400000000000000000000063431316350454000202740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "proxysettings.h" #include "networkproxyfactory.h" #include ProxySettings::ProxySettings(QWidget *parent) : QWidget(parent) { setupUi(this); proxyMode->addItem(tr("No proxy"), (int)NetworkProxyFactory::Mode_Direct); proxyMode->addItem(tr("Use the system proxy settings"), (int)NetworkProxyFactory::Mode_System); proxyMode->addItem(tr("Manual proxy configuration"), (int)NetworkProxyFactory::Mode_Manual); connect(proxyMode, SIGNAL(currentIndexChanged(int)), SLOT(toggleMode())); } ProxySettings::~ProxySettings() { } void ProxySettings::load() { QSettings s; s.beginGroup(NetworkProxyFactory::constSettingsGroup); int mode=s.value("mode", NetworkProxyFactory::Mode_System).toInt(); for (int i=0; icount(); ++i) { if (proxyMode->itemData(i).toInt()==mode) { proxyMode->setCurrentIndex(i); break; } } proxyType->setCurrentIndex(QNetworkProxy::HttpProxy==s.value("type", QNetworkProxy::HttpProxy).toInt() ? 0 : 1); proxyHost->setText(s.value("hostname").toString()); proxyPort->setValue(s.value("port", 8080).toInt()); proxyUsername->setText(s.value("username").toString()); proxyPassword->setText(s.value("password").toString()); s.endGroup(); toggleMode(); } void ProxySettings::save() { QSettings s; s.beginGroup(NetworkProxyFactory::constSettingsGroup); s.setValue("mode", proxyMode->itemData(proxyMode->currentIndex()).toInt()); s.setValue("type", 0==proxyType->currentIndex() ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy); s.setValue("hostname", proxyHost->text()); s.setValue("port", proxyPort->value()); s.setValue("username", proxyUsername->text()); s.setValue("password", proxyPassword->text()); s.endGroup(); NetworkProxyFactory::self()->reloadSettings(); } void ProxySettings::toggleMode() { bool showManual=NetworkProxyFactory::Mode_Manual==proxyMode->itemData(proxyMode->currentIndex()).toInt(); proxyType->setVisible(showManual); proxyTypeLabel->setVisible(showManual); proxyHost->setVisible(showManual); proxyHostLabel->setVisible(showManual); proxyPort->setVisible(showManual); proxyPortLabel->setVisible(showManual); proxyUsername->setVisible(showManual); proxyUsernameLabel->setVisible(showManual); proxyPassword->setVisible(showManual); proxyPasswordLabel->setVisible(showManual); } cantata-2.2.0/network/proxysettings.h000066400000000000000000000022341316350454000177340ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PROXYSETTINGS_H #define PROXYSETTINGS_H #include #include "ui_proxysettings.h" class ProxySettings : public QWidget, public Ui::ProxySettings { Q_OBJECT public: ProxySettings(QWidget *parent); ~ProxySettings(); void load(); void save(); private Q_SLOTS: void toggleMode(); }; #endif cantata-2.2.0/network/proxysettings.ui000066400000000000000000000067671316350454000201410ustar00rootroot00000000000000 ProxySettings 0 0 374 207 0 0 0 Mode: proxyMode Type: proxyType HTTP Proxy SOCKS Proxy Host: proxyHost Port: proxyPort 65535 8080 Username: proxyUsername Password: proxyPassword QLineEdit::Password LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    cantata-2.2.0/online/000077500000000000000000000000001316350454000144135ustar00rootroot00000000000000cantata-2.2.0/online/icons/000077500000000000000000000000001316350454000155265ustar00rootroot00000000000000cantata-2.2.0/online/icons/CMakeLists.txt000066400000000000000000000006651316350454000202750ustar00rootroot00000000000000set(CANTATA_INSTALL_ONLINE_ICONS bbc.svg cbc.svg npr.svg podcasts.png soundcloud.png) if (WIN32) install(FILES ${CANTATA_INSTALL_ONLINE_ICONS} DESTINATION ${CMAKE_INSTALL_PREFIX}/icons/) elseif (APPLE) install(FILES ${CANTATA_INSTALL_ONLINE_ICONS} DESTINATION ${MACOSX_BUNDLE_RESOURCES}/icons/) else () install(FILES ${CANTATA_INSTALL_ONLINE_ICONS} DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/icons/) endif () cantata-2.2.0/online/icons/bbc.svg000066400000000000000000000023711316350454000170000ustar00rootroot00000000000000 cantata-2.2.0/online/icons/cbc.svg000066400000000000000000000030421316350454000167750ustar00rootroot00000000000000 cantata-2.2.0/online/icons/gpodder.svg000066400000000000000000000152301316350454000176740ustar00rootroot00000000000000 cantata-2.2.0/online/icons/itunes.svg000066400000000000000000000246671316350454000175750ustar00rootroot00000000000000 itunes cantata-2.2.0/online/icons/jamendo.svg000066400000000000000000000051771316350454000176760ustar00rootroot00000000000000 cantata-2.2.0/online/icons/magnatune.svg000066400000000000000000000016151316350454000202310ustar00rootroot00000000000000 cantata-2.2.0/online/icons/npr.svg000066400000000000000000000037501316350454000170530ustar00rootroot00000000000000 cantata-2.2.0/online/icons/podcasts.png000066400000000000000000000055671316350454000200710ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org< IDATx]{Ug6 Į( QZZYe&uQemCtp$BDScXB>%xݝٝevgf{[|.s<|}9`m3CV9yY>3I'ɬY䨅 \ &N`lw-kx`cV,b+m.?It= 7^ C̾PB]WA]wnݕP[PP rf_Ϭ]u)xWL`vx}!x.w闡fyPs}jJƂg,tgg3ls3=Suq~T$rIfRʋD8@*^/_gUg8;NQfper@#$P\)Sv%ʗ_O*k~q@ m"@?G,iйBW  kx R{; @ZJ"/ , Ї<P|98.W""cVA#>Zi"3!$qT]Nп'~ Z/TA=@w@?U=_F[ h%2=Jk'@DЗ}^(AF 'hkH$IЧ5 E-[5E:RR&Ї OqdRJ#AH!1p߆ʠ~K!]?&RPQqAˋZgF,D) @ 䥀{< FO4=J>{@G 1"-~humYtC0W/4l_s#`Y<X5zо< W t$X>zz}cר2[(gkжoM`Q`-( R+.k룷,!y]ɬ\HѴgFzf cz}WM#!xtmeL! mtޟCw8 $[W %;퇶<1>0DBwc-Mx}[ٱh]X;n{C$SM?w(:"C$Y3V&#asWo:?/Ʈd]y8YrvG8-o&/FHh~r]6E/QnH+'d8DP<]sKLJ>ǗzFU6q[S:c9;GI_~ks1cXNrP1.MT('ܖgBKgAڪ45j(biLL#2U7=-ߜr4~YOoOJ4I9ɮ(0ڥ$ꎎ:vEsϮZKcoj"K!ﻤ$0+blAb fzF[5߁;ɳ|rn$]Xךۨu0jؑnEW0ƧϹ<ѡJz𭉣aio-r+g*X*$ VIݶ<6S/4(BII&%26X̿",4'[͝i#̓:&=;12]:~ܯ_eE!͂'I7\K`!r<~2)Xd[ˡJ lbŊDgm>:b4>&^t/(֭.նN K&a&/oIo#3+|DE`RczGSiF OQ]ӛ#EwSS LL]=#{ׁO1 f/XExPܫPMSF5BԆ=4jJ'z;,'\S'cIH&cq//"m+N<2"p͆;?OS|)7˴d')&f5CWq$gQFK2GFoh޽oîlB=ӽL/0m 3OcL:(i2 ^og?%hH*~h~/?shnG_MA,$0+LJlR{2Ӿ fEn@6 2yfe`:[L̺_߅`_in cantata-2.2.0/online/icons/soundcloud.png000066400000000000000000000067461316350454000204300ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org< cIDATx]kWέq<8@MFl6D!RDբ]XFA`W $M+ˮ4yZo0A{߰A=[% s[24~^|!)#,zbքS"bJ#MikU&p}EXz) m䱛$@XmsEA50enW|vQ .pnk  BPȀ.J0 X?d^BUvv P.࣏O> >:jT?"0;$6]t2aXIBsy_OF6At|(7|Uׂ^}MUznAS]`@'xAo@zdx;'At D@¸ECGc7̊ovI]!ݛ)I,,LC,I׾V3 Z 66 ]W, H'j6v$R'u&MqщJb5 Aftk*жݺ0t%$߁٦K0Wg)ó3>S),9Qe"(%asОWo_L?r/v%G2BW:AF X´iŌ#+,װeW+"Tʱ"5UPTfq o|@7N_9+)Ƽ+Lé+_UP d<)IÙ-tk}i>7=PEa@MsA>R=i<-/H +)W"%b=k$LB!Xo`Ty_9 ^mGEe$ܶmyض u*=ux` ѳiĥ@ܓ1z:* ӷȨKV(R%'jq[)[@#:Fc[H@{`E8D򐘞A,>Xvhb]%UPoRcK).\ׅ8 ^^[v.?~-`؟yiw@{cPE, h?8? \'NLea:>@dp&eR{-!*:!N@q{:2z3IVl\9™*]aTq-`U'0X PL9P -D L-emH-`@=9M`6Hx15O=ghp|x71%#/~`4Q: << ̜;Vb+۟7#ɬ#O^z䅭g@(͸P: @]g@H@^!jԊ 怰 +.f@(+CC s@BYY]w9۶O<}r<vIS|0p6IyjA1~$\\ٱ Ag}jX._,'b|LAClSFU|+IGUl_nl_Lޛ !hIf|5~鑓w5(pW}ЄW1lS|مXe(܂T_+ӕKҕ,oU|ĒMß4ۮ7n??an3NQ3SyQ|S {9cA)ƍh"~ t9)*6VvŒ JD%9fNAq.+O2NcM="*p͂p k(HfUJ$pno=Ϯ\ IENDB`cantata-2.2.0/online/icons/soundcloud.svg000066400000000000000000000077211316350454000204350ustar00rootroot00000000000000image/svg+xml cantata-2.2.0/online/jamendoservice.cpp000066400000000000000000000321041316350454000201150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "jamendoservice.h" #include "jamendosettingsdialog.h" #include "db/onlinedb.h" #include "models/roles.h" #include "support/icon.h" #include "support/configuration.h" #include #include #ifdef TAGLIB_FOUND #include #include #include static QString id3Genre(int id) { static QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // Clementine: In theory, genre 0 is "blues"; in practice it's invalid. return 0==id ? QString() : codec->toUnicode(TagLib::ID3v1::genre(id).toCString(true)).trimmed(); } #else // TAGLIB_FOUND static QString id3Genre(int id) { // Clementine: In theory, genre 0 is "blues"; in practice it's invalid. if (0==id) { return QString(); } static QMap idMap; if (idMap.isEmpty()) { idMap.insert(0, "Blues"); idMap.insert(1, "Classic Rock"); idMap.insert(2, "Country"); idMap.insert(3, "Dance"); idMap.insert(4, "Disco"); idMap.insert(5, "Funk"); idMap.insert(6, "Grunge"); idMap.insert(7, "Hip-Hop"); idMap.insert(8, "Jazz"); idMap.insert(9, "Metal"); idMap.insert(10, "New Age"); idMap.insert(11, "Oldies"); idMap.insert(12, "Other"); idMap.insert(13, "Pop"); idMap.insert(14, "R&B"); idMap.insert(15, "Rap"); idMap.insert(16, "Reggae"); idMap.insert(17, "Rock"); idMap.insert(18, "Techno"); idMap.insert(19, "Industrial"); idMap.insert(20, "Alternative"); idMap.insert(21, "Ska"); idMap.insert(22, "Death Metal"); idMap.insert(23, "Pranks"); idMap.insert(24, "Soundtrack"); idMap.insert(25, "Euro-Techno"); idMap.insert(26, "Ambient"); idMap.insert(27, "Trip-Hop"); idMap.insert(28, "Vocal"); idMap.insert(29, "Jazz+Funk"); idMap.insert(30, "Fusion"); idMap.insert(31, "Trance"); idMap.insert(32, "Classical"); idMap.insert(33, "Instrumental"); idMap.insert(34, "Acid"); idMap.insert(35, "House"); idMap.insert(36, "Game"); idMap.insert(37, "Sound Clip"); idMap.insert(38, "Gospel"); idMap.insert(39, "Noise"); idMap.insert(40, "AlternRock"); idMap.insert(41, "Bass"); idMap.insert(42, "Soul"); idMap.insert(43, "Punk"); idMap.insert(44, "Space"); idMap.insert(45, "Meditative"); idMap.insert(46, "Instrumental Pop"); idMap.insert(47, "Instrumental Rock"); idMap.insert(48, "Ethnic"); idMap.insert(49, "Gothic"); idMap.insert(50, "Darkwave"); idMap.insert(51, "Techno-Industrial"); idMap.insert(52, "Electronic"); idMap.insert(53, "Pop-Folk"); idMap.insert(54, "Eurodance"); idMap.insert(55, "Dream"); idMap.insert(56, "Southern Rock"); idMap.insert(57, "Comedy"); idMap.insert(58, "Cult"); idMap.insert(59, "Gangsta"); idMap.insert(60, "Top 40"); idMap.insert(61, "Christian Rap"); idMap.insert(62, "Pop/Funk"); idMap.insert(63, "Jungle"); idMap.insert(64, "Native American"); idMap.insert(65, "Cabaret"); idMap.insert(66, "New Wave"); idMap.insert(67, "Psychadelic"); idMap.insert(68, "Rave"); idMap.insert(69, "Showtunes"); idMap.insert(70, "Trailer"); idMap.insert(71, "Lo-Fi"); idMap.insert(72, "Tribal"); idMap.insert(73, "Acid Punk"); idMap.insert(74, "Acid Jazz"); idMap.insert(75, "Polka"); idMap.insert(76, "Retro"); idMap.insert(77, "Musical"); idMap.insert(78, "Rock & Roll"); idMap.insert(79, "Hard Rock"); idMap.insert(80, "Folk"); idMap.insert(81, "Folk-Rock"); idMap.insert(82, "National Folk"); idMap.insert(83, "Swing"); idMap.insert(84, "Fast Fusion"); idMap.insert(85, "Bebob"); idMap.insert(86, "Latin"); idMap.insert(87, "Revival"); idMap.insert(88, "Celtic"); idMap.insert(89, "Bluegrass"); idMap.insert(90, "Avantgarde"); idMap.insert(91, "Gothic Rock"); idMap.insert(92, "Progressive Rock"); idMap.insert(93, "Psychedelic Rock"); idMap.insert(94, "Symphonic Rock"); idMap.insert(95, "Slow Rock"); idMap.insert(96, "Big Band"); idMap.insert(97, "Chorus"); idMap.insert(98, "Easy Listening"); idMap.insert(99, "Acoustic"); idMap.insert(100, "Humour"); idMap.insert(101, "Speech"); idMap.insert(102, "Chanson"); idMap.insert(103, "Opera"); idMap.insert(104, "Chamber Music"); idMap.insert(105, "Sonata"); idMap.insert(106, "Symphony"); idMap.insert(107, "Booty Bass"); idMap.insert(108, "Primus"); idMap.insert(109, "Porn Groove"); idMap.insert(110, "Satire"); idMap.insert(111, "Slow Jam"); idMap.insert(112, "Club"); idMap.insert(113, "Tango"); idMap.insert(114, "Samba"); idMap.insert(115, "Folklore"); idMap.insert(116, "Ballad"); idMap.insert(117, "Power Ballad"); idMap.insert(118, "Rhythmic Soul"); idMap.insert(119, "Freestyle"); idMap.insert(120, "Duet"); idMap.insert(121, "Punk Rock"); idMap.insert(122, "Drum Solo"); idMap.insert(123, "A capella"); idMap.insert(124, "Euro-House"); idMap.insert(125, "Dance Hall"); } return idMap[id]; } #endif static const QLatin1String constStreamUrl("http://api.jamendo.com/get2/stream/track/redirect/?id=%1&streamencoding="); static const QLatin1String constListingUrl("http://imgjam.com/data/dbdump_artistalbumtrack.xml.gz"); static const QLatin1String constName("jamendo"); int JamendoXmlParser::parse(QXmlStreamReader &xml) { int artistCount=0; QList *songList=new QList(); while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType() && QLatin1String("artist")==xml.name()) { parseArtist(songList, xml); artistCount++; if (songList->count()>500) { emit songs(songList); songList=new QList(); } } } if (songList->isEmpty()) { delete songList; } else { emit songs(songList); } return artistCount; } void JamendoXmlParser::parseArtist(QList *songList, QXmlStreamReader &xml) { Song song; while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType()) { QStringRef name = xml.name(); if (QLatin1String("name")==name) { song.artist=xml.readElementText().trimmed(); } else if (QLatin1String("album")==name) { parseAlbum(song, songList, xml); } /*else if (artist && QLatin1String("image")==name) { artist->setImageUrl(xml.readElementText().trimmed()); }*/ } else if (xml.isEndElement() && QLatin1String("artist")==xml.name()) { break; } } } void JamendoXmlParser::parseAlbum(Song &song, QList *songList, QXmlStreamReader &xml) { QString id; QString genre; song.track=0; song.album=QString(); while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType()) { QStringRef name = xml.name(); if (QLatin1String("name")==name) { song.album=xml.readElementText().trimmed(); } else if (QLatin1String("track")==name) { song.track++; parseSong(song, genre, xml); songList->append(song); } else if (QLatin1String("id")==name) { id=xml.readElementText().trimmed(); } else if (QLatin1String("id3genre")==name) { int g=xml.readElementText().toInt(); if (0!=g) { genre=id3Genre(g); } } } else if (xml.isEndElement() && QLatin1String("album")==xml.name()) { break; } } if (!id.isEmpty()) { emit coverUrl(song.artistOrComposer(), song.album, id); } } void JamendoXmlParser::parseSong(Song &song, const QString &albumGenre, QXmlStreamReader &xml) { song.time=0; song.title=QString(); song.genres[0]=albumGenre; while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType()) { QStringRef name = xml.name(); if (QLatin1String("name")==name) { song.title=xml.readElementText().trimmed(); } else if (QLatin1String("duration")==name) { song.time=xml.readElementText().toFloat(); } else if (QLatin1String("id3genre")==name && albumGenre.isEmpty()) { int g=xml.readElementText().toInt(); if (0!=g) { song.genres[0]=id3Genre(g); } } else if (QLatin1String("id")==name) { song.file=xml.readElementText().trimmed(); } } else if (xml.isEndElement() && QLatin1String("track")==xml.name()) { break; } } song.fillEmptyFields(); } static const QLatin1String constMp3Format("mp3"); static const QLatin1String constOggFormat("ogg"); static QString formatStr(JamendoService::Format f) { return JamendoService::FMT_MP3==f ? "mp3" : "ogg"; } static JamendoService::Format toFormat(const QString &f) { return f=="ogg" ? JamendoService::FMT_Ogg : JamendoService::FMT_MP3; } JamendoService::JamendoService(QObject *p) : OnlineDbService(new OnlineDb(constName, p), p) { icn.addFile(":"+constName); useCovers(name()); Configuration cfg(constName); format=toFormat(cfg.get("format", formatStr(FMT_MP3))); } QVariant JamendoService::data(const QModelIndex &index, int role) const { if (index.isValid()) { switch (role) { case Cantata::Role_CoverSong: { QVariant v; Item *item = static_cast(index.internalPointer()); switch (item->getType()) { case T_Album: if (item->getSong().isEmpty()) { Song song; song.artist=item->getParent()->getId(); song.album=item->getId(); song.setIsFromOnlineService(constName); song.file=constName; // Just so that isEmpty() is false! QString id=static_cast(db)->getCoverUrl(/*T_Album==topLevel() ? static_cast(item)->getArtistId() : */item->getParent()->getId(), item->getId()); song.setExtraField(Song::OnlineImageUrl, QString("http://api.jamendo.com/get2/image/album/redirect/?id=%1&imagesize=300").arg(id)); item->setSong(song); } v.setValue(item->getSong()); break; case T_Artist: break; default: break; } return v; } } } return OnlineDbService::data(index, role); } QString JamendoService::name() const { return constName; } QString JamendoService::title() const { return QLatin1String("Jamendo"); } QString JamendoService::descr() const { return tr("The world's largest digital service for free music"); } OnlineXmlParser * JamendoService::createParser() { return new JamendoXmlParser(); } QUrl JamendoService::listingUrl() const { return QUrl(constListingUrl); } Song & JamendoService::fixPath(Song &s) const { s.file=QString(constStreamUrl).replace("id=%1", "id="+s.file); s.file+=FMT_MP3==format ? QLatin1String("mp31") : QLatin1String("ogg2"); s.genres[0]=FMT_MP3==format ? QLatin1String("mp3") : QLatin1String("ogg"); s.type=Song::OnlineSvrTrack; s.setIsFromOnlineService(name()); return encode(s); } void JamendoService::configure(QWidget *p) { JamendoSettingsDialog dlg(p); if (dlg.run(FMT_MP3==format)) { Format f=0==dlg.format() ? FMT_MP3 : FMT_Ogg; if (f!=format) { format=f; Configuration cfg(constName); cfg.set("format", formatStr(format)); } } } cantata-2.2.0/online/jamendoservice.h000066400000000000000000000034471316350454000175720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef JAMENDO_SERVICE_H #define JAMENDO_SERVICE_H #include "models/sqllibrarymodel.h" #include "onlinedbservice.h" class JamendoXmlParser : public OnlineXmlParser { public: int parse(QXmlStreamReader &xml); private: void parseArtist(QList *songList, QXmlStreamReader &xml); void parseAlbum(Song &song, QList *songList, QXmlStreamReader &xml); void parseSong(Song &song, const QString &albumGenre, QXmlStreamReader &xml); }; class JamendoService : public OnlineDbService { Q_OBJECT public: enum Format { FMT_MP3, FMT_Ogg }; JamendoService(QObject *p); QVariant data(const QModelIndex &index, int role) const; QString name() const; QString title() const; QString descr() const; OnlineXmlParser * createParser(); QUrl listingUrl() const; void configure(QWidget *p); int averageSize() const { return 100; } private: Song & fixPath(Song &s) const; private: Format format; }; #endif cantata-2.2.0/online/jamendosettingsdialog.cpp000066400000000000000000000031111316350454000214710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "jamendosettingsdialog.h" #include "support/buddylabel.h" #include JamendoSettingsDialog::JamendoSettingsDialog(QWidget *parent) : Dialog(parent) { setButtons(Ok|Cancel); setCaption(tr("Jamendo Settings")); QWidget *mw=new QWidget(this); QFormLayout *layout=new QFormLayout(mw); fmt=new QComboBox(mw); fmt->insertItem(0, tr("MP3")); fmt->insertItem(1, tr("Ogg")); layout->setWidget(0, QFormLayout::LabelRole, new BuddyLabel(tr("Streaming format:"), mw, fmt)); layout->setWidget(0, QFormLayout::FieldRole, fmt); layout->setMargin(0); setMainWidget(mw); } bool JamendoSettingsDialog::run(bool mp3) { fmt->setCurrentIndex(mp3 ? 0 : 1); return QDialog::Accepted==Dialog::exec(); } cantata-2.2.0/online/jamendosettingsdialog.h000066400000000000000000000022621316350454000211440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef JAMENDO_SETTINGS_DIALOG_H #define JAMENDO_SETTINGS_DIALOG_H #include "support/dialog.h" #include class JamendoSettingsDialog : public Dialog { Q_OBJECT public: JamendoSettingsDialog(QWidget *parent); bool run(bool mp3); int format() const { return fmt->currentIndex(); } private: QComboBox *fmt; }; #endif cantata-2.2.0/online/magnatuneservice.cpp000066400000000000000000000214271316350454000204650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "magnatuneservice.h" #include "magnatunesettingsdialog.h" #include "db/onlinedb.h" #include "models/roles.h" #include "support/icon.h" #include "support/configuration.h" #include #include int MagnatuneXmlParser::parse(QXmlStreamReader &xml) { QList *songList=new QList(); while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType() && QLatin1String("Track")==xml.name()) { songList->append(parseSong(xml)); if (songList->count()>500) { emit songs(songList); songList=new QList(); } } } if (songList->isEmpty()) { delete songList; } else { emit songs(songList); } return artists.count(); } Song MagnatuneXmlParser::parseSong(QXmlStreamReader &xml) { Song s; // QString artistImg; QString albumImg; while (!xml.atEnd()) { xml.readNext(); if (QXmlStreamReader::StartElement==xml.tokenType()) { QStringRef name = xml.name(); QString value = xml.readElementText(QXmlStreamReader::SkipChildElements); if (QLatin1String("artist")==name) { s.artist=value; } else if (QLatin1String("albumname")==name) { s.album=value; } else if (QLatin1String("trackname")==name) { s.title=value; } else if (QLatin1String("tracknum")==name) { s.track=value.toInt(); } else if (QLatin1String("year")==name) { s.year=value.toInt(); } else if (QLatin1String("magnatunegenres")==name) { QStringList genres=value.split(',', QString::SkipEmptyParts); // foreach (const QString &g, genres) { // s.addGenre(g); // } s.genres[0]=genres.first(); } else if (QLatin1String("seconds")==name) { s.time=value.toInt(); // } else if (QLatin1String("cover_small")==name) { // } else if (QLatin1String("albumsku")==name) { } else if (QLatin1String("url")==name) { s.file=value; } /*else if (QLatin1String("artistphoto")==name) { artistImg=value; }*/ else if (QLatin1String("cover_small")==name) { albumImg=value; } } else if (QXmlStreamReader::EndElement==xml.tokenType()) { break; } } if (!albumImg.isEmpty()) { QString key=s.artistOrComposer()+"-"+s.albumId(); if (!albumUrls.contains(key)) { albumUrls.insert(key); emit coverUrl(s.artistOrComposer(), s.album, albumImg); } } artists.insert(s.artistOrComposer()); return s; } static const QLatin1String constListingUrl("http://magnatune.com/info/song_info_xml.gz"); static const QLatin1String constName("magnatune"); static const char * constStreamingHostname = "streaming.magnatune.com"; static const char * constDownloadHostname = "download.magnatune.com"; QString MagnatuneService::membershipStr(MemberShip f, bool trans) { switch (f) { default: case MB_None : return trans ? tr("None") : QLatin1String("none"); case MB_Streaming : return trans ? tr("Streaming") : QLatin1String("streaming"); // case MB_Download : return trans ? tr("Download") : QLatin1String("download"); // TODO: Magnatune downloads! } } static MagnatuneService::MemberShip toMembership(const QString &f) { for (int i=0; i<=MagnatuneService::MB_Count; ++i) { if (f==MagnatuneService::membershipStr((MagnatuneService::MemberShip)i)) { return (MagnatuneService::MemberShip)i; } } return MagnatuneService::MB_None; } QString MagnatuneService::downloadTypeStr(DownloadType f, bool trans) { switch (f) { default: case DL_Mp3 : return trans ? tr("MP3 128k") : QLatin1String("mp3"); case DL_Mp3Vbr : return trans ? tr("MP3 VBR") : QLatin1String("vbr"); case DL_Ogg : return trans ? tr("Ogg Vorbis") : QLatin1String("ogg"); case DL_Flac : return trans ? tr("FLAC"): QLatin1String("flac"); case DL_Wav : return trans ? tr("WAV") : QLatin1String("wav"); } } static MagnatuneService::DownloadType toDownloadType(const QString &f) { for (int i=0; i<=MagnatuneService::DL_Count; ++i) { if (f==MagnatuneService::downloadTypeStr((MagnatuneService::DownloadType)i)) { return (MagnatuneService::DownloadType)i; } } return MagnatuneService::DL_Mp3; } MagnatuneService::MagnatuneService(QObject *p) : OnlineDbService(new OnlineDb(constName, p), p) { icn.addFile(":"+constName); useCovers(name()); Configuration cfg(constName); membership=toMembership(cfg.get("membership", membershipStr(MB_None))); download=toDownloadType(cfg.get("download", downloadTypeStr(DL_Mp3))); username=cfg.get("username", username); password=cfg.get("username", password); } QVariant MagnatuneService::data(const QModelIndex &index, int role) const { if (index.isValid()) { switch (role) { case Cantata::Role_CoverSong: { QVariant v; Item *item = static_cast(index.internalPointer()); switch (item->getType()) { case T_Album: if (item->getSong().isEmpty()) { Song song; song.artist=item->getParent()->getId(); song.album=item->getId(); song.setIsFromOnlineService(constName); song.file=constName; // Just so that isEmpty() is false! QString url=static_cast(db)->getCoverUrl(/*T_Album==topLevel() ? static_cast(item)->getArtistId() : */item->getParent()->getId(), item->getId()); song.setExtraField(Song::OnlineImageUrl, url); item->setSong(song); } v.setValue(item->getSong()); break; case T_Artist: break; default: break; } return v; } } } return OnlineDbService::data(index, role); } QString MagnatuneService::name() const { return constName; } QString MagnatuneService::title() const { return QLatin1String("Magnatune"); } QString MagnatuneService::descr() const { return tr("Online music from magnatune.com"); } OnlineXmlParser * MagnatuneService::createParser() { return new MagnatuneXmlParser(); } QUrl MagnatuneService::listingUrl() const { return QUrl(constListingUrl); } Song & MagnatuneService::fixPath(Song &s) const { s.type=Song::OnlineSvrTrack; if (MB_None!=membership) { QUrl url(s.file); url.setScheme("http"); url.setHost(MB_Streaming==membership ? constStreamingHostname : constDownloadHostname); url.setUserName(username); url.setPassword(password); // And remove the commercial QString path = url.path(); path.insert(path.lastIndexOf('.'), "_nospeech"); url.setPath(path); s.file=url.toString(); // TODO: Magnatune downloads! // if (MB_Download==membership) { // s.genre=downloadTypeStr(download); // } } s.setIsFromOnlineService(name()); return encode(s); } void MagnatuneService::configure(QWidget *p) { MagnatuneSettingsDialog dlg(p); if (dlg.run(membership, download, username, password) && (username!=dlg.username() || password!=dlg.password() || membership!=dlg.membership() || download!=dlg.download())) { username=dlg.username(); password=dlg.password(); membership=(MemberShip)dlg.membership(); download=(DownloadType)dlg.download(); Configuration cfg(constName); cfg.set("membership", membershipStr(membership)); cfg.set("download", downloadTypeStr(download)); cfg.set("username", username); cfg.set("password", password); } } cantata-2.2.0/online/magnatuneservice.h000066400000000000000000000041741316350454000201320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MAGNATUNE_SERVICE_H #define MAGNATUNE_SERVICE_H #include "models/sqllibrarymodel.h" #include "onlinedbservice.h" #include class MagnatuneXmlParser : public OnlineXmlParser { public: int parse(QXmlStreamReader &xml); private: Song parseSong(QXmlStreamReader &xml); private: QSet artists; QSet albumUrls; }; class MagnatuneService : public OnlineDbService { Q_OBJECT public: enum MemberShip { MB_None, MB_Streaming, // MB_Download, // TODO: Magnatune downloads! MB_Count }; enum DownloadType { DL_Mp3, DL_Mp3Vbr, DL_Ogg, DL_Flac, DL_Wav, DL_Count }; static QString membershipStr(MemberShip f, bool trans=false); static QString downloadTypeStr(DownloadType f, bool trans=false); MagnatuneService(QObject *p); QVariant data(const QModelIndex &index, int role) const; QString name() const; QString title() const; QString descr() const; OnlineXmlParser * createParser(); QUrl listingUrl() const; void configure(QWidget *p); int averageSize() const { return 10; } private: Song & fixPath(Song &s) const; private: MemberShip membership; DownloadType download; QString username; QString password; }; #endif cantata-2.2.0/online/magnatunesettingsdialog.cpp000066400000000000000000000065031316350454000220430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "magnatunesettingsdialog.h" #include "magnatuneservice.h" #include "support/buddylabel.h" #include MagnatuneSettingsDialog::MagnatuneSettingsDialog(QWidget *parent) : Dialog(parent) { setButtons(Ok|Cancel); setCaption(tr("Magnatune Settings")); QWidget *mw=new QWidget(this); QFormLayout *layout=new QFormLayout(mw); member=new QComboBox(mw); for (int i=0; iaddItem(MagnatuneService::membershipStr((MagnatuneService::MemberShip)i, true)); } user=new LineEdit(mw); pass=new LineEdit(mw); userLabel=new BuddyLabel(tr("Username:"), mw, user); passLabel=new BuddyLabel(tr("Password:"), mw, pass); pass->setEchoMode(QLineEdit::Password); dl=new QComboBox(mw); for (int i=0; i<=MagnatuneService::DL_Count; ++i) { dl->addItem(MagnatuneService::downloadTypeStr((MagnatuneService::DownloadType)i, true)); } layout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); layout->setWidget(0, QFormLayout::LabelRole, new BuddyLabel(tr("Membership:"), mw, member)); layout->setWidget(0, QFormLayout::FieldRole, member); layout->setWidget(1, QFormLayout::LabelRole, userLabel); layout->setWidget(1, QFormLayout::FieldRole, user); layout->setWidget(2, QFormLayout::LabelRole, passLabel); layout->setWidget(2, QFormLayout::FieldRole, pass); BuddyLabel *dlLabel=new BuddyLabel(tr("Downloads:"), mw, dl); layout->setWidget(3, QFormLayout::LabelRole, dlLabel); layout->setWidget(3, QFormLayout::FieldRole, dl); layout->setMargin(0); dlLabel->setVisible(false); // TODO: Magnatune downloads! dl->setVisible(false); // TODO: Magnatune downloads! setMainWidget(mw); connect(member, SIGNAL(currentIndexChanged(int)), SLOT(membershipChanged(int))); } bool MagnatuneSettingsDialog::run(int m, int d, const QString &u, const QString &p) { member->setCurrentIndex(m); dl->setCurrentIndex(d); user->setText(u); user->setEnabled(m); pass->setText(p); pass->setEnabled(m); userLabel->setEnabled(m); passLabel->setEnabled(m); // dl->setEnabled(MagnatuneService::MB_Download==m); // TODO: Magnatune downloads! return QDialog::Accepted==Dialog::exec(); } void MagnatuneSettingsDialog::membershipChanged(int i) { user->setEnabled(0!=i); pass->setEnabled(0!=i); userLabel->setEnabled(0!=i); passLabel->setEnabled(0!=i); // dl->setEnabled(MagnatuneService::MB_Download==i); // TODO: Magnatune downloads! } cantata-2.2.0/online/magnatunesettingsdialog.h000066400000000000000000000031741316350454000215110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MAGNATUNE_SETTINGS_DIALOG_H #define MAGNATUNE_SETTINGS_DIALOG_H #include "support/dialog.h" #include "support/lineedit.h" #include class BuddyLabel; class MagnatuneSettingsDialog : public Dialog { Q_OBJECT public: MagnatuneSettingsDialog(QWidget *parent); bool run(int m, int d, const QString &u, const QString &p); int membership() const { return member->currentIndex(); } int download() const { return dl->currentIndex(); } QString username() const { return user->text().trimmed(); } QString password() const { return pass->text().trimmed(); } private Q_SLOTS: void membershipChanged(int i); private: QComboBox *member; QComboBox *dl; LineEdit *user; LineEdit *pass; BuddyLabel *userLabel; BuddyLabel *passLabel; }; #endif cantata-2.2.0/online/onlinedbservice.cpp000066400000000000000000000147151316350454000203020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinedbservice.h" #include "gui/covers.h" #include "models/roles.h" #include "network/networkaccessmanager.h" #include "qtiocompressor/qtiocompressor.h" #include "db/onlinedb.h" #include OnlineXmlParser::OnlineXmlParser() { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); connect(this, SIGNAL(startParsing(NetworkJob*)), this, SLOT(doParsing(NetworkJob*))); } OnlineXmlParser::~OnlineXmlParser() { } void OnlineXmlParser::start(NetworkJob *job) { emit startParsing(job); } void OnlineXmlParser::doParsing(NetworkJob *job) { QtIOCompressor comp(job->actualJob()); comp.setStreamFormat(QtIOCompressor::GzipFormat); if (comp.open(QIODevice::ReadOnly)) { QXmlStreamReader reader; reader.setDevice(&comp); emit startUpdate(); int artistCount=parse(reader); if (artistCount>0) { emit endUpdate(); emit stats(artistCount); } else { emit error(tr("Failed to parse")); emit abortUpdate(); } } else { emit error(tr("Failed to parse")); } emit complete(); } OnlineDbService::OnlineDbService(LibraryDb *d, QObject *p) : SqlLibraryModel(d, p, T_Genre) , lastPc(-1) , job(0) { connect(Covers::self(), SIGNAL(cover(Song,QImage,QString)), this, SLOT(cover(Song,QImage,QString))); } QVariant OnlineDbService::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: if (!status.isEmpty()) { return status; } if (!stats.isEmpty()) { return stats; } return descr(); case Qt::DecorationRole: return icon(); } } return SqlLibraryModel::data(index, role); } bool OnlineDbService::previouslyDownloaded() const { // Create DB, if it does not already exist static_cast(db)->create(); return 0!=db->getCurrentVersion(); } void OnlineDbService::open() { if (0==rowCount(QModelIndex())) { // Create DB, if it does not already exist static_cast(db)->create(); libraryUpdated(); updateStats(); } } void OnlineDbService::download(bool redownload) { if (job) { return; } if (redownload || !previouslyDownloaded()) { job=NetworkAccessManager::self()->get(QUrl(listingUrl())); connect(job, SIGNAL(downloadPercent(int)), this, SLOT(downloadPercent(int))); connect(job, SIGNAL(finished()), this, SLOT(downloadFinished())); lastPc=-1; downloadPercent(0); } } void OnlineDbService::abort() { if (job) { job->cancelAndDelete(); job=0; } db->abortUpdate(); } void OnlineDbService::cover(const Song &song, const QImage &img, const QString &file) { if (file.isEmpty() || img.isNull() || !song.isFromOnlineService() || song.onlineService()!=name()) { return; } const Item *genre=root ? root->getChild(song.genres[0]) : 0; if (genre) { const Item *artist=static_cast(genre)->getChild(song.artistOrComposer()); if (artist) { const Item *album=static_cast(artist)->getChild(song.albumId()); if (album) { QModelIndex idx=index(album->getRow(), 0, index(artist->getRow(), 0, index(genre->getRow(), 0, QModelIndex()))); emit dataChanged(idx, idx); } } } } void OnlineDbService::updateStatus(const QString &msg) { status=msg; emit dataChanged(QModelIndex(), QModelIndex()); } void OnlineDbService::downloadPercent(int pc) { if (lastPc!=pc) { lastPc=pc; updateStatus(tr("Downloading...%1%").arg(pc)); } } void OnlineDbService::downloadFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } if (reply!=job) { reply->deleteLater(); return; } if (reply->ok()) { // Ensure DB is created static_cast(db)->create(); updateStatus(tr("Parsing music list....")); OnlineXmlParser *parser=createParser(); db->clear(); connect(parser, SIGNAL(startUpdate()), static_cast(db), SLOT(startUpdate())); connect(parser, SIGNAL(endUpdate()), static_cast(db), SLOT(endUpdate())); connect(parser, SIGNAL(abortUpdate()), static_cast(db), SLOT(abortUpdate())); connect(parser, SIGNAL(stats(int)), static_cast(db), SLOT(insertStats(int))); connect(parser, SIGNAL(coverUrl(QString,QString,QString)), static_cast(db), SLOT(storeCoverUrl(QString,QString,QString))); connect(parser, SIGNAL(songs(QList*)), static_cast(db), SLOT(insertSongs(QList*))); connect(parser, SIGNAL(complete()), job, SLOT(deleteLater())); connect(parser, SIGNAL(complete()), this, SLOT(updateStats())); connect(parser, SIGNAL(error(QString)), this, SIGNAL(error(QString))); connect(parser, SIGNAL(complete()), parser, SLOT(deleteLater())); parser->start(reply); } else { reply->deleteLater(); updateStatus(QString()); emit error(tr("Failed to download")); } job=0; } void OnlineDbService::updateStats() { int numArtists=static_cast(db)->getStats(); if (numArtists>0) { stats=tr("%n Artist(s)", "", numArtists); } else { stats=QString(); } updateStatus(QString()); } cantata-2.2.0/online/onlinedbservice.h000066400000000000000000000051071316350454000177420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_DB_SERVICE_H #define ONLINE_DB_SERVICE_H #include "onlineservice.h" #include "models/sqllibrarymodel.h" class NetworkJob; class Thread; class QXmlStreamReader; struct Song; class OnlineXmlParser : public QObject { Q_OBJECT public: OnlineXmlParser(); virtual ~OnlineXmlParser(); void start(NetworkJob *job); virtual int parse(QXmlStreamReader &xml) = 0; Q_SIGNALS: void songs(QList *s); void coverUrl(const QString &artist, const QString &album, const QString &cover); void startUpdate(); void endUpdate(); void abortUpdate(); void stats(int numArtists); void complete(); void error(const QString &msg); void startParsing(NetworkJob *job); private Q_SLOTS: void doParsing(NetworkJob *job); private: Thread *thread; }; class OnlineDbService : public SqlLibraryModel, public OnlineService { Q_OBJECT public: OnlineDbService(LibraryDb *d, QObject *p); virtual ~OnlineDbService() { } void createDb(); QVariant data(const QModelIndex &index, int role) const; bool previouslyDownloaded() const; bool isDownloading() { return 0!=job; } void open(); void download(bool redownload); virtual OnlineXmlParser * createParser() = 0; virtual QUrl listingUrl() const = 0; virtual void configure(QWidget *p) =0; virtual int averageSize() const = 0; public Q_SLOTS: void abort(); Q_SIGNALS: void error(const QString &msg); private Q_SLOTS: void cover(const Song &song, const QImage &img, const QString &file); void updateStatus(const QString &msg); void downloadPercent(int pc); void downloadFinished(); void updateStats(); protected: int lastPc; QString status; QString stats; NetworkJob *job; }; #endif cantata-2.2.0/online/onlinedbwidget.cpp000066400000000000000000000164531316350454000201260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinedbwidget.h" #include "gui/stdactions.h" #include "widgets/itemview.h" #include "widgets/menubutton.h" #include "widgets/icons.h" #include "support/action.h" #include "support/messagebox.h" #include "support/configuration.h" #include OnlineDbWidget::OnlineDbWidget(OnlineDbService *s, QWidget *p) : SinglePageWidget(p) , configGroup(s->name()) , srv(s) { srv->setParent(this); view->setModel(s); view->alwaysShowHeader(); Configuration config(configGroup); view->setMode(ItemView::Mode_DetailedTree); view->load(config); srv->load(config); MenuButton *menu=new MenuButton(this); menu->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); menu->addAction(createMenuGroup(tr("Group By"), QList() << MenuItem(tr("Genre"), SqlLibraryModel::T_Genre) << MenuItem(tr("Artist"), SqlLibraryModel::T_Artist), srv->topLevel(), this, SLOT(groupByChanged()))); Action *configureAction=new Action(Icons::self()->configureIcon, tr("Configure"), this); connect(configureAction, SIGNAL(triggered()), SLOT(configure())); menu->addAction(configureAction); init(ReplacePlayQueue|AppendToPlayQueue|Refresh, QList() << menu); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(view, SIGNAL(updateToPlayQueue(QModelIndex,bool)), this, SLOT(updateToPlayQueue(QModelIndex,bool))); view->setOpenAfterSearch(SqlLibraryModel::T_Album!=srv->topLevel()); connect(StdActions::self()->addRandomAlbumToPlayQueueAction, SIGNAL(triggered()), SLOT(addRandomAlbum())); } OnlineDbWidget::~OnlineDbWidget() { Configuration config(configGroup); view->save(config); srv->save(config); } void OnlineDbWidget::groupByChanged() { QAction *act=qobject_cast(sender()); if (!act) { return; } int mode=act->property(constValProp).toInt(); srv->setTopLevel((SqlLibraryModel::Type)mode); view->setOpenAfterSearch(SqlLibraryModel::T_Album!=srv->topLevel()); } QStringList OnlineDbWidget::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } return srv->filenames(selected, allowPlaylists); } QList OnlineDbWidget::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return srv->songs(selected, allowPlaylists); } void OnlineDbWidget::showEvent(QShowEvent *e) { SinglePageWidget::showEvent(e); if (srv->isDownloading() || srv->rowCount(QModelIndex())) { return; } if (srv->previouslyDownloaded()) { srv->open(); } else { QTimer::singleShot(0, this, SLOT(firstTimePrompt())); } } void OnlineDbWidget::firstTimePrompt() { if (MessageBox::No==MessageBox::questionYesNo(this, srv->averageSize() ? tr("The music listing needs to be downloaded, this can consume over %1Mb of disk space").arg(srv->averageSize()) : tr("Dowload music listing?"), QString(), GuiItem(tr("Download")), StdGuiItem::cancel())) { emit close(); } else { srv->download(false); } } void OnlineDbWidget::headerClicked(int level) { if (0==level) { emit close(); } } void OnlineDbWidget::addRandomAlbum() { if (!isVisible()) { return; } QStringList genres; QStringList artists; QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... foreach (const QModelIndex &idx, selected) { SqlLibraryModel::Item *item=static_cast(idx.internalPointer()); switch (item->getType()) { case SqlLibraryModel::T_Genre: genres.append(item->getId()); break; case SqlLibraryModel::T_Artist: artists.append(item->getId()); default: break; } } // If all items selected, then just choose random of all albums if (SqlLibraryModel::T_Genre==srv->topLevel() && genres.size()==srv->rowCount(QModelIndex())) { genres=QStringList(); } if (SqlLibraryModel::T_Artist==srv->topLevel() && artists.size()==srv->rowCount(QModelIndex())) { artists=QStringList(); } LibraryDb::Album album=srv->getRandomAlbum(genres, artists); if (album.artist.isEmpty() || album.id.isEmpty()) { return; } QList songs=srv->getAlbumTracks(album.artist, album.id); if (!songs.isEmpty()) { QStringList files; foreach (const Song &s, songs) { files.append(s.file); } emit add(files, /*replace ? MPDConnection::ReplaceAndplay : */MPDConnection::Append, 0, false); } } void OnlineDbWidget::doSearch() { srv->search(view->searchText()); } // TODO: Cancel download? void OnlineDbWidget::refresh() { if (!srv->isDownloading() && MessageBox::Yes==MessageBox::questionYesNo(this, tr("Re-download music listing?"), QString(), GuiItem(tr("Download")), StdGuiItem::cancel())) { srv->download(true); } } void OnlineDbWidget::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool allowRandomAlbum=isVisible() && !selected.isEmpty(); if (allowRandomAlbum) { foreach (const QModelIndex &idx, selected) { if (SqlLibraryModel::T_Track==static_cast(idx.internalPointer())->getType() || SqlLibraryModel::T_Album==static_cast(idx.internalPointer())->getType()) { allowRandomAlbum=false; break; } } } StdActions::self()->addRandomAlbumToPlayQueueAction->setVisible(allowRandomAlbum); } void OnlineDbWidget::configure() { srv->configure(this); } void OnlineDbWidget::updateToPlayQueue(const QModelIndex &idx, bool replace) { QStringList files=srv->filenames(QModelIndexList() << idx, true); if (!files.isEmpty()) { emit add(files, replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0, false); } } cantata-2.2.0/online/onlinedbwidget.h000066400000000000000000000032061316350454000175630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_DB_WIDGET_H #define ONLINE_DB_WIDGET_H #include #include "widgets/singlepagewidget.h" #include "onlinedbservice.h" class OnlineDbWidget : public SinglePageWidget { Q_OBJECT public: OnlineDbWidget(OnlineDbService *s, QWidget *p); virtual ~OnlineDbWidget(); QStringList selectedFiles(bool allowPlaylists) const; QList selectedSongs(bool allowPlaylists) const; void showEvent(QShowEvent *e); private Q_SLOTS: void groupByChanged(); void firstTimePrompt(); void headerClicked(int level); void configure(); void updateToPlayQueue(const QModelIndex &idx, bool replace); void addRandomAlbum(); private: void doSearch(); void refresh(); void controlActions(); private: QString configGroup; OnlineDbService *srv; }; #endif cantata-2.2.0/online/onlinedevice.cpp000066400000000000000000000066651316350454000176000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinedevice.h" #include "support/utils.h" #include "network/networkaccessmanager.h" #include "mpd-interface/mpdconnection.h" #include "models/mpdlibrarymodel.h" #include void OnlineDevice::copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover) { Q_UNUSED(copyCover) jobAbortRequested=false; QString baseDir=MPDConnection::self()->getDetails().dir; QString dest(baseDir+musicPath); if (!overwrite && (QFile::exists(dest) || MpdLibraryModel::self()->songExists(s))) { emit actionStatus(SongExists); return; } overWrite=overwrite; lastProg=-1; currentDestFile=baseDir+musicPath; currentSong=s; QDir dir(Utils::getDir(dest)); if (!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), baseDir)) { emit actionStatus(DirCreationFaild); return; } job=NetworkAccessManager::self()->get(QUrl(s.file)); connect(job, SIGNAL(finished()), SLOT(downloadFinished())); connect(job, SIGNAL(downloadProgress(qint64,qint64)), SLOT(downloadProgress(qint64,qint64))); } void OnlineDevice::downloadFinished() { NetworkJob *reply=qobject_cast(sender()); if (!reply) { return; } reply->deleteLater(); if (reply!=job) { return; } if (reply->ok()) { if (overWrite && QFile::exists(currentDestFile)) { QFile::remove(currentDestFile); } QFile f(currentDestFile); if (f.open(QIODevice::WriteOnly)) { f.write(reply->readAll()); currentSong.file=currentDestFile.mid(MPDConnection::self()->getDetails().dir.length()); QString origPath; if (MPDConnection::self()->isMopidy()) { origPath=currentSong.file; currentSong.file=Song::encodePath(currentSong.file); } Utils::setFilePerms(currentDestFile); // MusicLibraryModel::self()->addSongToList(currentSong); // DirViewModel::self()->addFileToList(origPath.isEmpty() ? currentSong.file : origPath, // origPath.isEmpty() ? QString() : currentSong.file); emit actionStatus(Ok); } else { emit actionStatus(WriteFailed); } } else { emit actionStatus(DownloadFailed); } } void OnlineDevice::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (jobAbortRequested || bytesTotal<=1) { return; } int prog=(bytesReceived*100)/bytesTotal; if (prog!=lastProg) { lastProg=prog; emit progress(prog); } } cantata-2.2.0/online/onlinedevice.h000066400000000000000000000036721316350454000172400ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_DEVICE_H #define ONLINE_DEVICE_H #include "devices/device.h" #include "mpd-interface/song.h" class NetworkJob; class OnlineDevice : public Device { Q_OBJECT public: OnlineDevice() : Device(0, QString(), QString()), lastProg(-1), overWrite(false), job(0) { } virtual ~OnlineDevice() { } bool isConnected() const { return true; } void rescan(bool) { } bool isRefreshing() const { return false; } void stop() { } QString path() const { return QString(); } void addSong(const Song&, bool, bool) { } void copySongTo(const Song &s, const QString &musicPath, bool overwrite, bool copyCover); void removeSong(const Song&) { } void cleanDirs(const QSet&) { } double usedCapacity() { return 0.0; } QString capacityString() { return QString(); } qint64 freeSpace() { return 0; } DevType devType() const { return RemoteFs; } void saveOptions() { } private Q_SLOTS: void downloadFinished(); void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); private: int lastProg; bool overWrite; NetworkJob *job; }; #endif cantata-2.2.0/online/onlinesearchservice.cpp000066400000000000000000000035621316350454000211600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinesearchservice.h" #include "models/roles.h" #include "network/networkaccessmanager.h" OnlineSearchService::OnlineSearchService(QObject *p) : SearchModel(p) , job(0) { } QVariant OnlineSearchService::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: return job ? tr("Searching...") : descr(); case Qt::DecorationRole: return icon(); default: break; } } switch (role) { case Cantata::Role_ListImage: return false; case Cantata::Role_CoverSong: return QVariant(); default: break; } return SearchModel::data(index, role); } Song & OnlineSearchService::fixPath(Song &s) const { s.setIsFromOnlineService(name()); s.album=title(); return encode(s); } void OnlineSearchService::cancel() { if (job) { job->cancelAndDelete(); job=0; } } cantata-2.2.0/online/onlinesearchservice.h000066400000000000000000000026011316350454000206160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINESEARCH_SERVICE_H #define ONLINESEARCH_SERVICE_H #include "onlineservice.h" #include "models/searchmodel.h" class NetworkJob; class OnlineSearchService : public SearchModel, public OnlineService { Q_OBJECT public: OnlineSearchService(QObject *p); virtual ~OnlineSearchService() { cancel(); } QVariant data(const QModelIndex &index, int role) const; Song & fixPath(Song &s) const; virtual void search(const QString &key, const QString &value) =0; virtual void cancel(); protected: NetworkJob *job; }; #endif cantata-2.2.0/online/onlinesearchwidget.cpp000066400000000000000000000052001316350454000207720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinesearchwidget.h" #include "widgets/itemview.h" #include "support/action.h" #include "support/messagebox.h" #include OnlineSearchWidget::OnlineSearchWidget(OnlineSearchService *s, QWidget *p) : SinglePageWidget(p) , srv(s) { statsLabel=new SqueezedTextLabel(this); view->setModel(s); statsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); init(ReplacePlayQueue|AppendToPlayQueue, QList() << statsLabel); view->alwaysShowHeader(); view->setPermanentSearch(); view->setMode(ItemView::Mode_List); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(srv, SIGNAL(statsUpdated(int, quint32)), this, SLOT(statsUpdated(int, quint32))); statsUpdated(0, 0); } OnlineSearchWidget::~OnlineSearchWidget() { } void OnlineSearchWidget::showEvent(QShowEvent *e) { SinglePageWidget::showEvent(e); view->focusSearch(); } QStringList OnlineSearchWidget::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } return srv->filenames(selected, allowPlaylists); } QList OnlineSearchWidget::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return srv->songs(selected, allowPlaylists); } void OnlineSearchWidget::headerClicked(int level) { if (0==level) { emit close(); } } void OnlineSearchWidget::statsUpdated(int songs, quint32 time) { statsLabel->setText(0==songs ? tr("No tracks found.") : tr("%n Tracks (%1)", "", songs).arg(Utils::formatDuration(time))); } void OnlineSearchWidget::doSearch() { srv->search(QString(), view->searchText()); } cantata-2.2.0/online/onlinesearchwidget.h000066400000000000000000000030711316350454000204430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINESEARCH_WIDGET_H #define ONLINESEARCH_WIDGET_H #include #include "widgets/singlepagewidget.h" #include "onlinesearchservice.h" class SqueezedTextLabel; class OnlineSearchWidget : public SinglePageWidget { Q_OBJECT public: OnlineSearchWidget(OnlineSearchService *s, QWidget *p); virtual ~OnlineSearchWidget(); QStringList selectedFiles(bool allowPlaylists) const; QList selectedSongs(bool allowPlaylists) const; void setView(int) { } void showEvent(QShowEvent *e); private Q_SLOTS: void headerClicked(int level); void statsUpdated(int songs, quint32 time); private: void doSearch(); private: OnlineSearchService *srv; SqueezedTextLabel *statsLabel; }; #endif cantata-2.2.0/online/onlineservice.cpp000066400000000000000000000071411316350454000177670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlineservice.h" #include "mpd-interface/song.h" #include static const QString constUrlGuard=QLatin1String("#{SONG_DETAILS}"); static const QString constDeliminator=QLatin1String("<@>"); static inline QString fixString(QString str) { return str.replace(constDeliminator, " ").replace("\n", ""); } Song & OnlineService::encode(Song &song) { song.file=QUrl(song.file).toEncoded()+constUrlGuard+ fixString(song.artist)+constDeliminator+ fixString(song.albumartist)+constDeliminator+ fixString(song.album)+constDeliminator+ fixString(song.title)+constDeliminator+ fixString(song.genres[0])+constDeliminator+ QString::number(song.time)+constDeliminator+ QString::number(song.year)+constDeliminator+ QString::number(song.track)+constDeliminator+ QString::number(song.disc)+constDeliminator+ song.onlineService(); return song; } bool OnlineService::decode(Song &song) { if (!song.file.startsWith(QLatin1String("http://")) && !song.file.startsWith(QLatin1String("https://"))) { return false; } int pos=song.file.indexOf(constUrlGuard); if (pos>0) { QStringList parts=song.file.mid(pos+constUrlGuard.length()).split(constDeliminator); if (parts.length()>=10) { song.artist=parts.at(0); song.albumartist=parts.at(1); song.album=parts.at(2); song.title=parts.at(3); song.genres[0]=parts.at(4); song.time=parts.at(5).toUInt(); song.year=parts.at(6).toUInt(); song.track=parts.at(7).toUInt(); song.disc=parts.at(8).toUInt(); song.type=Song::OnlineSvrTrack; song.setIsFromOnlineService(parts.at(9)); song.file=song.file.left(pos); return true; } } return false; } static QSet servicesWithCovers; static QSet servicesWithCoversIfCached; QString OnlineService::iconPath(const QString &srv) { QString file=QString(CANTATA_SYS_ICONS_DIR+srv+".png"); if (!QFile::exists(file)) { file=QString(CANTATA_SYS_ICONS_DIR+srv+".svg"); } return file; } bool OnlineService::isPodcasts(const QString &srv) { return QLatin1String("podcasts")==srv; } bool OnlineService::showLogoAsCover(const Song &s) { return s.isFromOnlineService() && !servicesWithCovers.contains(s.onlineService()) && (!servicesWithCoversIfCached.contains(s.onlineService()) || s.extraField(Song::OnlineImageCacheName).isEmpty()); } void OnlineService::useCovers(const QString &name, bool onlyIfCache) { if (onlyIfCache) { servicesWithCoversIfCached.insert(name); } else { servicesWithCovers.insert(name); } } cantata-2.2.0/online/onlineservice.h000066400000000000000000000027571316350454000174440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_SERVICE_H #define ONLINE_SERVICE_H #include "support/icon.h" class QString; struct Song; class OnlineService { public: virtual ~OnlineService() { } static QString iconPath(const QString &srv); static bool showLogoAsCover(const Song &s); static bool isPodcasts(const QString &srv); static Song & encode(Song &s); static bool decode(Song &song); virtual QString name() const =0; virtual QString title() const =0; virtual QString descr() const =0; const Icon & icon() const { return icn; } protected: static void useCovers(const QString &name, bool onlyIfCache=false); protected: Icon icn; }; #endif cantata-2.2.0/online/onlineservicespage.cpp000066400000000000000000000052351316350454000210110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlineservicespage.h" #include "onlinedbwidget.h" #include "jamendoservice.h" #include "magnatuneservice.h" #include "soundcloudservice.h" #include "onlinesearchwidget.h" #include "podcastservice.h" #include "podcastwidget.h" #include "streams/streamspage.h" #include "models/streamsmodel.h" #include "support/configuration.h" OnlineServicesPage::OnlineServicesPage(QWidget *p) : MultiPageWidget(p) { addPage(StreamsModel::self()->name(), StreamsModel::self()->icon(), StreamsModel::self()->title(), StreamsModel::self()->descr(), new StreamsPage(this)); JamendoService *jamendo=new JamendoService(this); addPage(jamendo->name(), jamendo->icon(), jamendo->title(), jamendo->descr(), new OnlineDbWidget(jamendo, this)); connect(jamendo, SIGNAL(error(QString)), this, SIGNAL(error(QString))); MagnatuneService *magnatune=new MagnatuneService(this); addPage(magnatune->name(), magnatune->icon(), magnatune->title(), magnatune->descr(), new OnlineDbWidget(magnatune, this)); connect(magnatune, SIGNAL(error(QString)), this, SIGNAL(error(QString))); SoundCloudService *soundcloud=new SoundCloudService(this); addPage(soundcloud->name(), soundcloud->icon(), soundcloud->title(), soundcloud->descr(), new OnlineSearchWidget(soundcloud, this)); podcast=new PodcastService(this); addPage(podcast->name(), podcast->icon(), podcast->title(), podcast->descr(), new PodcastWidget(podcast, this)); connect(podcast, SIGNAL(error(QString)), this, SIGNAL(error(QString))); Configuration config(metaObject()->className()); load(config); } OnlineServicesPage::~OnlineServicesPage() { Configuration config(metaObject()->className()); save(config); } bool OnlineServicesPage::isDownloading() { return podcast->isDownloading(); } void OnlineServicesPage::cancelAll() { podcast->cancelAll(); } cantata-2.2.0/online/onlineservicespage.h000066400000000000000000000025221316350454000204520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_SERVICES_PAGE_H #define ONLINE_SERVICES_PAGE_H #include "onlineservice.h" #include "widgets/multipagewidget.h" class PodcastService; class OnlineServicesPage : public MultiPageWidget { Q_OBJECT public: OnlineServicesPage(QWidget *p); virtual ~OnlineServicesPage(); bool isDownloading(); void cancelAll(); bool isEanbeld() { return true; } void setEnabled(bool) { } Q_SIGNALS: void error(const QString &msg); private: PodcastService *podcast; }; #endif cantata-2.2.0/online/onlinesettings.cpp000066400000000000000000000063551316350454000201750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "onlinesettings.h" #include "models/onlineservicesmodel.h" #include "onlineservice.h" #include "widgets/basicitemdelegate.h" #include "support/icon.h" #include enum Roles { KeyRole = Qt::UserRole, ConfigurableRole }; OnlineSettings::OnlineSettings(QWidget *p) : QWidget(p) { setupUi(this); providers->setItemDelegate(new BasicItemDelegate(providers)); providers->setSortingEnabled(true); int iSize=Icon::stdSize(QApplication::fontMetrics().height()*1.25); providers->setIconSize(QSize(iSize, iSize)); connect(providers, SIGNAL(currentRowChanged(int)), SLOT(currentProviderChanged(int))); connect(configureButton, SIGNAL(clicked()), this, SLOT(configure())); configureButton->setEnabled(false); } void OnlineSettings::load() { QList provs=OnlineServicesModel::self()->getProviders(); foreach (const OnlineServicesModel::Provider &prov, provs) { QListWidgetItem *item=new QListWidgetItem(prov.name, providers); item->setCheckState(prov.hidden ? Qt::Unchecked : Qt::Checked); item->setData(KeyRole, prov.key); item->setData(ConfigurableRole, prov.configurable); item->setIcon(prov.icon); } } void OnlineSettings::save() { QSet disabled; for (int i=0; icount(); ++i) { QListWidgetItem *item=providers->item(i); if (Qt::Unchecked==item->checkState()) { QString id=item->data(KeyRole).toString(); if (OnlineServicesModel::self()->serviceIsBusy(id)) { item->setCheckState(Qt::Checked); } else { disabled.insert(id); } } } OnlineServicesModel::self()->setHiddenProviders(disabled); } void OnlineSettings::currentProviderChanged(int row) { bool enableConfigure=false; if (row>=0) { QListWidgetItem *item=providers->item(row); enableConfigure=item->data(ConfigurableRole).toBool(); } configureButton->setEnabled(enableConfigure); } void OnlineSettings::configure() { int row=providers->currentRow(); if (row<0) { return; } QListWidgetItem *item=providers->item(row); if (!item->data(ConfigurableRole).toBool()) { return; } OnlineService *srv=OnlineServicesModel::self()->service(item->data(KeyRole).toString()); if (srv && srv->canConfigure()) { srv->configure(this); } } cantata-2.2.0/online/onlinesettings.h000066400000000000000000000023331316350454000176320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ONLINE_SETTINGS_H #define ONLINE_SETTINGS_H #include "ui_onlinesettings.h" class QListWidgetItem; class OnlineSettings : public QWidget, private Ui::OnlineSettings { Q_OBJECT public: OnlineSettings(QWidget *p); virtual ~OnlineSettings() { } void load(); void save(); private Q_SLOTS: void currentProviderChanged(int row); void configure(); }; #endif cantata-2.2.0/online/onlinesettings.ui000066400000000000000000000027461316350454000200300ustar00rootroot00000000000000 OnlineSettings 0 Use the checkboxes below to configure the list of active services. true Qt::Horizontal QSizePolicy::MinimumExpanding 0 0 0 0 Configure Service cantata-2.2.0/online/opmlparser.cpp000066400000000000000000000115501316350454000173050ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "opmlparser.h" #include #include using namespace OpmlParser; static bool parseUntil(QXmlStreamReader &reader, const QString &elem) { while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement() && reader.name() == elem) { return true; } } return false; } static void consumeCurrentElement(QXmlStreamReader &reader) { int level = 1; while (0!=level && !reader.atEnd()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement: ++level; break; case QXmlStreamReader::EndElement: --level; break; default: break; } } } static void parseOutline(QXmlStreamReader &reader, Category &cat) { while (!reader.atEnd()) { QXmlStreamReader::TokenType type = reader.readNext(); switch (type) { case QXmlStreamReader::StartElement: { const QStringRef name = reader.name(); if (name != QLatin1String("outline")) { consumeCurrentElement(reader); continue; } QXmlStreamAttributes attributes = reader.attributes(); if (QLatin1String("rss")==attributes.value(QLatin1String("type")).toString() || QLatin1String("link")==attributes.value(QLatin1String("type")).toString()) { // Parse the feed and add it to this container Podcast podcast; podcast.name=attributes.value("title").toString().trimmed(); if (podcast.name.isEmpty()) { podcast.name=attributes.value(QLatin1String("text")).toString(); } podcast.description=attributes.value("description").toString().trimmed(); if (podcast.description.isEmpty()) { podcast.description=attributes.value(QLatin1String("text")).toString(); } podcast.htmlUrl=attributes.value("htmlUrl").toString().trimmed(); podcast.url=QUrl::fromEncoded(attributes.value(QLatin1String("xmlUrl")).toString().toLatin1()); if (podcast.url.isEmpty()) { podcast.url=QUrl::fromEncoded(attributes.value(QLatin1String("url")).toString().toLatin1()); } podcast.image=QUrl::fromEncoded(attributes.value(QLatin1String("imageUrl")).toString().toLatin1()); if (podcast.image.isEmpty()) { podcast.image=QUrl::fromEncoded(attributes.value(QLatin1String("imageHref")).toString().toLatin1()); } cat.podcasts.append(podcast); // Consume any children and the EndElement. consumeCurrentElement(reader); } else { // Create a new child container Category child; // Take the name from the fullname attribute first if it exists. child.name = attributes.value(QLatin1String("fullname")).toString().trimmed(); if (child.name.isEmpty()) { child.name = attributes.value(QLatin1String("title")).toString().trimmed(); } if (child.name.isEmpty()) { child.name = attributes.value(QLatin1String("text")).toString().trimmed(); } // Parse its contents and add it to this container parseOutline(reader, child); cat.categories.append(child); } break; } case QXmlStreamReader::EndElement: return; default: break; } } } Category OpmlParser::parse(QIODevice *dev) { Category cat; QXmlStreamReader reader(dev); if (parseUntil(reader, QLatin1String("body"))) { parseOutline(reader, cat); } return cat; } Category OpmlParser::parse(const QByteArray &data) { Category cat; QXmlStreamReader reader(data); if (parseUntil(reader, QLatin1String("body"))) { parseOutline(reader, cat); } return cat; } cantata-2.2.0/online/opmlparser.h000066400000000000000000000025171316350454000167550ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef OPMLPARSER_H #define OPMLPARSER_H #include #include #include class QIODevice; namespace OpmlParser { struct Podcast { Podcast() { } QString name; QString description; QString htmlUrl; QUrl url; QUrl image; }; struct Category { QString name; QList podcasts; QList categories; bool isValid() const { return !name.isEmpty(); } }; Category parse(QIODevice *dev); Category parse(const QByteArray &data); } #endif cantata-2.2.0/online/podcast_directories.xml000066400000000000000000000011541316350454000211670ustar00rootroot00000000000000 cantata-2.2.0/online/podcastsearchdialog.cpp000066400000000000000000000547101316350454000211310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "podcastsearchdialog.h" #include "support/pagewidget.h" #include "support/lineedit.h" #include "network/networkaccessmanager.h" #include "opmlparser.h" #include "widgets/icons.h" #include "support/spinner.h" #include "widgets/basicitemdelegate.h" #include "podcastservice.h" #include "support/utils.h" #include "support/action.h" #include "widgets/textbrowser.h" #include "support/messagewidget.h" #include "gui/covers.h" #include "rssparser.h" #include "podcastservice.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int iCount=0; static QCache imageCache(200*1024); static int maxImageSize=-1; static const char * constOrigUrlProperty="orig-url"; enum Roles { IsPodcastRole = Qt::UserRole, UrlRole, ImageUrlRole, DescriptionRole, WebPageUrlRole }; QString PodcastSearchDialog::constCacheDir=QLatin1String("podcast-directories"); QString PodcastSearchDialog::constExt=QLatin1String(".opml"); static QString generateCacheFileName(const QUrl &url, bool create) { QString hash=QCryptographicHash::hash(url.toString().toUtf8(), QCryptographicHash::Md5).toHex(); QString dir=Utils::cacheDir(PodcastSearchDialog::constCacheDir, create); return dir.isEmpty() ? QString() : (dir+hash+PodcastSearchDialog::constExt); } class ITunesSearchPage : public PodcastSearchPage { public: ITunesSearchPage(QWidget *p) : PodcastSearchPage(p, QLatin1String("iTunes"), QLatin1String("itunes"), QUrl(QLatin1String("http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStoreServices.woa/wa/wsSearch")), QLatin1String("term"), QStringList() << QLatin1String("country") << QLatin1String("US") << QLatin1String("media") << QLatin1String("podcast")) { } void parse(const QVariant &data) { foreach (const QVariant &resultVariant, data.toMap()[QLatin1String("results")].toList()) { QVariantMap result(resultVariant.toMap()); if (result[QLatin1String("kind")].toString() != QLatin1String("podcast")) { continue; } addPodcast(result[QLatin1String("trackName")].toString(), result[QLatin1String("feedUrl")].toUrl(), result[QLatin1String("artworkUrl100")].toUrl(), QString(), result[QLatin1String("collectionViewUrl")].toString(), 0); } } }; class GPodderSearchPage : public PodcastSearchPage { public: GPodderSearchPage(QWidget *p) : PodcastSearchPage(p, QLatin1String("GPodder"), QLatin1String("gpodder"), QUrl(QLatin1String("http://gpodder.net/search.json")), QLatin1String("q")) { } void parse(const QVariant &data) { QVariantList list=data.toList(); foreach (const QVariant &var, list) { QVariantMap map=var.toMap(); addPodcast(map[QLatin1String("title")].toString(), map[QLatin1String("url")].toUrl(), map[QLatin1String("logo_url")].toUrl(), map[QLatin1String("description")].toString(), map[QLatin1String("website")].toString(), 0); } } }; PodcastPage::PodcastPage(QWidget *p, const QString &n) : QWidget(p) , pageName(n) , job(0) , imageJob(0) { tree = new QTreeWidget(this); tree->setItemDelegate(new BasicItemDelegate(tree)); tree->header()->setVisible(false); text=new TextBrowser(this); spinner=new Spinner(this); spinner->setWidget(tree); imageSpinner=new Spinner(this); imageSpinner->setWidget(text); connect(tree, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged())); tree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); text->setOpenLinks(false); connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(openLink(QUrl))); updateText(); } QUrl PodcastPage::currentRss() const { QList selection=tree->selectedItems(); return selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl(); } void PodcastPage::fetch(const QUrl &url) { cancel(); tree->clear(); spinner->start(); job=NetworkAccessManager::self()->get(url); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); } void PodcastPage::fetchImage(const QUrl &url) { cancelImage(); imageSpinner->start(); imageJob=NetworkAccessManager::self()->get(url, 5000); imageJob->setProperty(constOrigUrlProperty, url); connect(imageJob, SIGNAL(finished()), this, SLOT(imageJobFinished())); } void PodcastPage::cancel() { spinner->stop(); if (job) { job->cancelAndDelete(); job=0; } } void PodcastPage::cancelImage() { imageSpinner->stop(); if (imageJob) { imageJob->cancelAndDelete(); imageJob=0; } } void PodcastPage::addPodcast(const QString &title, const QUrl &url, const QUrl &image, const QString &description, const QString &webPage, QTreeWidgetItem *p) { if (title.isEmpty() || url.isEmpty()) { return; } QTreeWidgetItem *podItem=p ? new QTreeWidgetItem(p, QStringList() << title) : new QTreeWidgetItem(tree, QStringList() << title); podItem->setData(0, IsPodcastRole, true); podItem->setData(0, UrlRole, url); podItem->setData(0, ImageUrlRole, image); podItem->setData(0, DescriptionRole, description); podItem->setData(0, WebPageUrlRole, webPage); podItem->setIcon(0, Icons::self()->audioFileIcon); } static QString encode(const QImage &img) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); img.save(&buffer, "PNG"); return QString("

    ").arg(QString(buffer.data().toBase64())); } void PodcastPage::updateText() { QList selection=tree->selectedItems(); if (!selection.isEmpty()) { QTreeWidgetItem *item=selection.at(0); if (item->data(0, IsPodcastRole).toBool()) { QUrl url=item->data(0, UrlRole).toUrl(); QUrl imageUrl=item->data(0, ImageUrlRole).toUrl(); QString descr=item->data(0, DescriptionRole).toString(); QString web=item->data(0, WebPageUrlRole).toString(); QString str=""+item->text(0)+"
    "; if (!imageUrl.isEmpty()) { QImage *img=imageCache.object(imageUrl); if (img) { str+=encode(*img); } else { fetchImage(imageUrl); } } if (!descr.isEmpty()) { str+="

    "+descr+"


    "; } str+=""; if (!web.isEmpty()) { str+=""; } str+="
    "+tr("RSS:")+""+url.toString()+"
    "+tr("Website:")+""+web+"
    "; text->setHtml(str); return; } } text->setHtml(""+tr("Podcast details")+"

    "+tr("Select a podcast to display its details")+"

    "); } void PodcastPage::selectionChanged() { cancelImage(); updateText(); QList selection=tree->selectedItems(); emit rssSelected(selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl()); } void PodcastPage::imageJobFinished() { NetworkJob *j=qobject_cast(sender()); if (!j) { return; } j->deleteLater(); if (j!=imageJob) { return; } if (imageSpinner) { imageSpinner->stop(); } QByteArray data=imageJob->readAll(); QImage img=QImage::fromData(data, Covers::imageFormat(data)); if (!img.isNull()) { if (img.width()>maxImageSize || img.height()>maxImageSize) { img=img.scaled(maxImageSize, maxImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } imageCache.insert(imageJob->property(constOrigUrlProperty).toUrl(), new QImage(img), img.byteCount()); updateText(); } imageJob=0; } void PodcastPage::jobFinished() { NetworkJob *j=qobject_cast(sender()); if (!j) { return; } j->deleteLater(); if (j!=job) { return; } if (spinner) { spinner->stop(); } parseResonse(j->ok() ? j->actualJob() : 0); job=0; } void PodcastPage::openLink(const QUrl &url) { QDesktopServices::openUrl(url); } PodcastSearchPage::PodcastSearchPage(QWidget *p, const QString &n, const QString &i, const QUrl &qu, const QString &qk, const QStringList &other) : PodcastPage(p, n) , queryUrl(qu) , queryKey(qk) , otherArgs(other) { QBoxLayout *searchLayout=new QBoxLayout(QBoxLayout::LeftToRight); QBoxLayout *viewLayout=new QBoxLayout(QBoxLayout::LeftToRight); QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, this); searchLayout->setMargin(0); viewLayout->setMargin(0); mainLayout->setMargin(0); search=new LineEdit(p); search->setPlaceholderText(tr("Enter search term...")); searchButton=new QPushButton(tr("Search"), p); QWidget::setTabOrder(search, searchButton); QWidget::setTabOrder(searchButton, tree); searchLayout->addWidget(search); searchLayout->addWidget(searchButton); viewLayout->addWidget(tree, 1); viewLayout->addWidget(text, 0); mainLayout->addLayout(searchLayout); mainLayout->addLayout(viewLayout); connect(search, SIGNAL(returnPressed()), SLOT(doSearch())); connect(searchButton, SIGNAL(clicked()), SLOT(doSearch())); icn.addFile(":"+i); } void PodcastSearchPage::showEvent(QShowEvent *e) { search->setFocus(); QWidget::showEvent(e); } void PodcastSearchPage::doSearch() { QString text=search->text().trimmed(); if (text.isEmpty() || text==currentSearch) { return; } currentSearch=text; QUrl url=queryUrl; QUrlQuery query; query.addQueryItem(queryKey, text); if (otherArgs.size()>1) { for (int i=0; ireadAll()).toVariant(); if (data.isNull()) { emit error(tr("There was a problem parsing the response from %1").arg(name())); return; } parse(data); } OpmlBrowsePage::OpmlBrowsePage(QWidget *p, const QString &n, const QString &i, const QUrl &u) : PodcastPage(p, n) , loaded(false) , url(u) { QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::LeftToRight, this); mainLayout->setMargin(0); mainLayout->addWidget(tree, 1); mainLayout->addWidget(text, 0); Action *act=new Action(tr("Reload"), this); tree->addAction(act); connect(act, SIGNAL(triggered()), this, SLOT(reload())); tree->setContextMenuPolicy(Qt::ActionsContextMenu); icn.addFile(i.isEmpty() || !QFile::exists(i) ? ":podcasts" : i); } void OpmlBrowsePage::showEvent(QShowEvent *e) { if (!loaded) { QString cacheFile=generateCacheFileName(url, false); if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) { QFile f(cacheFile); if (f.open(QIODevice::ReadOnly)) { parseResonse(&f); if (tree->topLevelItemCount()>0) { Utils::touchFile(cacheFile); loaded=true; return; } } } fetch(url); loaded=true; } QWidget::showEvent(e); } void OpmlBrowsePage::reload() { QString cacheFile=generateCacheFileName(url, false); if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) { QFile::remove(cacheFile); } fetch(url); } void OpmlBrowsePage::parseResonse(QIODevice *dev) { bool isLoadingFromCache=dynamic_cast(dev) ? true : false; if (!dev) { if (!isLoadingFromCache) { emit error(tr("Failed to download directory listing")); } return; } QByteArray data=dev->readAll(); OpmlParser::Category parsed=OpmlParser::parse(data); if (parsed.categories.isEmpty() && parsed.podcasts.isEmpty()) { if (!isLoadingFromCache) { emit error(tr("Failed to parse directory listing")); } return; } if (1==parsed.categories.count() && parsed.podcasts.isEmpty()) { parsed=parsed.categories.at(0); } foreach (const OpmlParser::Category &cat, parsed.categories) { addCategory(cat, 0); } foreach (const OpmlParser::Podcast &pod, parsed.podcasts) { addPodcast(pod, 0); } if (!isLoadingFromCache && tree->topLevelItemCount()>0) { QString cacheFile=generateCacheFileName(url, true); if (!cacheFile.isEmpty()) { QFile f(cacheFile); if (f.open(QIODevice::WriteOnly)) { f.write(data); } } } } void OpmlBrowsePage::addCategory(const OpmlParser::Category &cat, QTreeWidgetItem *p) { if (cat.categories.isEmpty() && cat.podcasts.isEmpty()) { return; } QTreeWidgetItem *catItem=p ? new QTreeWidgetItem(p, QStringList() << cat.name) : new QTreeWidgetItem(tree, QStringList() << cat.name); catItem->setData(0, IsPodcastRole, false); catItem->setIcon(0, Icons::self()->folderIcon); foreach (const OpmlParser::Podcast &pod, cat.podcasts) { addPodcast(pod, catItem); } foreach (const OpmlParser::Category &cat, cat.categories) { addCategory(cat, catItem); } } void OpmlBrowsePage::addPodcast(const OpmlParser::Podcast &pod, QTreeWidgetItem *p) { PodcastPage::addPodcast(pod.name, pod.url, pod.image, pod.description, pod.htmlUrl, p); } PodcastUrlPage::PodcastUrlPage(QWidget *p) : PodcastPage(p, tr("URL")) { QBoxLayout *searchLayout=new QBoxLayout(QBoxLayout::LeftToRight); QBoxLayout *viewLayout=new QBoxLayout(QBoxLayout::LeftToRight); QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, this); searchLayout->setMargin(0); viewLayout->setMargin(0); mainLayout->setMargin(0); urlEntry=new LineEdit(p); urlEntry->setPlaceholderText(tr("Enter podcast URL...")); loadButton=new QPushButton(tr("Load"), p); QWidget::setTabOrder(urlEntry, loadButton); QWidget::setTabOrder(loadButton, tree); searchLayout->addWidget(urlEntry); searchLayout->addWidget(loadButton); viewLayout->addWidget(tree, 1); viewLayout->addWidget(text, 0); mainLayout->addWidget(new QLabel(tr("Enter podcast URL below, and press 'Load'"), this)); mainLayout->addLayout(searchLayout); mainLayout->addLayout(viewLayout); connect(urlEntry, SIGNAL(returnPressed()), SLOT(loadUrl())); connect(loadButton, SIGNAL(clicked()), SLOT(loadUrl())); icn.addFile(":podcasts"); } void PodcastUrlPage::showEvent(QShowEvent *e) { urlEntry->setFocus(); QWidget::showEvent(e); } void PodcastUrlPage::loadUrl() { QString text=urlEntry->text().trimmed(); if (text.isEmpty()) { return; } QUrl url(PodcastService::fixUrl(text)); if (url==currentUrl) { return; } if (!PodcastService::isUrlOk(url)) { emit error(tr("Invalid URL!")); } else { currentUrl=url; fetch(url); } } void PodcastUrlPage::parseResonse(QIODevice *dev) { if (!dev) { emit error(tr("Failed to fetch podcast!")); return; } RssParser::Channel ch=RssParser::parse(dev, false, true); if (!ch.isValid()) { emit error(tr("Failed to parse podcast.")); return; } if (!ch.isValid()) { emit error(tr("Cantata only supports audio podcasts! The URL entered contains only video podcasts.")); return; } addPodcast(ch.name, currentUrl, ch.image, ch.description, QString(), 0); } int PodcastSearchDialog::instanceCount() { return iCount; } PodcastSearchDialog::PodcastSearchDialog(PodcastService *s, QWidget *parent) : Dialog(parent, "PodcastSearchDialog", QSize(800, 600)) , service(s) { Utils::clearOldCache(constCacheDir, 2); iCount++; setButtons(User1|Close); setButtonText(User1, tr("Subscribe")); QWidget *mainWidget = new QWidget(this); messageWidget = new MessageWidget(mainWidget); spacer = new QWidget(this); spacer->setFixedSize(Utils::layoutSpacing(this), 0); QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, mainWidget); layout->setMargin(0); layout->setSpacing(0); pageWidget = new PageWidget(mainWidget); QList pages; layout->addWidget(messageWidget); layout->addWidget(spacer); layout->addWidget(pageWidget); PodcastUrlPage *urlPage=new PodcastUrlPage(pageWidget); pageWidget->addPage(urlPage, tr("Enter URL"), urlPage->icon(), tr("Manual podcast URL")); pages << urlPage; ITunesSearchPage *itunes=new ITunesSearchPage(pageWidget); pageWidget->addPage(itunes, tr("Search %1").arg(itunes->name()), itunes->icon(), tr("Search for podcasts on %1").arg(itunes->name())); pages << itunes; GPodderSearchPage *gpodder=new GPodderSearchPage(pageWidget); pageWidget->addPage(gpodder, tr("Search %1").arg(gpodder->name()), gpodder->icon(), tr("Search for podcasts on %1").arg(gpodder->name())); pages << gpodder; QSet loaded; pages << loadDirectories(Utils::dataDir(), false, loaded); pages << loadDirectories(CANTATA_SYS_CONFIG_DIR, true, loaded); foreach (PodcastPage *p, pages) { connect(p, SIGNAL(rssSelected(QUrl)), SLOT(rssSelected(QUrl))); connect(p, SIGNAL(error(QString)), SLOT(showError(QString))); } setCaption(tr("Add Podcast Subscription")); setMainWidget(mainWidget); setAttribute(Qt::WA_DeleteOnClose); enableButton(User1, false); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); if (-1==maxImageSize) { maxImageSize=fontMetrics().height()*8; } connect(service, SIGNAL(newError(QString)), this, SLOT(showError(QString))); connect(messageWidget, SIGNAL(visible(bool)), SLOT(msgWidgetVisible(bool))); connect(pageWidget, SIGNAL(currentPageChanged()), this, SLOT(pageChanged())); messageWidget->hide(); } PodcastSearchDialog::~PodcastSearchDialog() { iCount--; imageCache.clear(); } void PodcastSearchDialog::rssSelected(const QUrl &url) { currentUrl=url; enableButton(User1, currentUrl.isValid()); } void PodcastSearchDialog::showError(const QString &msg) { messageWidget->setError(msg); } void PodcastSearchDialog::showInfo(const QString &msg) { messageWidget->setInformation(msg); } void PodcastSearchDialog::msgWidgetVisible(bool v) { spacer->setFixedSize(spacer->width(), v ? spacer->width() : 0); } void PodcastSearchDialog::pageChanged() { PageWidgetItem *pwi=pageWidget->currentPage(); PodcastPage *page=pwi ? qobject_cast(pwi->widget()) : 0; rssSelected(page ? page->currentRss() : QUrl()); } QList PodcastSearchDialog::loadDirectories(const QString &dir, bool isSystem, QSet &loaded) { QList pages; if (dir.isEmpty()) { return pages; } QFile file(dir+QLatin1String("/podcast_directories.xml")); if (file.open(QIODevice::ReadOnly)) { QXmlStreamReader reader(&file); while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement() && QLatin1String("directory")==reader.name()) { QString url=reader.attributes().value(QLatin1String("url")).toString(); if (!loaded.contains(url)) { QString icon=reader.attributes().value(QLatin1String("icon")).toString(); if (!icon.isEmpty()) { icon=dir+(isSystem ? "../icons/" : "")+icon; } OpmlBrowsePage *page=new OpmlBrowsePage(pageWidget, reader.attributes().value(QLatin1String("name")).toString(), icon, QUrl(url)); pageWidget->addPage(page, tr("Browse %1").arg(page->name()), page->icon(), tr("Browse %1 podcasts").arg(page->name())); pages << page; loaded.insert(url); } } } } return pages; } void PodcastSearchDialog::slotButtonClicked(int button) { switch (button) { case User1: { QUrl fixed=PodcastService::fixUrl(currentUrl); if (service->subscribedToUrl(fixed)) { showError(tr("You are already subscribed to this podcast!")); } else { service->addUrl(fixed); showInfo(tr("Subscription added")); } break; } case Close: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } cantata-2.2.0/online/podcastsearchdialog.h000066400000000000000000000106761316350454000206010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PODCAST_SEARCH_DIALOG_H #define PODCAST_SEARCH_DIALOG_H #include "support/dialog.h" #include "support/icon.h" #include #include class QPushButton; class LineEdit; class QTreeWidget; class NetworkJob; class QIODevice; class Spinner; class QTreeWidgetItem; class TextBrowser; class MessageWidget; class PageWidget; class PodcastService; namespace OpmlParser { struct Category; struct Podcast; } class PodcastPage : public QWidget { Q_OBJECT public: PodcastPage(QWidget *p, const QString &n); virtual ~PodcastPage() { cancel(); cancelImage(); } const Icon & icon() const { return icn; } const QString & name() const { return pageName; } QUrl currentRss() const; Q_SIGNALS: void rssSelected(const QUrl &url); void error(const QString &msg); protected: void fetch(const QUrl &url); void fetchImage(const QUrl &url); void cancel(); void cancelImage(); void addPodcast(const QString &title, const QUrl &url, const QUrl &image, const QString &description, const QString &webPage, QTreeWidgetItem *p); private Q_SLOTS: void selectionChanged(); void jobFinished(); void imageJobFinished(); void openLink(const QUrl &url); private: void updateText(); virtual void parseResonse(QIODevice *dev) = 0; protected: QString pageName; Spinner *spinner; Spinner *imageSpinner; QTreeWidget *tree; TextBrowser *text; NetworkJob *job; NetworkJob *imageJob; Icon icn; }; class PodcastSearchPage : public PodcastPage { Q_OBJECT public: PodcastSearchPage(QWidget *p, const QString &n, const QString &i, const QUrl &qu, const QString &qk, const QStringList &other=QStringList()); virtual ~PodcastSearchPage() { } void showEvent(QShowEvent *e); private: void parseResonse(QIODevice *dev); private Q_SLOTS: virtual void doSearch(); virtual void parse(const QVariant &data)=0; protected: LineEdit *search; QPushButton *searchButton; QString currentSearch; QUrl queryUrl; QString queryKey; QStringList otherArgs; }; class OpmlBrowsePage : public PodcastPage { Q_OBJECT public: OpmlBrowsePage(QWidget *p, const QString &n, const QString &i, const QUrl &u); virtual ~OpmlBrowsePage() { } void showEvent(QShowEvent *e); private Q_SLOTS: void reload(); private: void parseResonse(QIODevice *dev); void addCategory(const OpmlParser::Category &cat, QTreeWidgetItem *p); void addPodcast(const OpmlParser::Podcast &pod, QTreeWidgetItem *p); private: bool loaded; QUrl url; }; class PodcastUrlPage : public PodcastPage { Q_OBJECT public: PodcastUrlPage(QWidget *p); virtual ~PodcastUrlPage() { } void showEvent(QShowEvent *e); private: void parseResonse(QIODevice *dev); private Q_SLOTS: void loadUrl(); protected: LineEdit *urlEntry; QPushButton *loadButton; QUrl currentUrl; }; class PodcastSearchDialog : public Dialog { Q_OBJECT public: static int instanceCount(); static QString constCacheDir; static QString constExt; PodcastSearchDialog(PodcastService *s, QWidget *parent); virtual ~PodcastSearchDialog(); private Q_SLOTS: void rssSelected(const QUrl &url); void showError(const QString &msg); void showInfo(const QString &msg); void msgWidgetVisible(bool v); void pageChanged(); private: QList loadDirectories(const QString &dir, bool isSystem, QSet &loaded); void slotButtonClicked(int button); private: QUrl currentUrl; PageWidget *pageWidget; MessageWidget *messageWidget; QWidget *spacer; PodcastService *service; }; #endif cantata-2.2.0/online/podcastservice.cpp000066400000000000000000001172371316350454000201500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "podcastservice.h" #include "podcastsettingsdialog.h" #include "rssparser.h" #include "support/utils.h" #include "gui/settings.h" #include "widgets/icons.h" #include "mpd-interface/mpdconnection.h" #include "config.h" #include "http/httpserver.h" #include "qtiocompressor/qtiocompressor.h" #include "network/networkaccessmanager.h" #include "models/roles.h" #include "models/playqueuemodel.h" #include #include #include #include #include #include #include #include #include #include PodcastService::Proxy::Proxy(QObject *parent) : ProxyModel(parent) , unplayedOnly(false) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); } void PodcastService::Proxy::showUnplayedOnly(bool on) { if (on!=unplayedOnly) { unplayedOnly=on; invalidateFilter(); } } bool PodcastService::Proxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (left.row()<0 || right.row()<0) { return left.row()<0; } if (!static_cast(left.internalPointer())->isPodcast()) { Episode *l=static_cast(left.internalPointer()); Episode *r=static_cast(right.internalPointer()); if (l->publishedDate!=r->publishedDate) { return l->publishedDate>r->publishedDate; } } return QSortFilterProxyModel::lessThan(left, right); } bool PodcastService::Proxy::filterAcceptsPodcast(const Podcast *pod) const { foreach (const Episode *ep, pod->episodes) { if (filterAcceptsEpisode(ep)) { return true; } } return false; } bool PodcastService::Proxy::filterAcceptsEpisode(const Episode *item) const { return (!unplayedOnly || (unplayedOnly && !item->played)) && matchesFilter(QStringList() << item->name << item->parent->name); } bool PodcastService::Proxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled && !unplayedOnly) { return true; } const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const PodcastService::Item *item = static_cast(index.internalPointer()); if (filterStrings.isEmpty() && !unplayedOnly) { return true; } if (item->isPodcast()) { return filterAcceptsPodcast(static_cast(item)); } return filterAcceptsEpisode(static_cast(item)); } const QLatin1String PodcastService::constName("podcasts"); static const QLatin1String constExt(".xml.gz"); static const char * constNewFeedProperty="new-feed"; static const char * constRssUrlProperty="rss-url"; static const char * constDestProperty="dest"; static const QLatin1String constPartialExt(".partial"); static QString generateFileName(const QUrl &url, bool creatingNew) { QString hash=QCryptographicHash::hash(url.toString().toUtf8(), QCryptographicHash::Md5).toHex(); QString dir=Utils::dataDir(PodcastService::constName, true); QString fileName=dir+hash+constExt; if (creatingNew) { int i=0; while (QFile::exists(fileName) && i<100) { fileName=dir+hash+QChar('_')+QString::number(i)+constExt; i++; } } return fileName; } bool PodcastService::isPodcastFile(const QString &file) { if (file.startsWith(Utils::constDirSep) && MPDConnection::self()->getDetails().isLocal()) { QString downloadPath=Settings::self()->podcastDownloadPath(); if (downloadPath.isEmpty()) { return false; } return file.startsWith(downloadPath); } return false; } QUrl PodcastService::fixUrl(const QString &url) { QString trimmed(url.trimmed()); // Thanks gpodder! static QMap prefixMap; if (prefixMap.isEmpty()) { prefixMap.insert(QLatin1String("fb:"), QLatin1String("http://feeds.feedburner.com/%1")); prefixMap.insert(QLatin1String("yt:"), QLatin1String("http://www.youtube.com/rss/user/%1/videos.rss")); prefixMap.insert(QLatin1String("sc:"), QLatin1String("http://soundcloud.com/%1")); prefixMap.insert(QLatin1String("fm4od:"), QLatin1String("http://onapp1.orf.at/webcam/fm4/fod/%1.xspf")); prefixMap.insert(QLatin1String("ytpl:"), QLatin1String("http://gdata.youtube.com/feeds/api/playlists/%1")); } QMap::ConstIterator it(prefixMap.constBegin()); QMap::ConstIterator end(prefixMap.constEnd()); for (; it!=end; ++it) { if (trimmed.startsWith(it.key())) { trimmed=it.value().arg(trimmed.mid(it.key().length())); } } if (!trimmed.contains(QLatin1String("://"))) { trimmed.prepend(QLatin1String("http://")); } return fixUrl(QUrl(trimmed)); } QUrl PodcastService::fixUrl(const QUrl &orig) { QUrl u=orig; if (u.scheme().isEmpty() || QLatin1String("itpc")==u.scheme() || QLatin1String("pcast")==u.scheme() || QLatin1String("feed")==u.scheme() || QLatin1String("itms")==u.scheme()) { u.setScheme(QLatin1String("http")); } return u; } Song PodcastService::Episode::toSong() const { Song song; song.title=name; song.file=url.toString(); song.artist=parent->name; song.time=duration; if (!localFile.isEmpty()) { song.setPodcastLocalPath(localFile); } return song; } PodcastService::Podcast::Podcast(const QString &f) : unplayedCount(0) , fileName(f) , imageFile(f) { imageFile=imageFile.replace(constExt, ".jpg"); } static QLatin1String constTopTag("podcast"); static QLatin1String constImageAttribute("img"); static QLatin1String constRssAttribute("rss"); static QLatin1String constEpisodeTag("episode"); static QLatin1String constNameAttribute("name"); static QLatin1String constDescrAttribute("descr"); static QLatin1String constDateAttribute("date"); static QLatin1String constUrlAttribute("url"); static QLatin1String constTimeAttribute("time"); static QLatin1String constPlayedAttribute("played"); static QLatin1String constLocalAttribute("local"); static QLatin1String constTrue("true"); bool PodcastService::Podcast::load() { if (fileName.isEmpty()) { return false; } QFile file(fileName); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor.open(QIODevice::ReadOnly)) { return false; } QXmlStreamReader reader(&compressor); unplayedCount=0; while (!reader.atEnd()) { reader.readNext(); if (!reader.error() && reader.isStartElement()) { QString element = reader.name().toString(); QXmlStreamAttributes attributes=reader.attributes(); if (constTopTag == element) { imageUrl=attributes.value(constImageAttribute).toString(); url=attributes.value(constRssAttribute).toString(); name=attributes.value(constNameAttribute).toString(); descr=attributes.value(constDescrAttribute).toString(); if (url.isEmpty() || name.isEmpty()) { return false; } } else if (constEpisodeTag == element) { QString epName=attributes.value(constNameAttribute).toString(); QString epUrl=attributes.value(constUrlAttribute).toString(); if (!epName.isEmpty() && !epUrl.isEmpty()) { Episode *ep=new Episode(QDateTime::fromString(attributes.value(constDateAttribute).toString(), Qt::ISODate), epName, epUrl, this); QString localFile=attributes.value(constLocalAttribute).toString(); QString time=attributes.value(constTimeAttribute).toString(); ep->duration=time.isEmpty() ? 0 : time.toUInt(); ep->played=constTrue==attributes.value(constPlayedAttribute).toString(); ep->descr=attributes.value(constDescrAttribute).toString(); if (QFile::exists(localFile)) { ep->localFile=localFile; } episodes.append(ep); if (!ep->played) { unplayedCount++; } } } } } return true; } bool PodcastService::Podcast::save() const { if (fileName.isEmpty()) { return false; } QFile file(fileName); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor.open(QIODevice::WriteOnly)) { return false; } QXmlStreamWriter writer(&compressor); writer.writeStartElement(constTopTag); writer.writeAttribute(constImageAttribute, imageUrl.toString()); // ?? writer.writeAttribute(constRssAttribute, url.toString()); // ?? writer.writeAttribute(constNameAttribute, name); writer.writeAttribute(constDateAttribute, descr); foreach (Episode *ep, episodes) { writer.writeStartElement(constEpisodeTag); writer.writeAttribute(constNameAttribute, ep->name); writer.writeAttribute(constDescrAttribute, ep->descr); writer.writeAttribute(constUrlAttribute, ep->url.toString()); // ?? if (ep->duration) { writer.writeAttribute(constTimeAttribute, QString::number(ep->duration)); } if (ep->played) { writer.writeAttribute(constPlayedAttribute, constTrue); } if (ep->publishedDate.isValid()) { writer.writeAttribute(constDateAttribute, ep->publishedDate.toString(Qt::ISODate)); } if (!ep->localFile.isEmpty()) { writer.writeAttribute(constLocalAttribute, ep->localFile); } writer.writeEndElement(); } writer.writeEndElement(); compressor.close(); return true; } void PodcastService::Podcast::add(Episode *ep) { ep->parent=this; episodes.append(ep); } void PodcastService::Podcast::add(QList &eps) { foreach(Episode *ep, eps) { add(ep); } setUnplayedCount(); } PodcastService::Episode * PodcastService::Podcast::getEpisode(const QUrl &epUrl) const { foreach (Episode *episode, episodes) { if (episode->url==epUrl) { return episode; } } return 0; } void PodcastService::Podcast::setUnplayedCount() { unplayedCount=episodes.count(); foreach (Episode *episode, episodes) { if (episode->played) { unplayedCount--; } } } void PodcastService::Podcast::removeFiles() { if (!fileName.isEmpty() && QFile::exists(fileName)) { QFile::remove(fileName); } if (!imageFile.isEmpty() && QFile::exists(imageFile)) { QFile::remove(imageFile); } } const Song & PodcastService::Podcast::coverSong() { if (song.isEmpty()) { song.artist=constName; song.album=name; song.title=name; song.type=Song::OnlineSvrTrack; song.setIsFromOnlineService(constName); song.setExtraField(Song::OnlineImageUrl, imageUrl.toString()); song.setExtraField(Song::OnlineImageCacheName, imageFile); song.file=url.toString(); } return song; } PodcastService::PodcastService(QObject *p) : ActionModel(p) , downloadJob(0) , rssUpdateTimer(0) { QMetaObject::invokeMethod(this, "loadAll", Qt::QueuedConnection); icn.addFile(":"+constName); useCovers(name(), true); clearPartialDownloads(); connect(MPDConnection::self(), SIGNAL(currentSongUpdated(const Song &)), this, SLOT(currentMpdSong(const Song &))); } QString PodcastService::name() const { return constName; } QString PodcastService::title() const { return QLatin1String("Podcasts"); } QString PodcastService::descr() const { return tr("Subscribe to RSS feeds"); } int PodcastService::rowCount(const QModelIndex &index) const { if (index.column()>0) { return 0; } if (!index.isValid()) { return podcasts.size(); } Item *item=static_cast(index.internalPointer()); if (item->isPodcast()) { return static_cast(index.internalPointer())->episodes.count(); } return 0; } bool PodcastService::hasChildren(const QModelIndex &parent) const { return !parent.isValid() || static_cast(parent.internalPointer())->isPodcast(); } QModelIndex PodcastService::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } Item *item=static_cast(index.internalPointer()); if (item->isPodcast()) { return QModelIndex(); } else { Episode *episode=static_cast(item); if (episode->parent) { return createIndex(podcasts.indexOf(episode->parent), 0, episode->parent); } } return QModelIndex(); } QModelIndex PodcastService::index(int row, int col, const QModelIndex &parent) const { if (!hasIndex(row, col, parent)) { return QModelIndex(); } if (parent.isValid()) { Item *p=static_cast(parent.internalPointer()); if (p->isPodcast()) { Podcast *podcast=static_cast(p); return rowepisodes.count() ? createIndex(row, col, podcast->episodes.at(row)) : QModelIndex(); } } return rowconstMaxDescrLen) { descr=descr.left(constMaxDescrLen)+QLatin1String("..."); } descr+=QLatin1String("

    "); } return descr; } QVariant PodcastService::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: return tr("%n Podcast(s)", "", podcasts.count()); case Qt::DecorationRole: return icon(); } return QVariant(); } Item *item=static_cast(index.internalPointer()); if (item->isPodcast()) { Podcast *podcast=static_cast(item); switch(role) { case Cantata::Role_ListImage: return true; case Cantata::Role_CoverSong: { QVariant v; v.setValue(podcast->coverSong()); return v; } case Qt::DecorationRole: return Icons::self()->podcastIcon; case Cantata::Role_LoadCoverInUIThread: return true; case Cantata::Role_MainText: case Qt::DisplayRole: return tr("%1 (%2)", "podcast name (num unplayed episodes)").arg(podcast->name).arg(podcast->unplayedCount); case Cantata::Role_SubText: return tr("%n Episode(s)", "", podcast->episodes.count()); case Qt::ToolTipRole: if (Settings::self()->infoTooltips()) { return podcast->name+QLatin1String("
    ")+ trimDescr(podcast->descr)+ tr("%n Episode(s)", "", podcast->episodes.count()); } break; case Qt::FontRole: if (podcast->unplayedCount>0) { QFont f; f.setBold(true); return f; } default: break; } } else { Episode *episode=static_cast(item); switch (role) { case Qt::DecorationRole: if (!episode->localFile.isEmpty()) { return Icons::self()->savedRssListIcon; } else if (episode->downloadProg>=0) { return Icons::self()->downloadIcon; } else if (Episode::QueuedForDownload==episode->downloadProg) { return Icons::self()->clockIcon; } else { return Icons::self()->rssListIcon; } case Cantata::Role_MainText: case Qt::DisplayRole: return episode->name; case Cantata::Role_SubText: if (episode->downloadProg>=0) { return Utils::formatTime(episode->duration, true)+QLatin1Char(' ')+ tr("(Downloading: %1%)").arg(episode->downloadProg); } return episode->publishedDate.toString(Qt::LocalDate)+ (0==episode->duration ? QString() : (QLatin1String(" (")+Utils::formatTime(episode->duration, true)+QLatin1Char(')'))); case Qt::ToolTipRole: if (Settings::self()->infoTooltips()) { return QLatin1String("")+episode->parent->name+QLatin1String("
    ")+ episode->name+QLatin1String("
    ")+ trimDescr(episode->descr)+ Utils::formatTime(episode->duration, true)+QLatin1String("
    ")+ episode->publishedDate.toString(Qt::LocalDate); } break; case Qt::FontRole: if (!episode->played) { QFont f; f.setBold(true); return f; } default: break; } } return ActionModel::data(index, role); } Qt::ItemFlags PodcastService::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; } return Qt::NoItemFlags; } QMimeData * PodcastService::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData=0; QStringList paths=filenames(indexes); if (!paths.isEmpty()) { mimeData=new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constUriMimeType, paths); } return mimeData; } QStringList PodcastService::filenames(const QModelIndexList &indexes, bool allowPlaylists) const { Q_UNUSED(allowPlaylists) QList songList=songs(indexes); QStringList files; foreach (const Song &song, songList) { files.append(song.file); } return files; } QList PodcastService::songs(const QModelIndexList &indexes, bool allowPlaylists) const { Q_UNUSED(allowPlaylists) QList songs; QSet selectedPodcasts; foreach (const QModelIndex &idx, indexes) { Item *item=static_cast(idx.internalPointer()); if (item->isPodcast()) { Podcast *podcast=static_cast(item); foreach (const Episode *episode, podcast->episodes) { selectedPodcasts.insert(item); Song s=episode->toSong(); songs.append(fixPath(s)); } } } foreach (const QModelIndex &idx, indexes) { Item *item=static_cast(idx.internalPointer()); if (!item->isPodcast()) { Episode *episode=static_cast(item); if (!selectedPodcasts.contains(episode->parent)) { Song s=episode->toSong(); songs.append(fixPath(s)); } } } return songs; } Song & PodcastService::fixPath(Song &song) const { song.setPodcastLocalPath(QString()); song.setIsFromOnlineService(constName); song.artist=title(); if (!song.podcastLocalPath().isEmpty() && QFile::exists(song.podcastLocalPath())) { if (MPDConnection::self()->localFilePlaybackSupported()) { song.file=QLatin1String("file://")+song.podcastLocalPath(); } else if (HttpServer::self()->isAlive()) { song.file=song.podcastLocalPath(); song.file=HttpServer::self()->encodeUrl(song); } return song; } return encode(song); } void PodcastService::clear() { cancelAllJobs(); beginResetModel(); qDeleteAll(podcasts); podcasts.clear(); endResetModel(); emit dataChanged(QModelIndex(), QModelIndex()); } void PodcastService::loadAll() { beginResetModel(); QString dir=Utils::dataDir(constName); if (!dir.isEmpty()) { QDir d(dir); QStringList entries=d.entryList(QStringList() << "*"+constExt, QDir::Files|QDir::Readable|QDir::NoDot|QDir::NoDotDot); foreach (const QString &e, entries) { Podcast *podcast=new Podcast(dir+e); if (podcast->load()) { podcasts.append(podcast); } else { delete podcast; } } startRssUpdateTimer(); } endResetModel(); emit dataChanged(QModelIndex(), QModelIndex()); } void PodcastService::cancelAll() { foreach (NetworkJob *j, rssJobs) { disconnect(j, SIGNAL(finished()), this, SLOT(rssJobFinished())); j->cancelAndDelete(); } rssJobs.clear(); cancelAllDownloads(); } void PodcastService::rssJobFinished() { NetworkJob *j=dynamic_cast(sender()); if (!j || !rssJobs.contains(j)) { return; } j->deleteLater(); rssJobs.removeAll(j); bool isNew=j->property(constNewFeedProperty).toBool(); if (j->ok()) { if (updateUrls.contains(j->origUrl())){ updateUrls.remove(j->origUrl()); if (updateUrls.isEmpty()) { lastRssUpdate=QDateTime::currentDateTime(); Settings::self()->saveLastRssUpdate(lastRssUpdate); startRssUpdateTimer(); } } RssParser::Channel ch=RssParser::parse(j->actualJob()); if (!ch.isValid()) { if (isNew) { emit newError(tr("Failed to parse %1").arg(j->origUrl().toString())); } else { emit error(tr("Failed to parse %1").arg(j->origUrl().toString())); } return; } if (ch.video) { if (isNew) { emit newError(tr("Cantata only supports audio podcasts! %1 contains only video podcasts.").arg(j->origUrl().toString())); } else { emit error(tr("Cantata only supports audio podcasts! %1 contains only video podcasts.").arg(j->origUrl().toString())); } return; } int autoDownload=Settings::self()->podcastAutoDownloadLimit(); if (isNew) { Podcast *podcast=new Podcast(); podcast->url=j->origUrl(); podcast->fileName=podcast->imageFile=generateFileName(podcast->url, true); podcast->imageFile=podcast->imageFile.replace(constExt, ".jpg"); podcast->imageUrl=ch.image.toString(); podcast->name=ch.name; podcast->descr=ch.description; podcast->unplayedCount=ch.episodes.count(); foreach (const RssParser::Episode &ep, ch.episodes) { Episode *episode=new Episode(ep.publicationDate, ep.name, ep.url, podcast); episode->duration=ep.duration; episode->descr=ep.description; podcast->add(episode); } podcast->save(); beginInsertRows(QModelIndex(), podcasts.count(), podcasts.count()); podcasts.append(podcast); emit dataChanged(QModelIndex(), QModelIndex()); if (autoDownload) { int ep=0; foreach (Episode *episode, podcast->episodes) { downloadEpisode(podcast, QUrl(episode->url)); if (autoDownload<1000 && ++ep>=autoDownload) { break; } } } endInsertRows(); } else { Podcast *podcast = getPodcast(j->origUrl()); if (!podcast) { return; } QSet newEpisodes; QSet oldEpisodes; foreach (Episode *episode, podcast->episodes) { newEpisodes.insert(episode->url); } foreach (const RssParser::Episode &ep, ch.episodes) { oldEpisodes.insert(ep.url); } QSet added=oldEpisodes-newEpisodes; QSet removed=newEpisodes-oldEpisodes; if (added.count() || removed.count()) { QModelIndex podcastIndex=createIndex(podcasts.indexOf(podcast), 0, (void *)podcast); if (removed.count()) { foreach (const QUrl &s, removed) { Episode *episode=podcast->getEpisode(s); if (episode->localFile.isEmpty() || !QFile::exists(episode->localFile)) { int idx=podcast->episodes.indexOf(episode); if (-1!=idx) { beginRemoveRows(podcastIndex, idx, idx); podcast->episodes.removeAt(idx); delete episode; endRemoveRows(); } } } } if (added.count()) { beginInsertRows(podcastIndex, podcast->episodes.count(), (podcast->episodes.count()+added.count())-1); foreach (const RssParser::Episode &ep, ch.episodes) { QString epUrl=ep.url.toString(); if (added.contains(epUrl)) { Episode *episode=new Episode(ep.publicationDate, ep.name, ep.url, podcast); episode->duration=ep.duration; episode->descr=ep.description; podcast->add(episode); } } endInsertRows(); } podcast->setUnplayedCount(); podcast->save(); emit dataChanged(podcastIndex, podcastIndex); } } } else { if (isNew) { emit newError(tr("Failed to download %1").arg(j->origUrl().toString())); } else { emit error(tr("Failed to download %1").arg(j->origUrl().toString())); } } } void PodcastService::configure(QWidget *p) { PodcastSettingsDialog dlg(p); if (QDialog::Accepted==dlg.exec()) { int changes=dlg.changes(); if (changes&PodcastSettingsDialog::RssUpdate) { startRssUpdateTimer(); } } } PodcastService::Podcast * PodcastService::getPodcast(const QUrl &url) const { foreach (Podcast *podcast, podcasts) { if (podcast->url==url) { return podcast; } } return 0; } void PodcastService::unSubscribe(Podcast *podcast) { int row=podcasts.indexOf(podcast); if (row>=0) { QList episodes; foreach (Episode *e, podcast->episodes) { episodes.append(e); } cancelDownloads(episodes); beginRemoveRows(QModelIndex(), row, row); podcast->removeFiles(); delete podcasts.takeAt(row); endRemoveRows(); emit dataChanged(QModelIndex(), QModelIndex()); if (podcasts.isEmpty()) { stopRssUpdateTimer(); } } } void PodcastService::refresh(const QModelIndexList &list) { foreach (const QModelIndex &idx, list) { Item *itm=static_cast(idx.internalPointer()); if (itm->isPodcast()) { refreshSubscription(static_cast(itm)); } } } void PodcastService::refreshAll() { foreach (Podcast *pod, podcasts) { refreshSubscription(pod); } } void PodcastService::refreshSubscription(Podcast *item) { if (item) { QUrl url=item->url; if (processingUrl(url)) { return; } addUrl(url, false); } else { updateRss(); } } bool PodcastService::processingUrl(const QUrl &url) const { foreach (NetworkJob *j, rssJobs) { if (j->origUrl()==url) { return true; } } return false; } void PodcastService::addUrl(const QUrl &url, bool isNew) { NetworkJob *job=NetworkAccessManager::self()->get(url); connect(job, SIGNAL(finished()), this, SLOT(rssJobFinished())); job->setProperty(constNewFeedProperty, isNew); rssJobs.append(job); } bool PodcastService::downloadingEpisode(const QUrl &url) const { if (downloadJob && downloadJob->origUrl()==url) { return true; } return toDownload.contains(url); } void PodcastService::cancelAllDownloads() { foreach (const DownloadEntry &e, toDownload) { updateEpisode(e.rssUrl, e.url, Episode::NotDownloading); } toDownload.clear(); cancelDownload(); } void PodcastService::downloadPodcasts(Podcast *pod, const QList &episodes) { foreach (Episode *ep, episodes) { downloadEpisode(pod, QUrl(ep->url)); } } void PodcastService::deleteDownloadedPodcasts(Podcast *pod, const QList &episodes) { cancelDownloads(episodes); bool modified=false; foreach (Episode *ep, episodes) { QString fileName=ep->localFile; if (!fileName.isEmpty()) { if (QFile::exists(fileName)) { QFile::remove(fileName); } QString dirName=fileName.isEmpty() ? QString() : Utils::getDir(fileName); if (!dirName.isEmpty()) { QDir dir(dirName); if (dir.exists()) { dir.rmdir(dirName); } } ep->localFile=QString(); ep->downloadProg=Episode::NotDownloading; QModelIndex idx=createIndex(pod->episodes.indexOf(ep), 0, (void *)ep); emit dataChanged(idx, idx); modified=true; } } if (modified) { QModelIndex idx=createIndex(podcasts.indexOf(pod), 0, (void *)pod); emit dataChanged(idx, idx); pod->save(); } } void PodcastService::setPodcastsAsListened(Podcast *pod, const QList &episodes, bool listened) { bool modified=false; foreach (Episode *ep, episodes) { if (listened!=ep->played) { ep->played=listened; QModelIndex idx=createIndex(pod->episodes.indexOf(ep), 0, (void *)ep); emit dataChanged(idx, idx); modified=true; if (listened) { pod->unplayedCount--; } else { pod->unplayedCount++; } } } if (modified) { QModelIndex idx=createIndex(podcasts.indexOf(pod), 0, (void *)pod); emit dataChanged(idx, idx); pod->save(); } } static QString encodeName(const QString &name) { QString n=name; n=n.replace("/", "_"); n=n.replace("\\", "_"); n=n.replace(":", "_"); return n; } void PodcastService::downloadEpisode(const Podcast *podcast, const QUrl &episode) { QString dest=Settings::self()->podcastDownloadPath(); if (dest.isEmpty()) { return; } if (downloadingEpisode(episode)) { return; } dest=Utils::fixPath(dest)+Utils::fixPath(encodeName(podcast->name))+Utils::getFile(episode.toString()); toDownload.append(DownloadEntry(episode, podcast->url, dest)); updateEpisode(podcast->url, episode, Episode::QueuedForDownload); doNextDownload(); } void PodcastService::cancelDownloads(const QList episodes) { bool cancelDl=false; foreach (Episode *e, episodes) { toDownload.removeAll(e->url); e->downloadProg=Episode::NotDownloading; QModelIndex idx=createIndex(e->parent->episodes.indexOf(e), 0, (void *)e); emit dataChanged(idx, idx); if (!cancelDl && downloadJob && downloadJob->url()==e->url) { cancelDl=true; } } if (cancelDl) { cancelDownload(); } } void PodcastService::cancelDownload(const QUrl &url) { if (downloadJob && downloadJob->origUrl()==url) { cancelDownload(); doNextDownload(); } } void PodcastService::cancelDownload() { if (downloadJob) { downloadJob->cancelAndDelete(); disconnect(downloadJob, SIGNAL(finished()), this, SLOT(downloadJobFinished())); disconnect(downloadJob, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); disconnect(downloadJob, SIGNAL(downloadPercent(int)), this, SLOT(downloadPercent(int))); QString dest=downloadJob->property(constDestProperty).toString(); QString partial=dest.isEmpty() ? QString() : QString(dest+constPartialExt); if (!partial.isEmpty() && QFile::exists(partial)) { QFile::remove(partial); } updateEpisode(downloadJob->property(constRssUrlProperty).toUrl(), downloadJob->origUrl(), Episode::NotDownloading); downloadJob=0; } } void PodcastService::doNextDownload() { if (downloadJob) { return; } if (toDownload.isEmpty()) { return; } DownloadEntry entry=toDownload.takeFirst(); downloadJob=NetworkAccessManager::self()->get(entry.url); connect(downloadJob, SIGNAL(finished()), this, SLOT(downloadJobFinished())); connect(downloadJob, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); connect(downloadJob, SIGNAL(downloadPercent(int)), this, SLOT(downloadPercent(int))); downloadJob->setProperty(constRssUrlProperty, entry.rssUrl); downloadJob->setProperty(constDestProperty, entry.dest); updateEpisode(entry.rssUrl, entry.url, 0); QString partial=entry.dest+constPartialExt; if (QFile::exists(partial)) { QFile::remove(partial); } } void PodcastService::updateEpisode(const QUrl &rssUrl, const QUrl &url, int pc) { Podcast *pod=getPodcast(rssUrl); if (pod) { Episode *episode=pod->getEpisode(url); if (episode && episode->downloadProg!=pc) { episode->downloadProg=pc; QModelIndex idx=createIndex(pod->episodes.indexOf(episode), 0, (void *)episode); emit dataChanged(idx, idx); } } } void PodcastService::clearPartialDownloads() { QString dest=Settings::self()->podcastDownloadPath(); if (dest.isEmpty()) { return; } dest=Utils::fixPath(dest); QStringList sub=QDir(dest).entryList(QDir::Dirs|QDir::NoDotAndDotDot); foreach (const QString &d, sub) { QStringList partials=QDir(dest+d).entryList(QStringList() << QLatin1Char('*')+constPartialExt, QDir::Files); foreach (const QString &p, partials) { QFile::remove(dest+d+Utils::constDirSep+p); } } } void PodcastService::downloadJobFinished() { NetworkJob *job=dynamic_cast(sender()); if (!job || job!=downloadJob) { return; } job->deleteLater(); QString dest=job->property(constDestProperty).toString(); QString partial=dest.isEmpty() ? QString() : QString(dest+constPartialExt); if (job->ok()) { QString dest=job->property(constDestProperty).toString(); if (dest.isEmpty()) { return; } QString partial=dest+constPartialExt; if (QFile::exists(partial)) { if (QFile::exists(dest)) { QFile::remove(dest); } if (QFile::rename(partial, dest)) { Podcast *pod=getPodcast(job->property(constRssUrlProperty).toUrl()); if (pod) { Episode *episode=pod->getEpisode(job->origUrl()); if (episode) { episode->localFile=dest; pod->save(); QModelIndex idx=createIndex(pod->episodes.indexOf(episode), 0, (void *)episode); emit dataChanged(idx, idx); } } } } } else if (!partial.isEmpty() && QFile::exists(partial)) { QFile::remove(partial); } updateEpisode(job->property(constRssUrlProperty).toUrl(), job->origUrl(), Episode::NotDownloading); downloadJob=0; doNextDownload(); } void PodcastService::downloadReadyRead() { NetworkJob *job=dynamic_cast(sender()); if (!job || job!=downloadJob) { return; } QString dest=job->property(constDestProperty).toString(); QString partial=dest.isEmpty() ? QString() : QString(dest+constPartialExt); if (!partial.isEmpty()) { QString dir=Utils::getDir(partial); if (!QDir(dir).exists()) { QDir(dir).mkpath(dir); } if (!QDir(dir).exists()) { return; } QFile f(partial); while (true) { const qint64 bytes = job->bytesAvailable(); if (bytes <= 0) { break; } if (!f.isOpen()) { if (!f.open(QIODevice::Append)) { return; } } f.write(job->read(bytes)); } } } void PodcastService::downloadPercent(int pc) { NetworkJob *job=dynamic_cast(sender()); if (!job || job!=downloadJob) { return; } updateEpisode(job->property(constRssUrlProperty).toUrl(), job->origUrl(), pc); } void PodcastService::startRssUpdateTimer() { if (0==Settings::self()->rssUpdate() || podcasts.isEmpty()) { stopRssUpdateTimer(); return; } if (!rssUpdateTimer) { rssUpdateTimer=new QTimer(this); rssUpdateTimer->setSingleShot(true); connect(rssUpdateTimer, SIGNAL(timeout()), this, SLOT(updateRss())); } if (!lastRssUpdate.isValid()) { lastRssUpdate=Settings::self()->lastRssUpdate(); } if (!lastRssUpdate.isValid()) { updateRss(); } else { QDateTime nextUpdate = lastRssUpdate.addSecs(Settings::self()->rssUpdate()*60); int secsUntilNextUpdate = QDateTime::currentDateTime().secsTo(nextUpdate); if (secsUntilNextUpdate<0) { // Oops, missed update time!!! updateRss(); } else { rssUpdateTimer->start(secsUntilNextUpdate*1000ll); } } } void PodcastService::stopRssUpdateTimer() { if (rssUpdateTimer) { rssUpdateTimer->stop(); } } void PodcastService::updateRss() { foreach (Podcast *podcast, podcasts) { const QUrl &url=podcast->url; updateUrls.insert(url); if (!processingUrl(url)) { addUrl(url, false); } } } void PodcastService::currentMpdSong(const Song &s) { if ((s.isFromOnlineService() && s.onlineService()==constName) || isPodcastFile(s.file)) { QString path=s.decodedPath(); if (path.isEmpty()) { path=s.file; } foreach (Podcast *podcast, podcasts) { foreach (Episode *episode, podcast->episodes) { if (episode->url==path || episode->localFile==path) { if (!episode->played) { episode->played=true; QModelIndex idx=createIndex(podcast->episodes.indexOf(episode), 0, (void *)episode); emit dataChanged(idx, idx); podcast->unplayedCount--; podcast->save(); idx=createIndex(podcasts.indexOf(podcast), 0, (void *)podcast); emit dataChanged(idx, idx); } return; } } } } } cantata-2.2.0/online/podcastservice.h000066400000000000000000000152561316350454000176130ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PODCAST_SERVICE_H #define PODCAST_SERVICE_H #include "onlineservice.h" #include "models/actionmodel.h" #include "models/proxymodel.h" #include "mpd-interface/song.h" #include #include #include #include #include class QTimer; class NetworkJob; class PodcastService : public ActionModel, public OnlineService { Q_OBJECT public: struct Item { Item(const QString &n=QString(), const QUrl &u=QUrl()) : name(n), url(u) { } virtual ~Item() { } virtual bool isPodcast() const { return false; } QString name; QString descr; QUrl url; }; struct Podcast; struct Episode : public Item { enum DownloadState { NotDownloading = -1, QueuedForDownload = -2 }; Episode(const QDateTime &d=QDateTime(), const QString &n=QString(), const QUrl &u=QUrl(), Podcast *p=0) : Item(n, u), played(false), duration(0), publishedDate(d), parent(p), downloadProg(NotDownloading) { } virtual ~Episode() { } Song toSong() const; bool played; int duration; QDateTime publishedDate; Podcast *parent; QString localFile; int downloadProg; }; struct Podcast : public Item { Podcast(const QString &f=QString()); virtual ~Podcast() { qDeleteAll(episodes); } virtual bool isPodcast() const { return true; } bool load(); bool save() const; void add(Episode *ep); void add(QList &eps); Episode * getEpisode(const QUrl &epUrl) const; void setUnplayedCount(); void removeFiles(); const Song & coverSong(); QList episodes; int unplayedCount; QString fileName; QString imageFile; QUrl imageUrl; Song song; }; class Proxy : public ProxyModel { public: Proxy(QObject *parent); void showUnplayedOnly(bool on); private: bool lessThan(const QModelIndex &left, const QModelIndex &right) const; bool filterAcceptsPodcast(const Podcast *pod) const; bool filterAcceptsEpisode(const Episode *item) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; private: bool unplayedOnly; }; static const QLatin1String constName; PodcastService(QObject *p); virtual ~PodcastService() { cancelAll(); } Song & fixPath(Song &song) const; QString name() const; QString title() const; QString descr() const; int rowCount(const QModelIndex &index = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const { Q_UNUSED(parent) return 1; } bool hasChildren(const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &index) const; QModelIndex index(int row, int col, const QModelIndex &parent) const; QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; QMimeData * mimeData(const QModelIndexList &indexes) const; QStringList filenames(const QModelIndexList &indexes, bool allowPlaylists=false) const; QList songs(const QModelIndexList &indexes, bool allowPlaylists=false) const; int podcastCount() const { return podcasts.count(); } void clear(); void configure(QWidget *p); bool subscribedToUrl(const QUrl &url) { return 0!=getPodcast(url); } void unSubscribe(Podcast *podcast); void refresh(const QModelIndexList &list); void refreshAll(); void refreshSubscription(Podcast *item); bool processingUrl(const QUrl &url) const; void addUrl(const QUrl &url, bool isNew=true); static bool isPodcastFile(const QString &file); static const QString & iconPath() { return iconFile; } static QUrl fixUrl(const QString &url); static QUrl fixUrl(const QUrl &orig); static bool isUrlOk(const QUrl &u) { return QLatin1String("http")==u.scheme() || QLatin1String("https")==u.scheme(); } bool isDownloading() const { return 0!=downloadJob; } void cancelAllDownloads(); void downloadPodcasts(Podcast *pod, const QList &episodes); void deleteDownloadedPodcasts(Podcast *pod, const QList &episodes); void setPodcastsAsListened(Podcast *pod, const QList &episodes, bool listened); void cancelAllJobs() { cancelAll(); cancelAllDownloads(); } Podcast * getPodcast(const QUrl &url) const; void cancelAll(); void startRssUpdateTimer(); void stopRssUpdateTimer(); Q_SIGNALS: void error(const QString &msg); void newError(const QString &msg); private: bool downloadingEpisode(const QUrl &url) const; void downloadEpisode(const Podcast *podcast, const QUrl &episode); void cancelDownloads(const QList episodes); void cancelDownload(const QUrl &url); void cancelDownload(); void doNextDownload(); void updateEpisode(const QUrl &rssUrl, const QUrl &url, int pc); void clearPartialDownloads(); private Q_SLOTS: void loadAll(); void rssJobFinished(); void updateRss(); void currentMpdSong(const Song &s); void downloadJobFinished(); void downloadReadyRead(); void downloadPercent(int pc); private: struct DownloadEntry { DownloadEntry(const QUrl &u=QUrl(), const QUrl &r=QUrl(), const QString &d=QString()) : url(u), rssUrl(r), dest(d) { } bool operator==(const DownloadEntry &o) const { return o.url==url; } QUrl url; QUrl rssUrl; QString dest; Episode *ep; }; QList podcasts; QList rssJobs; NetworkJob * downloadJob; QList toDownload; QTimer *rssUpdateTimer; QDateTime lastRssUpdate; QTimer *deleteTimer; QDateTime lastDelete; QSet updateUrls; static QString iconFile; }; #endif cantata-2.2.0/online/podcastsettingsdialog.cpp000066400000000000000000000140321316350454000215150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "podcastsettingsdialog.h" #include "support/buddylabel.h" #include "support/pathrequester.h" #include "gui/settings.h" #include "support/utils.h" #include #include #include static void setIndex(QComboBox *combo, int val) { int possible=0; for (int i=0; icount(); ++i) { int cval=combo->itemData(i).toInt(); if (cval==val) { combo->setCurrentIndex(i); possible=-1; break; } if (cval=0) { combo->setCurrentIndex(possible); } } PodcastSettingsDialog::PodcastSettingsDialog(QWidget *p) : Dialog(p, "PodcastSettingsDialog", QSize(550, 160)) { QWidget *mw=new QWidget(this); QFormLayout * lay=new QFormLayout(mw); BuddyLabel * updateLabel=new BuddyLabel(tr("Check for new episodes:"), mw); BuddyLabel * downloadLabel=new BuddyLabel(tr("Download episodes to:"), mw); BuddyLabel * autoDownloadLabel=new BuddyLabel(tr("Download automatically:"), mw); updateCombo = new QComboBox(this); updateLabel->setBuddy(updateCombo); downloadPath = new PathRequester(this); downloadLabel->setBuddy(downloadPath); downloadPath->setDirMode(true); autoDownloadCombo = new QComboBox(this); autoDownloadLabel->setBuddy(autoDownloadCombo); int row=0; lay->setMargin(0); lay->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); lay->setWidget(row, QFormLayout::LabelRole, updateLabel); lay->setWidget(row++, QFormLayout::FieldRole, updateCombo); lay->setWidget(row, QFormLayout::LabelRole, downloadLabel); lay->setWidget(row++, QFormLayout::FieldRole, downloadPath); lay->setWidget(row, QFormLayout::LabelRole, downloadLabel); lay->setWidget(row++, QFormLayout::FieldRole, downloadPath); lay->setWidget(row, QFormLayout::LabelRole, autoDownloadLabel); lay->setWidget(row++, QFormLayout::FieldRole, autoDownloadCombo); setButtons(Ok|Cancel); setMainWidget(mw); setCaption(tr("Podcast Settings")); updateCombo->addItem(tr("Manually"), 0); updateCombo->addItem(tr("Every 15 minutes"), 15); updateCombo->addItem(tr("Every 30 minutes"), 30); updateCombo->addItem(tr("Every hour"), 60); updateCombo->addItem(tr("Every 2 hours"), 2*60); updateCombo->addItem(tr("Every 6 hours"), 6*60); updateCombo->addItem(tr("Every 12 hours"), 12*60); updateCombo->addItem(tr("Every day"), 24*60); updateCombo->addItem(tr("Every week"), 7*24*60); autoDownloadCombo->addItem(tr("Don't automatically download episodes"), 0); autoDownloadCombo->addItem(tr("Latest episode").arg(1), 1); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(2), 2); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(5), 5); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(10), 10); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(15), 15); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(20), 20); autoDownloadCombo->addItem(tr("Latest %1 episodes").arg(50), 50); autoDownloadCombo->addItem(tr("All episodes"), 1000); origRssUpdate=Settings::self()->rssUpdate(); setIndex(updateCombo, origRssUpdate); connect(updateCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkSaveable())); origPodcastDownloadPath=Utils::convertPathForDisplay(Settings::self()->podcastDownloadPath()); origPodcastAutoDownload=Settings::self()->podcastAutoDownloadLimit(); setIndex(autoDownloadCombo, origPodcastAutoDownload); downloadPath->setText(origPodcastDownloadPath); connect(downloadPath, SIGNAL(textChanged(QString)), SLOT(checkSaveable())); connect(autoDownloadCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkSaveable())); enableButton(Ok, false); changed=0; } void PodcastSettingsDialog::checkSaveable() { enableButton(Ok, autoDownloadCombo->itemData(autoDownloadCombo->currentIndex()).toInt()!=origPodcastAutoDownload || updateCombo->itemData(updateCombo->currentIndex()).toInt()!=origRssUpdate || downloadPath->text().trimmed()!=origPodcastDownloadPath); } void PodcastSettingsDialog::slotButtonClicked(int button) { switch (button) { case Ok: if (updateCombo->itemData(updateCombo->currentIndex()).toInt()!=origRssUpdate) { changed|=RssUpdate; Settings::self()->saveRssUpdate(updateCombo->itemData(updateCombo->currentIndex()).toInt()); } if (downloadPath->text().trimmed()!=origPodcastDownloadPath) { changed|=DownloadPath; Settings::self()->savePodcastDownloadPath(Utils::convertPathFromDisplay(downloadPath->text().trimmed())); } if (autoDownloadCombo->itemData(autoDownloadCombo->currentIndex()).toInt()!=origPodcastAutoDownload) { changed|=AutoDownload; Settings::self()->savePodcastAutoDownloadLimit(autoDownloadCombo->itemData(autoDownloadCombo->currentIndex()).toInt()); } accept(); case Close: case Cancel: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } cantata-2.2.0/online/podcastsettingsdialog.h000066400000000000000000000031371316350454000211660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PODCAST_SETTINGS_DIALOG_H #define PODCAST_SETTINGS_DIALOG_H #include "support/dialog.h" class QComboBox; class QCheckBox; class PathRequester; class PodcastSettingsDialog : public Dialog { Q_OBJECT public: enum Changes { RssUpdate = 0x01, DownloadPath = 0x02, AutoDownload = 0x04 }; PodcastSettingsDialog(QWidget *p); virtual ~PodcastSettingsDialog() { } int changes() const { return changed; } private Q_SLOTS: void checkSaveable(); private: void slotButtonClicked(int button); private: QComboBox *updateCombo; int origRssUpdate; PathRequester *downloadPath; QComboBox *autoDownloadCombo; QString origPodcastDownloadPath; int origPodcastAutoDownload; int changed; }; #endif cantata-2.2.0/online/podcastwidget.cpp000066400000000000000000000301731316350454000177640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "podcastwidget.h" #include "podcastsearchdialog.h" #include "podcastsettingsdialog.h" #include "widgets/itemview.h" #include "widgets/toolbutton.h" #include "widgets/icons.h" #include "widgets/menubutton.h" #include "support/action.h" #include "support/messagebox.h" #include "support/configuration.h" #include "support/monoicon.h" #include "support/utils.h" #include PodcastWidget::PodcastWidget(PodcastService *s, QWidget *p) : SinglePageWidget(p) , srv(s) , proxy(this) { subscribeAction = new Action(Icons::self()->addNewItemIcon, tr("Add Subscription"), this); unSubscribeAction = new Action(Icons::self()->removeIcon, tr("Remove Subscription"), this); downloadAction = new Action(Icons::self()->downloadIcon, tr("Download Episodes"), this); deleteAction = new Action(MonoIcon::icon(FontAwesome::trash, MonoIcon::constRed, MonoIcon::constRed), tr("Delete Downloaded Episodes"), this); cancelDownloadAction = new Action(Icons::self()->cancelIcon, tr("Cancel Download"), this); markAsNewAction = new Action(MonoIcon::icon(FontAwesome::asterisk, Utils::monoIconColor()), tr("Mark Episodes As New"), this); markAsListenedAction = new Action(tr("Mark Episodes As Listened"), this); unplayedOnlyAction = new Action(Icons::self()->rssListIcon, tr("Show Unplayed Only"), this); unplayedOnlyAction->setCheckable(true); proxy.setSourceModel(srv); view->setModel(&proxy); view->alwaysShowHeader(); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(subscribeAction, SIGNAL(triggered()), this, SLOT(subscribe())); connect(unSubscribeAction, SIGNAL(triggered()), this, SLOT(unSubscribe())); connect(downloadAction, SIGNAL(triggered()), this, SLOT(download())); connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteDownload())); connect(cancelDownloadAction, SIGNAL(triggered()), this, SLOT(cancelDownload())); connect(markAsNewAction, SIGNAL(triggered()), this, SLOT(markAsNew())); connect(markAsListenedAction, SIGNAL(triggered()), this, SLOT(markAsListened())); connect(unplayedOnlyAction, SIGNAL(toggled(bool)), this, SLOT(showUnplayedOnly(bool))); view->setMode(ItemView::Mode_DetailedTree); Configuration config(metaObject()->className()); view->load(config); MenuButton *menu=new MenuButton(this); ToolButton *addSub=new ToolButton(this); ToolButton *unplayedOnlyBtn=new ToolButton(this); addSub->setDefaultAction(subscribeAction); unplayedOnlyBtn->setDefaultAction(unplayedOnlyAction); menu->addActions(createViewActions(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); init(ReplacePlayQueue|AppendToPlayQueue|Refresh, QList() << menu << unplayedOnlyBtn, QList() << addSub); view->addAction(subscribeAction); view->addAction(unSubscribeAction); view->addSeparator(); view->addAction(downloadAction); view->addAction(deleteAction); view->addAction(cancelDownloadAction); view->addSeparator(); view->addAction(markAsNewAction); view->addAction(markAsListenedAction); } PodcastWidget::~PodcastWidget() { Configuration config(metaObject()->className()); view->save(config); } QStringList PodcastWidget::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } return srv->filenames(proxy.mapToSource(selected), allowPlaylists); } QList PodcastWidget::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return srv->songs(proxy.mapToSource(selected), allowPlaylists); } void PodcastWidget::headerClicked(int level) { if (0==level) { emit close(); } } void PodcastWidget::subscribe() { if (0==PodcastSearchDialog::instanceCount()) { PodcastSearchDialog *dlg=new PodcastSearchDialog(srv, this); dlg->show(); } } void PodcastWidget::unSubscribe() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } PodcastService::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (!item->isPodcast()) { return; } if (MessageBox::No==MessageBox::warningYesNo(this, tr("Unsubscribe from '%1'?").arg(item->name))) { return; } srv->unSubscribe(static_cast(item)); } enum GetEp { GetEp_Downloaded, GetEp_NotDownloaded, GetEp_Listened, GetEp_NotListened }; static QMap > getEpisodes(PodcastService::Proxy &proxy, const QModelIndexList &selected, GetEp get) { QMap > urls; foreach (const QModelIndex &idx, selected) { PodcastService::Item *item=static_cast(proxy.mapToSource(idx).internalPointer()); if (item->isPodcast()) { foreach (PodcastService::Episode *episode, static_cast(item)->episodes) { if ((GetEp_Downloaded==get && !episode->localFile.isEmpty()) || (GetEp_NotDownloaded==get && episode->localFile.isEmpty()) || (GetEp_Listened==get && episode->played) || (GetEp_NotListened==get && !episode->played)) { urls[static_cast(item)].insert(episode); } } } else { PodcastService::Episode *episode=static_cast(item); if ((GetEp_Downloaded==get && !episode->localFile.isEmpty()) || (GetEp_NotDownloaded==get && episode->localFile.isEmpty()) || (GetEp_Listened==get && episode->played) || (GetEp_NotListened==get && !episode->played)) { urls[episode->parent].insert(episode); } } } return urls; } void PodcastWidget::download() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(true), GetEp_NotDownloaded); if (!urls.isEmpty()) { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to download the selected podcast episodes?"))) { return; } QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->downloadPodcasts(it.key(), it.value().toList()); } } } void PodcastWidget::cancelDownload() { if (srv->isDownloading() && MessageBox::Yes==MessageBox::questionYesNo(this, tr("Cancel podcast episode downloads (both current and any that are queued)?"))) { srv->cancelAll(); } } void PodcastWidget::deleteDownload() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_Downloaded); if (!urls.isEmpty()) { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to the delete downloaded files of the selected podcast episodes?"))) { return; } QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->deleteDownloadedPodcasts(it.key(), it.value().toList()); } } } void PodcastWidget::markAsNew() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_Listened); if (!urls.isEmpty()) { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to mark the selected podcast episodes as new?"))) { return; } QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->setPodcastsAsListened(it.key(), it.value().toList(), false); } } } void PodcastWidget::markAsListened() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_NotListened); if (!urls.isEmpty()) { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to mark the selected podcast episodes as listened?"))) { return; } QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->setPodcastsAsListened(it.key(), it.value().toList(), true); } } } void PodcastWidget::showUnplayedOnly(bool on) { proxy.showUnplayedOnly(on); } void PodcastWidget::doSearch() { QString text=view->searchText().trimmed(); proxy.update(text); if (proxy.enabled() && !text.isEmpty()) { view->expandAll(); } } void PodcastWidget::refresh() { QModelIndexList sel=view->selectedIndexes(false); QModelIndexList selected; foreach (const QModelIndex &i, sel) { if (!i.parent().isValid()) { selected+=proxy.mapToSource(i); } } if (selected.isEmpty() || selected.count()==srv->podcastCount()) { if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Refresh all subscriptions?"), tr("Refresh"), GuiItem(tr("Refresh All")), StdGuiItem::cancel())) { srv->refreshAll(); } return; } switch (MessageBox::questionYesNoCancel(this, tr("Refresh all subscriptions, or only those selected?"), tr("Refresh"), GuiItem(tr("Refresh All")), GuiItem(tr("Refresh Selected")))) { case MessageBox::Yes: srv->refreshAll(); break; case MessageBox::No: srv->refresh(selected); break; default: break; } } void PodcastWidget::configure() { PodcastSettingsDialog dlg(this); if (QDialog::Accepted==dlg.exec()) { int changes=dlg.changes(); if (changes&PodcastSettingsDialog::RssUpdate) { srv->startRssUpdateTimer(); } } } void PodcastWidget::controlActions() { SinglePageWidget::controlActions(); QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... downloadAction->setEnabled(!selected.isEmpty()); unSubscribeAction->setEnabled(1==selected.count() && static_cast(proxy.mapToSource(selected.first()).internalPointer())->isPodcast()); downloadAction->setEnabled(!selected.isEmpty()); deleteAction->setEnabled(!selected.isEmpty()); cancelDownloadAction->setEnabled(!selected.isEmpty()); markAsNewAction->setEnabled(!selected.isEmpty()); markAsListenedAction->setEnabled(!selected.isEmpty()); } cantata-2.2.0/online/podcastwidget.h000066400000000000000000000036571316350454000174400ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PODCAST_WIDGET_H #define PODCAST_WIDGET_H #include #include "widgets/singlepagewidget.h" #include "podcastservice.h" class Action; class ProxyModel; class PodcastWidget : public SinglePageWidget { Q_OBJECT public: PodcastWidget(PodcastService *s, QWidget *p); virtual ~PodcastWidget(); QStringList selectedFiles(bool allowPlaylists) const; QList selectedSongs(bool allowPlaylists) const; private Q_SLOTS: void headerClicked(int level); void subscribe(); void unSubscribe(); void download(); void cancelDownload(); void deleteDownload(); void markAsNew(); void markAsListened(); void showUnplayedOnly(bool on); private: void doSearch(); void refresh(); void configure(); void controlActions(); private: PodcastService *srv; PodcastService::Proxy proxy; Action *subscribeAction; Action *unSubscribeAction; Action *downloadAction; Action *deleteAction; Action *cancelDownloadAction; Action *markAsNewAction; Action *markAsListenedAction; Action *unplayedOnlyAction; }; #endif cantata-2.2.0/online/rssparser.cpp000066400000000000000000000210501316350454000171410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "rssparser.h" #include #include #include static const char * constITunesNameSpace = "http://www.itunes.com/dtds/podcast-1.0.dtd"; static const char * constMediaNameSpace = "http://search.yahoo.com/mrss/"; using namespace RssParser; static bool parseUntil(QXmlStreamReader &reader, const QString &elem) { while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement() && reader.name() == elem) { return true; } } return false; } static void consumeCurrentElement(QXmlStreamReader &reader) { int level = 1; while (0!=level && !reader.atEnd()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement: ++level; break; case QXmlStreamReader::EndElement: --level; break; default: break; } } } static QString capitaliseWord(QString str) { if (str.isEmpty() || !str[0].isLetter()) { return str; } str=str.toLower(); str[0]=str[0].toUpper(); return str; } static QDateTime parseRfc822DateTime(const QString &text) { // This sucks but we need it because some podcasts don't quite follow the // spec properly - they might have 1-digit hour numbers for example. QRegExp re("([a-zA-Z]{3}),? (\\d{1,2}) ([a-zA-Z]{3}) (\\d{4}) (\\d{1,2}):(\\d{1,2}):(\\d{1,2})"); if (-1==re.indexIn(text)) { return QDateTime(); } QDateTime dt(QDate::fromString(QString("%1 %2 %3 %4").arg(re.cap(1), re.cap(3), re.cap(2), re.cap(4)), Qt::TextDate), QTime(re.cap(5).toInt(), re.cap(6).toInt(), re.cap(7).toInt())); if (dt.isValid()) { return dt; } return QDateTime(QDate::fromString(QString("%1 %2 %3 %4").arg(capitaliseWord(re.cap(1)), capitaliseWord(re.cap(3)), re.cap(2), re.cap(4)), Qt::TextDate), QTime(re.cap(5).toInt(), re.cap(6).toInt(), re.cap(7).toInt())); } static QUrl parseImage(QXmlStreamReader &reader) { QUrl url; while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement()) { if (QLatin1String("url")==reader.name()) { url=QUrl::fromEncoded(reader.readElementText().toLatin1()); } else { consumeCurrentElement(reader); } } else if (reader.isEndElement()) { break; } } return url; } static Episode parseEpisode(QXmlStreamReader &reader) { Episode ep; bool isAudio=false; QUrl guidUrl; while (!reader.atEnd()) { reader.readNext(); const QStringRef name = reader.name(); if (reader.isStartElement()) { if (QLatin1String("title")==name) { ep.name=reader.readElementText(); } else if (QLatin1String("duration")==name && constITunesNameSpace==reader.namespaceUri()) { QStringList parts = reader.readElementText().split(':'); if (2==parts.count()) { ep.duration=(parts[0].toInt() * 60) + parts[1].toInt(); } else if (parts.count()>=3) { ep.duration=(parts[0].toInt() * 60*60) + (parts[1].toInt() * 60) + parts[2].toInt(); } } else if (0==ep.duration && QLatin1String("content")==name && constMediaNameSpace==reader.namespaceUri()) { ep.duration=reader.attributes().value(QLatin1String("duration")).toString().toUInt(); consumeCurrentElement(reader); } else if (QLatin1String("enclosure")==name) { static QSet audioFormats; if (audioFormats.isEmpty()) { audioFormats.insert(QLatin1String("mp3")); audioFormats.insert(QLatin1String("MP3")); audioFormats.insert(QLatin1String("ogg")); audioFormats.insert(QLatin1String("OGG")); audioFormats.insert(QLatin1String("wma")); audioFormats.insert(QLatin1String("WMA")); } QString type=reader.attributes().value(QLatin1String("type")).toString(); if (type.startsWith(QLatin1String("audio/")) || audioFormats.contains(type)) { isAudio=true; ep.url=QUrl::fromEncoded(reader.attributes().value(QLatin1String("url")).toString().toLatin1()); } else if (type.startsWith(QLatin1String("video/")) ) { // At least one broken feed (BUG: 588) has the audio podcast listed as video/mp4, // ...but the path ends in .mp3 !!! QUrl url=QUrl::fromEncoded(reader.attributes().value(QLatin1String("url")).toString().toLatin1()); QString path=url.path(); if (path.endsWith(QLatin1String(".mp3"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(".ogg"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(".wma"), Qt::CaseInsensitive)) { ep.url=url; } else { ep.video=true; } } consumeCurrentElement(reader); } else if (QLatin1String("guid")==name) { guidUrl=QUrl(reader.readElementText()); } else if (QLatin1String("pubDate")==name) { ep.publicationDate=parseRfc822DateTime(reader.readElementText()); } else if (QLatin1String("description")==name) { ep.description=reader.readElementText(); } else { consumeCurrentElement(reader); } } else if (reader.isEndElement()) { break; } } // Sometimes the url entry in 'enclusure' is empty, but there is a url in 'guid' - so use // that if present (BUG: 602) if (isAudio && ep.url.isEmpty() && !guidUrl.isEmpty()) { ep.url=guidUrl; } return ep; } Channel RssParser::parse(QIODevice *dev, bool getEpisodes, bool getDescription) { Channel ch; QXmlStreamReader reader(dev); if (parseUntil(reader, QLatin1String("rss")) && parseUntil(reader, QLatin1String("channel"))) { while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement()) { const QStringRef name = reader.name(); if (ch.name.isEmpty() && QLatin1String("title")==name) { ch.name=reader.readElementText(); } else if (QLatin1String("image")==name && ch.image.isEmpty()) { if (constITunesNameSpace==reader.namespaceUri()) { ch.image=reader.attributes().value(QLatin1String("href")).toString(); consumeCurrentElement(reader); } else { ch.image=parseImage(reader); } } else if (getEpisodes && QLatin1String("item")==name) { Episode ep=parseEpisode(reader); if (!ep.name.isEmpty() && !ep.url.isEmpty()) { ch.episodes.append(ep); } else if (ep.video) { ch.video=true; } } else if (getDescription && QLatin1String("description")==name && ch.description.isEmpty()) { ch.description=reader.readElementText(); } else if (getDescription && QLatin1String("summary")==name && ch.description.isEmpty() && constITunesNameSpace==reader.namespaceUri()) { ch.description=reader.readElementText(); } else { consumeCurrentElement(reader); } } else if (reader.isEndElement()) { break; } } } if (ch.video && !ch.episodes.isEmpty()) { ch.video=false; } return ch; } cantata-2.2.0/online/rssparser.h000066400000000000000000000027651316350454000166220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef RSSPARSER_H #define RSSPARSER_H #include #include #include #include #include class QIODevice; namespace RssParser { struct Episode { Episode() : duration(0), video(false) { } QString name; QString description; QDateTime publicationDate; unsigned int duration; QUrl url; bool video; }; struct Channel { Channel() : video(false) { } QString name; QUrl image; QList episodes; QString description; bool video; bool isValid() const { return !name.isEmpty(); } }; Channel parse(QIODevice *dev, bool getEpisodes=true, bool getDescription=false); } #endif cantata-2.2.0/online/soundcloudservice.cpp000066400000000000000000000076611316350454000206710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "soundcloudservice.h" #include "network/networkaccessmanager.h" #include "mpd-interface/mpdconnection.h" #include #include #include static const QLatin1String constName("soundcloud"); static const QLatin1String constApiKey("0cb23dce473528973ce74815bd36a334"); static const QLatin1String constUrl("https://api.soundcloud.com/tracks"); SoundCloudService::SoundCloudService(QObject *p) : OnlineSearchService(p) { icn.addFile(":"+constName); } QString SoundCloudService::name() const { return constName; } QString SoundCloudService::title() const { return QLatin1String("SoundCloud"); } QString SoundCloudService::descr() const { return tr("Search for tracks from soundcloud.com"); } void SoundCloudService::search(const QString &key, const QString &value) { Q_UNUSED(key); if (value==currentValue) { return; } clear(); cancel(); currentValue=value; if (currentValue.isEmpty()) { return; } QUrl searchUrl(constUrl); QUrlQuery query; query.addQueryItem("client_id", constApiKey); query.addQueryItem("q", currentValue); searchUrl.setQuery(query); QNetworkRequest req(searchUrl); req.setRawHeader("Accept", "application/json"); job=NetworkAccessManager::self()->get(req); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); emit searching(); emit dataChanged(QModelIndex(), QModelIndex()); } void SoundCloudService::jobFinished() { NetworkJob *j=dynamic_cast(sender()); if (!j || j!=job) { return; } j->deleteLater(); QList songs; if (j->ok()) { QVariant result=QJsonDocument::fromJson(j->readAll()).toVariant(); if (result.isValid()) { QVariantList list = result.toList(); foreach(const QVariant &item, list) { QVariantMap details=item.toMap(); if (details["title"].toString().isEmpty()) { continue; } Song song; QUrl url = details["stream_url"].toUrl(); QUrlQuery query; query.addQueryItem("client_id", constApiKey); url.setQuery(query); // MPD does not seem to support https :-( if (QLatin1String("https")==url.scheme() && !MPDConnection::self()->urlHandlers().contains(QLatin1String("https"))) { url.setScheme(QLatin1String("http")); } song.file=url.toString(); // We don't have a real artist name, but username is the most similar thing we have song.artist=details["user"].toMap()["username"].toString(); song.title=details["title"].toString(); song.genres[0]=details["genre"].toString(); song.year=details["release_year"].toInt(); song.time=details["duration"].toUInt()/1000; song.fillEmptyFields(); songs.append(song); } } } results(songs); job=0; emit dataChanged(QModelIndex(), QModelIndex()); } cantata-2.2.0/online/soundcloudservice.h000066400000000000000000000024061316350454000203260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SOUNDCLOUD_SERVICE_H #define SOUNDCLOUD_SERVICE_H #include "onlinesearchservice.h" class SoundCloudService : public OnlineSearchService { Q_OBJECT public: SoundCloudService(QObject *p); virtual ~SoundCloudService() { } QString name() const; QString title() const; QString descr() const; void search(const QString &key, const QString &value); private Q_SLOTS: void jobFinished(); }; #endif cantata-2.2.0/playlists/000077500000000000000000000000001316350454000151535ustar00rootroot00000000000000cantata-2.2.0/playlists/cantata-dynamic000077500000000000000000001444141316350454000201460ustar00rootroot00000000000000#!/usr/bin/perl # Cantata-Dynamic # # Copyright (c) 2011-2016 Craig Drummond # # ---- # # 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; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. use IO::Socket::INET; use POSIX; use File::stat; use File::Basename; use Cwd 'abs_path'; use Socket; use IO::Socket; use threads; use threads::shared; use URI::Escape; use Encode; use Socket qw(:all); my $isServerMode =0; my $dynamicIsActive =1; my $currentStatus ="IDLE"; $testMode=0; $MIN_PLAY_QUEUE_DESIRED_LENGTH=10; $MAX_PLAY_QUEUE_DESIRED_LENGTH=500; $DEFAULT_PLAY_QUEUE_DESIRED_LENGTH=10; $playQueueDesiredLength=$DEFAULT_PLAY_QUEUE_DESIRED_LENGTH; my $mpdHost : shared = "localhost"; my $mpdPort : shared = "6600"; my $mpdPasswd : shared =""; my $readChannel : shared = "cantata-dynamic-in"; my $writeChannel = "cantata-dynamic-out"; my $sock; # This is NOT shared between HTTP thread and standard thread... my $currentStatusTime = time; my $httpPort : shared = 0; # Read MPDs host, port, and password details from env - if set sub readConnectionDetails() { my $hostEnv=$ENV{'MPD_HOST'}; my $portEnv=$ENV{'MPD_PORT'}; if (length($portEnv)>2) { $mpdPort=$portEnv; } if (length($hostEnv)>2) { my $sep = index($hostEnv, '@'); if ($sep>0) { $mpdPasswd=substr($hostEnv, 0, $sep); $mpdHost=substr($hostEnv, $sep+1, length($hostEnv)-$sep); } else { $mpdHost=$hostEnv; } } } sub readReply() { local $data; my $socketData; while ($sock->connected()) { $sock->recv($data, 1024); if (! $data) { return ''; } $socketData="${socketData}${data}"; $data=""; if (($socketData=~ m/(OK)$/) || ($socketData=~ m/^(OK)/)) { return $socketData; } elsif ($socketData=~ m/^(ACK)/) { return ''; } } } # Connect to MPD sub connectToMpd() { if ($testMode==1) { print "Connecting to MPD ${mpdHost}:${mpdPort}\n"; } my $connDetails=""; if ($mpdHost=~ m/^(\/)/) { $sock = new IO::Socket::UNIX(Peer => $mpdHost, Type => 0); $connDetails=$mpdHost; } else { $sock = new IO::Socket::INET(PeerAddr => $mpdHost, PeerPort => $mpdPort, Proto => 'tcp'); $connDetails="${mpdHost}:${mpdPort}"; } if ($sock && $sock->connected()) { if (&readReply($sock)) { if ($mpdPasswd) { if ($testMode==1) { print "Send password\n"; } $sock->send("password ${mpdPasswd} \n"); if (! &readReply($sock)) { print "ERROR: Invalid password\n"; eval { close $sock; };undef $sock; } } if ($isServerMode==1) { $sock->send("subscribe ${readChannel}\n"); &readReply($sock) } } else { print "ERROR: Failed to read connection reply fom MPD (${connDetails})\n"; close($sock); } } else { print "ERROR: Failed to connect to MPD (${connDetails})\n"; } return $sock; } sub sendCommand() { my $cmd = shift; if ($testMode==1) { print "Send command ${cmd}\n"; } my $status = 0; if (! ($sock && $sock->connected())) { $sock=&connectToMpd(); } my $sockData; $cmd="${cmd}\n"; if ($sock && $sock->connected()) { print $sock encode('utf-8' => $cmd); $sockData=&readReply($sock); } my $dataLen=length($sockData); if ($testMode==1) { print "Received ${dataLen} bytes\n"; } if ($sockData ne '') { return decode_utf8($sockData); } return $sockData; } sub waitForEvent() { while (1) { if (! ($sock && $sock->connected())) { $sock=&connectToMpd(); } if ($sock && $sock->connected()) { if (0==$isServerMode) { &sendCommand("idle player playlist"); return 1; } else { my $sockData=&sendCommand("idle player playlist message"); my @lines = split("\n", $sockData); my $haveNonMsg=0; foreach my $line (@lines) { if ($line=~ m/^(changed\:\ message)/) { &handleClientMessage(); } else { $haveNonMsg=1; if ($testMode==1) { printf "Idle message: $line\n"; } } } if ($haveNonMsg==1) { return 1; } } } else { return 0; } } } sub getEntries() { my $command=shift; my $key=shift; my @entries = (); my $data=&sendCommand($command); if (defined($data)) { my @lines=split('\n', $data); foreach my $line (@lines) { if ($line=~ m/^($key\:)/) { my $sep = index($line, ':'); if ($sep>0) { my $entry=substr($line, $sep+2, length($line)-($sep+1)); push (@entries, $entry); } } } } return @entries; } sub baseDir() { if ($^O eq "darwin") { # MacOSX return "$ENV{'HOME'}/Library/Caches/cantata/cantata/dynamic"; } # Linux my $cacheDir=$ENV{'XDG_CACHE_HOME'}; if (!$cacheDir) { $cacheDir="$ENV{'HOME'}/.cache"; } $cacheDir="${cacheDir}/cantata/dynamic"; return $cacheDir } sub lockFile() { my $fileName=&baseDir(); $fileName="${fileName}/lock"; return $fileName; } $lastArtistSearch=""; @artistSearchResults=(); $api_key="5a854b839b10f8d46e630e8287c2299b"; # Query LastFM for artists similar to supplied artist sub querySimilarArtists() { my $artist=uri_escape(shift); if ($artist ne $lastArtistSearch) { @artistSearchResults=(); @artists=`wget "https://ws.audioscrobbler.com/2.0/?method=artist.getSimilar&api_key=${api_key}&artist=${artist}&format=xml&limit=50" -O - | grep "" | grep -v "similarartists artist"`; foreach my $artist (@artists) { $artist =~ s///g; $artist =~ s/<\/name>//g; $artist =~ s/&/&/g; $artist =~ s/\n//g; $artistSearchResults[$artistNum]=$artist; $artistNum++; } } } $mpdDbUpdated=0; $rulesChanged=1; $includeRules; $excludeRules; $lastIncludeRules; $lastExcludeRules; $initialRead=1; $rulesTimestamp=0; $numMpdSongs=0; $ratingFrom=0; $ratingTo=0; # If we have no include rules, then song list is taken from tracks with ratings # Therefore, there is no need to subsequently filter - and ratingFilter would be # set to 0 $ratingFilter=1; $lastRatingFrom=0; $lastRatingTo=0; $minDuration=0; $maxDuration=0; $lastMinDuration=0; $lastMaxDuration=0; # Determine if rules file has been updated sub checkRulesChanged() { if ($initialRead==1) { # Always changed on first run... $rulesChanged=1; $initialRead=0; } elsif ($lastRatingFrom!=$ratingFrom || $lastRatingTo!=$ratingTo) { $rulesChanged=1; } elsif ($lastMinDuration!=$minDuration || $lastMaxDuration!=$maxDuration) { $rulesChanged=1; } elsif ( scalar(@lastIncludeRules)!=scalar(@includeRules) || scalar(@lastExcludeRules)!=scalar(@excludeRules)) { # Different number of rules $rulesChanged=1; } else { # Same number of rules, so need to check if the rules themselves have changed or not... $rulesChanged=0; for (my $i=0; $i0) { # Create rule for each date (as MPDs search does not take ranges) my $baseRule=$rule; foreach my $date (@dates) { $type[$ruleNum]="${ruleMatch} ${baseRule} Date \"${date}\""; if ($artist ne "") { $type[$ruleNum]=$type[$ruleNum]." Artist \"${artist}\""; } if ($genre ne "") { $type[$ruleNum]=$type[$ruleNum]." Genre \"${genre}\""; } $ruleNum++; } } elsif ($artist ne "" || $genre ne "" || $rule ne "") { $type[$ruleNum]="${ruleMatch} $rule"; if ($artist ne "") { $type[$ruleNum]=$type[$ruleNum]." Artist \"${artist}\""; } if ($genre ne "") { $type[$ruleNum]=$type[$ruleNum]." Genre \"${genre}\""; } $ruleNum++; } } } if ($isInclude == 1) { @includeRules=@type; } else { @excludeRules=@type; } } # Read rules from ~/.cache/cantata/dynamic/rules # (or from ${filesDir}/rules in server mode) # # File format: # # Rating: # Duration: # Rule # : # : # Rule # # e.g. # # Rating:1-5 # Duration:30-900 # Rule # AlbumArtist:Various Artists # Genre:Dance # Rule # AlbumArtist:Wibble # Date:1980-1989 # Exact:false # Exclude:true # $activeFile=""; $activeLinksTo=""; sub readRules() { if ($activeFile eq "") { $activeFile=&baseDir(); $activeFile="${activeFile}/rules"; } unless (-e $activeFile) { $rulesChanged=0; return; } # Check if rules (well, the file it points to), has changed since the last read... my $currentActiveLink=abs_path($activeFile); $fileTime = stat($currentActiveLink)->mtime; if ($initialRead!=1 && $fileTime==$rulesTimestamp && $activeLinksTo eq $currentActiveLink) { # No change, so no need to read it again! $rulesChanged=0; return; } $activeLinksTo=$currentActiveLink; $rulesTimestamp=$fileTime; for(my $i=0; $i<10; $i++) { open(my $fileHandle, "<:encoding(utf8)", $activeFile); if (tell($fileHandle) != -1) { my @lines = <$fileHandle>; # Read into an array... my $ruleMatch="find"; my @dates=(); my @similarArtists=(); my $isInclude=1; my $currentRule=""; @includeRules=(); @excludeRules=(); $ratingFrom=0; $ratingTo=0; $minDuration=0; $maxDuration=0; $playQueueDesiredLength=$DEFAULT_PLAY_QUEUE_DESIRED_LENGTH; close($fileHandle); foreach my $line (@lines) { if (! ($line=~ m/^(#)/)) { $line =~ s/\n//g; my $sep = index($line, ':'); if ($sep>0) { $key=substr($line, 0, $sep); $val=substr($line, $sep+1, length($line)-$sep); } else { $key=$line; $val=""; } if ($key=~ m/^(Rule)/) { # New rule... if (length($currentRule)>1 || scalar(@similarArtists)>0 || scalar(@dates)>0 || scalar(@genres)>0) { &saveRule($currentRule, \@dates, \@similarArtists, \@genres, $ruleMatch, $isInclude); } $currentRule=""; @dates=(); @similarArtists=(); @genres=(); $isInclude=1; $ruleMatch="find"; } elsif ($key=~ m/^(Rating)/) { my @vals = split("-", $val); if (scalar(@vals)==2) { $ratingFrom=int($vals[0]); $ratingTo=int($vals[1]); if ($ratingFrom > $ratingTo) { my $tmp=$ratingFrom; $ratingFrom=$ratingTo; $ratingTo=$tmp; } } } elsif ($key=~ m/^(Duration)/) { my @vals = split("-", $val); if (scalar(@vals)==2) { $minDuration=int($vals[0]); $maxDuration=int($vals[1]); if ($minDuration > $maxDuration && $maxDuration > 0 ) { my $tmp=$minDuration; $minDuration=$maxDuration; $maxDuration=$tmp; } } } elsif ($key=~ m/^(NumTracks)/) { if ($val >= $MIN_PLAY_QUEUE_DESIRED_LENGTH && $val <= $MAX_PLAY_QUEUE_DESIRED_LENGTH) { $playQueueDesiredLength=$val; if ($playQueueDesiredLength % 2 > 0) { $playQueueDesiredLength += 1; } } } else { if ($key eq "Date") { my @dateVals = split("-", $val); if (scalar(@dateVals)==2) { my $fromDate=int($dateVals[0]); my $toDate=int($dateVals[1]); if ($fromDate > $toDate) { # Fix dates if from>to!!! my $tmp=$fromDate; $fromDate=$toDate; $toDate=$tmp; } my $pos=0; for(my $d=$fromDate; $d<=$toDate; $d++) { $dates[$pos]=$d; $pos++; } } else { @dates=($val) } } elsif ($key eq "Genre" && $val =~ m{\*}) { # Wildcard genre - get list of genres from MPD, and find the ones that contain the genre string. $val =~ s/\*//g; my @mpdGenres = &getEntries("list genre", 'Genre'); my $pos=0; foreach my $genre (@mpdGenres) { if ($genre ne "" && $genre =~/$val/i) { $genres[$pos]=$genre; $pos++; } } } elsif ($key eq "Artist" || $key eq "Album" || $key eq "AlbumArtist" || $key eq "Composer" || $key eq "Comment" || $key eq "Title" || $key eq "Genre" || $key eq "File") { $currentRule="${currentRule} ${key} \"${val}\""; } elsif ($key eq "SimilarArtists") { &querySimilarArtists($val); # Perform a last.fm query to find similar artists @artistSearchResults; # Save results of query @artistSearchResults=uniq(@artistSearchResults); # Ensure we only have unique entries... if (scalar(@artistSearchResults)>1) { my @mpdArtists = (); my $pos=0; # Get MPD artists... my @mpdResponse=&getEntries("list artist", 'Artist'); foreach my $artist (@mpdResponse) { if ($artist ne "" && $artist ne $val) { $mpdArtists[$pos]=$artist; $pos++; } } @mpdArtists=uniq(@mpdArtists); # Now check which last.fm artists MPD actually has... my $pos=0; foreach my $artist (@artistSearchResults) { my @match = grep(/^$artist/i, @mpdArtists); if (scalar(@match)>0) { $similarArtists[$pos]=$artist; $pos++; } } } $similarArtists[scalar(@similarArtists)]=$val; # Add ourselves!!! } elsif ($key eq "Exact" && $val eq "false") { $ruleMatch="search"; } elsif ($key eq "Exclude" && $val eq "true") { $isInclude=0; } } } } if (length($currentRule)>1 || scalar(@similarArtists)>0 || scalar(@dates)>0 || scalar(@genres)>0) { &saveRule($currentRule, \@dates, \@similarArtists, \@genres, $ruleMatch, $isInclude); } if (1==$testMode) { print "INCLUDE--------------\n"; foreach my $rule (@includeRules) { print "${rule}\n"; } print "EXCLUDE--------------\n"; foreach my $rule (@excludeRules) { print "${rule}\n"; } print "---------------------\n"; print "RATING: ${ratingFrom} -> ${ratingTo}\n"; print "DURATION: ${minDuration} -> ${maxDuration}\n"; } &checkRulesChanged(); return 1; } } &checkRulesChanged(); return 0; } # Remove duplicate entries from an array... sub uniq { return keys %{{ map { $_ => 1 } @_ }}; } # Send message to Cantata application... sub sendMessage() { my $method=shift; my $argument=shift; if (0==$isServerMode) { if ($^O eq "darwin") { # MacOSX # TODO: How to send a dbus (or other) message to Cantata application???? } else { # Linux system("qdbus mpd.cantata /cantata ${method} ${argument}"); if ( $? == -1 ) { # Maybe qdbus is not installed? Try dbus-send... system("dbus-send --type=method_call --session --dest=mpd.cantata /cantata mpd.cantata.${method} string:${argument}"); } } } else { my $clientId=shift; if ($clientId eq "http") { return; } if ($clientId ne '') { &sendCommand("sendmessage ${writeChannel}-${clientId} \"${method}:${argument}\""); } else { &sendCommand("sendmessage ${writeChannel} \"${method}:${argument}\""); } } } # Parse sticker value, and check that its in range. sub songRatingInRange() { my $stickerEntry=shift; my @parts = split("=", $stickerEntry); if (2==scalar(@parts)) { my $rating=scalar($parts[1]); if ($rating >= $ratingFrom && $rating <= $ratingTo) { return 1; } } return 0; } # Get all songs with a rating between ratingFrom & ratingTo sub getRatedSongs() { my @entries = (); my $data=&sendCommand("sticker find song \"\" rating"); if (defined($data)) { my @lines=split('\n', $data); my $file=""; foreach my $line (@lines) { if ($line=~ m/^(file\:)/) { my $sep = index($line, ':'); if ($sep>0) { $file=substr($line, $sep+2, length($line)-($sep+1)); } } elsif ($line=~ m/^(sticker\:)/) { my $sep = index($line, ':'); if ($sep>0) { my $entry=substr($line, $sep+2, length($line)-($sep+1)); if (1==&songRatingInRange($entry)) { push (@entries, $file); } } } } } return @entries; } # Is a file with rating from .. rating to? sub checkSongRatingInRange() { if ($ratingFilter = 0) { # Rating filter disabled - i,e. had no include rule, so song list is # taken from those with a rating... return 1; } if ($ratingFrom<=0 && $ratingTo<=0) { # No filter, so must be in range! return 1; } if ($numMpdSongs<1) { # No songs! return 0; } my $file=shift; my @entries = &getEntries("sticker get song \"${file}\" rating", 'sticker'); foreach my $entry (@entries) { if (1==&songRatingInRange($entry)) { return 1; } } # Song is not within range, so 'blank' its name out of list my $pos=shift; if (1==$testMode) { print "$file is NOT in rating range - remove:${pos} total:${numMpdSongs}!\n"; } splice @mpdSongs, $pos, 1; $numMpdSongs--; return 0; } # Check song duration is in range sub checkSongDurationInRange() { if ($minDuration<=0 && $maxDuration<=0) { return 1; } if ($numMpdSongs<1) { # No songs! return 0; } my $file=shift; my @entries = &getEntries("lsinfo \"${file}\"", 'Time'); if (scalar(@entries)==1) { my $val=int(@entries[0]); if ( (0==$minDuration || $val>=$minDuration) && (0==$maxDuration || $val<=$maxDuration) ) { return 1; } } # Song is not within range, so 'blank' its name out of list my $pos=shift; if (1==$testMode) { print "$file is NOT in duration range - remove:${pos} total:${numMpdSongs}!\n"; } splice @mpdSongs, $pos, 1; $numMpdSongs--; return 0; } # Use rules to obtain a list of songs from MPD... sub getSongs() { # If we have no current songs, or rules have changed, or MPD has been updated - then we need to run the rules against MPD to get song list... if (scalar(@mpdSongs)<1 || $rulesChanged==1 || $mpdDbUpdated==1) { $numMpdSongs=0; my @excludeSongs=(); if (scalar(@excludeRules)>0) { # Get list of songs that should be removed from the song list... my $mpdSong=0; foreach my $rule (@excludeRules) { my @songs = &getEntries($rule, 'file'); foreach my $song (@songs) { $excludeSongs[$mpdSong]=$song; $mpdSong++; } @excludeSongs=uniq(@excludeSongs); } } my %excludeSongSet = map { $_ => 1 } @excludeSongs; @mpdSongs=(); my $mpdSong=0; if (scalar(@includeRules)>0) { foreach my $rule (@includeRules) { my @songs = &getEntries($rule, 'file'); foreach my $song (@songs) { if (! $excludeSongSet{$song}) { $mpdSongs[$mpdSong]=$song; $mpdSong++; } } @mpdSongs=uniq(@mpdSongs); } } elsif ($ratingTo>=1 && $ratingFrom>=0) { if (1==$testMode) { print "No include rules, so get all songs in rating range ${ratingFrom}..${ratingTo}...\n"; } my @songs = &getRatedSongs(); foreach my $song (@songs) { if (! $excludeSongSet{$song}) { $mpdSongs[$mpdSong]=$song; $mpdSong++; } } } else { if (1==$testMode) { print "No include rules, so get all songs...\n"; } # No 'include' rules => get all songs! Do this by getting all Artists, and then all songs by each... my $tag='Artist'; my @entries = &getEntries("list ${tag}", $tag); foreach my $entry (@entries) { my @songs = &getEntries("find ${tag} \"${entry}\"", 'file'); foreach my $song (@songs) { if (! $excludeSongSet{$song}) { $mpdSongs[$mpdSong]=$song; $mpdSong++; } } } } if (scalar(@mpdSongs)<1) { if (1==$isServerMode) { $currentStatus="NO_SONGS"; $dynamicIsActive=0; &sendStatus(); } else { &sendMessage("showError", "NO_SONGS"); exit(0); } } elsif (1==$isServerMode) { &sendMessage("status", "HAVE_SONGS"); } if (1==$testMode) { print "SONGS--------------\n"; foreach my $song (@mpdSongs) { print "${song}\n"; } print "---------------------\n" } $numMpdSongs=scalar(@mpdSongs); } } # # Following canAdd/storeSong are used to remember songs that have been added to the playqueue, so that # we don't re-add them too soon! # @playQueueHistory=(); $playQueueHistoryLimit=0; $playQueueHistoryPos=0; sub canAdd() { my $file=shift; my $numSongs=shift; my $pqLimit=0; # Calculate a reasonable level for the history... if (1==$numSongs) { return 1; } elsif ($numSongs<5) { $pqLimit=int(($numSongs/2)+0.5); } else { $pqLimit=int(($numSongs*0.75)+0.5); if ($pqLimit>200) { $pqLimit=200; } } # If the history level has changed, then so must have the rules/mpd/whatever, so add this song anyway... if ($pqLimit != $playQueueHistoryLimit) { $playQueueHistoryLimit=$pqLimit; @playQueueHistory=(); return 1; } my $size=scalar(@playQueueHistory); if ($size>$playQueueHistoryLimit) { $size=$playQueueHistoryLimit; } for (my $i=0; $i<$size; ++$i) { if ($playQueueHistory[$i] eq $file) { return 0; } } return 1; } sub storeSong() { my $file=shift; if ($playQueueHistoryLimit<=0) { $playQueueHistoryLimit=5; } if ($playQueueHistoryPos>=$playQueueHistoryLimit) { $playQueueHistoryPos=0; } $playQueueHistory[$playQueueHistoryPos]=$file; $playQueueHistoryPos++; } # # This is the 'main' function of the dynamizer # sub populatePlayQueue() { &readConnectionDetails(); my $lastMpdDbUpdate=-1; while (1) { my $socketData=''; if (1==$dynamicIsActive) { # Use status to obtain the current song pos, and to check that MPD is running... $socketData=&sendCommand("status"); } elsif (1==$isServerMode) { while (0==$dynamicIsActive) { &waitForEvent(); } } if (defined($socketData)) { my @lines = split('\n', $socketData); my $playQueueLength=0; my $playQueueCurrentTrackPos=0; my $isPlaying=0; foreach my $val (@lines) { if ($val=~ m/^(song\:)/) { my @vals = split(": ", $val); if (scalar(@vals)==2) { $playQueueCurrentTrackPos=scalar($vals[1]); } } elsif ($val=~ m/^(state\:)/) { my @vals = split(": ", $val); if (scalar(@vals)==2 && $vals[1]=~ m/^(play)/) { $isPlaying=1; } } } # Call stats, so that we can obtain the last time MPD was updated. # We use this to determine when we need to refresh the searched set of songs $mpdDbUpdated=0; $socketData=&sendCommand("stats"); if (defined($socketData)) { my @lines = split('\n', $socketData); foreach my $val (@lines) { if ($val=~ m/^(db_update\:)/) { my @vals = split(": ", $val); if (scalar(@vals)==2) { my $mpdDbUpdate=scalar($vals[1]); if ($mpdDbUpdate!=$lastMpdDbUpdate) { $lastMpdDbUpdate=$mpdDbUpdate; $mpdDbUpdated=1; } } break; } } } # Get current playlist info $socketData=&sendCommand("playlist"); if (defined($socketData)) { my @lines = split('\n', $socketData); my $playQueueLength=scalar(@lines); if ($playQueueLength>0 && $lines[$playQueueLength-1]=~ m/^(OK)/) { $playQueueLength--; } # trim playlist start so that current becomes <=$playQueueDesiredLength/2 my $wantCurrentPos = $playQueueDesiredLength/2; for (my $i=0; $i < $playQueueCurrentTrackPos - ($wantCurrentPos-1); $i++) { &sendCommand("delete 0"); $playQueueLength--; } if ($playQueueLength<0) { $playQueueLength=0; } &readRules(); &getSongs(); if ($numMpdSongs>0) { # fill up playlist to 10 random tunes my $failues=0; my $added=0; while ($playQueueLength < $playQueueDesiredLength && $numMpdSongs>0) { my $pos=int(rand($numMpdSongs)); my $origFile=${mpdSongs[$pos]}; my $file=$origFile; $file =~ s/\\/\\\\/g; $file =~ s/\"/\\\"/g; if (&checkSongDurationInRange($file, $pos) && &checkSongRatingInRange($file, $pos)) { if ($failues > 100 || &canAdd($origFile, $numMpdSongs)) { if (&sendCommand("add \"${file}\"") ne '') { &storeSong($origFile); $playQueueLength++; $failues=0; $added++; } } else { # Song is already in playqueue history... $failues++; } } } # If we are not currently playing and we filled playqueue - then play first! if ($numMpdSongs>0 && $isPlaying==0 && $added==$playQueueDesiredLength) { &sendCommand("play 0") } } if ($numMpdSongs>0) { &waitForEvent(); } else { if (1==$isServerMode) { $currentStatus="NO_SONGS"; $dynamicIsActive=0; &sendStatus(); } else { &sendMessage("showError", "NO_SONGS"); exit(0); } } } elsif (0==$isServerMode) { sleep 2; } } elsif (0==$isServerMode) { sleep 2; } } } sub readPid() { my $fileName=shift; if (-e $fileName) { open(my $fileHandle, $fileName); my @lines = <$fileHandle>; close($fileHandle); if (scalar(@lines)>0) { my $pid=$lines[0]; return scalar($pid); } } return 0; } sub daemonize() { my $fileName=shift; # daemonize process... chdir '/'; umask 0; open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!"; open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!"; defined( my $pid = fork ) or die "Can't fork: $!"; exit if $pid; # dissociate this process from the controlling terminal that started it and stop being part # of whatever process group this process was a part of. POSIX::setsid() or die "Can't start a new session."; # callback signal handler for signals. $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signalHandler; $SIG{PIPE} = 'ignore'; # Write our PID the lock file, so that 'stop' knows which PID to kill... open(my $fileHandle, ">${fileName}"); print $fileHandle $$; close $fileHandle; } sub start() { my $pidFile=&lockFile(); my $pid=&readPid($pidFile); if ($pid>0) { $exists = kill 0, $pid; if ($exists) { print "PROCESS $pid is running!\n"; return; } } my $fileName=&lockFile(); &daemonize($fileName); &sendMessage("dynamicStatus", "running"); &populatePlayQueue(); } sub signalHandler { if (0==$isServerMode) { unlink(&lockFile()); &sendMessage("dynamicStatus", "stopped"); } else { unlink($pidFile); } exit(0); } sub stop() { my $pidFile=&lockFile(); my $pid=&readPid($pidFile); if ($pid>0) { system("kill", $pid); system("pkill", "-P", $pid); } } # ##################################### # SERVER MODE # ##################################### $filesDir="/var/lib/mpd/dynamic"; $pidFile="/var/run/cantata-dynamic/pid"; sub encodeString() { my $str=shift; $str =~ s/\"/\{q\}/g; $str =~ s/\{/\{ob\}/g; $str =~ s/\}/\{cb\}/g; $str =~ s/\n/\{n\}/g; $str =~ s/\:/\{c\}/g; return $str; } sub decodeString() { my $str=shift; $str =~ s/\{c\}/\:/g; $str =~ s/\{n\}/\n/g; $str =~ s/\{cb\}/\}/g; $str =~ s/\{ob\}/\{/g; $str =~ s/\{q\}/\"/g; return $str; } # Attempt to load a config file that will specify MPD connection settings and dynamic folder location sub loadConfig() { my $config=shift; if (!$config || ($config=~ m/^(default)/)) { $config="/etc/cantata-dynamic.conf"; } open(my $fileHandle, $config) || die "ERROR: Failed to load config $config - $!\n"; $activeFile="/var/run/cantata-dynamic/rules"; if (tell($fileHandle) != -1) { my @lines = <$fileHandle>; # Read into an array... close($fileHandle); foreach my $line (@lines) { if (! ($line=~ m/^(#)/)) { $line =~ s/\n//g; my $sep = index($line, '='); if ($sep>0) { $key=substr($line, 0, $sep); $val=substr($line, $sep+1, length($line)-$sep); if ($key=~ m/^(filesDir)/) { $filesDir=$val; } elsif ($key=~ m/^(activeFile)/) { $activeFile=$val; } elsif ($key=~ m/^(mpdHost)/) { $mpdHost=$val; } elsif ($key=~ m/^(mpdPort)/) { $mpdPort=$val; } elsif ($key=~ m/^(mpdPassword)/) { $mpdPasswd=$val; } elsif ($key=~ m/^(pidFile)/) { $pidFile=$val; } elsif ($key=~ m/^(httpPort)/) { $httpPort=$val; } } } } } # Create folders, if these do not already exist... my $pidDir=dirname($pidFile); my $activeFileDir=dirname($activeFile); unless (-d $pidDir) { mkdir $pidDir or die; } unless (-d $activeFileDir) { mkdir $activeFileDir or die; } unless (-d $filesDir) { mkdir $filesDir or die; } } sub readRulesFile() { my @result=(); my $fileName=uri_unescape(shift); open(my $fileHandle, $fileName); if (tell($fileHandle) != -1) { my @lines = <$fileHandle>; # Read into an array... close($fileHandle); foreach my $line (@lines) { if ($line =~ /\n$/) { push(@result, $line); } else { push(@result, $line."\n"); } } } return @result; } sub listAllRules() { my $req=shift; my $clientId=shift; my $showContents=shift; my @result=(); opendir(D, "$filesDir"); while (my $f = readdir(D)) { if ($f=~m/.rules$/) { push(@result, "FILENAME:${f}\n"); if ($showContents eq "" || $showContents>0) { push(@result, &readRulesFile($filesDir."/".$f)); } } } closedir(D); if ($req eq "http") { return @result; } else { my $response=&encodeString(join('', @result)); &sendMessage($req, $response, $clientId); } } sub determineActiveRules() { local $fileName=""; if (-f $activeFile && -l $activeFile) { $fileName=basename abs_path($activeFile); $fileName =~ s/.rules//g; } return $fileName; } sub getRulesContents() { my $req=shift; my $clientId=shift; my $origName=shift; my $name=&decodeString($origName); $name =~ s/\///g; my $active=&determineActiveRules(); my $rulesName=$name; $rulesName="${filesDir}/${rulesName}.rules"; my @result=&readRulesFile($rulesName); my $response=&encodeString(join('', @result)); &sendMessage($req, $response, $clientId); } sub saveRulesToFile() { my $req=shift; my $clientId=shift; my $origName=shift; my $name=&decodeString($origName); $name =~ s/\///g; if (! $name) { &sendMessage($req, "1", $clientId); return; } if ($name =~ m/\.rules/ || $name =~ m/\//) { &sendMessage($req, "2:${origName}", $clientId); return; } my $content=&decodeString(shift); # TODO: Parse content!!! my $rulesName=$name; $rulesName="${filesDir}/${rulesName}.rules"; open (my $fileHandle, '>'.$rulesName); if (tell($fileHandle) != -1) { print $fileHandle $content; close($fileHandle); $currentStatusTime = time; &sendMessage($req, "0:${origName}", $clientId); &sendStatus(); } else { &sendMessage($req, "3:${origName}", $clientId); } } sub deleteRules() { my $req=shift; my $clientId=shift; my $origName=shift; my $name=&decodeString($origName); $name =~ s/\///g; my $active=&determineActiveRules(); my $rulesName=$name; $rulesName="${filesDir}/${rulesName}.rules"; if (!unlink($rulesName)) { &sendMessage($req, "4:${origName}", $clientId); return; } $currentStatusTime = time; if ($name eq $active) { &control("stop"); } &sendMessage($req, "0:${origName}", $clientId); &sendStatus(); return; } sub control() { my $req=shift; my $clientId=shift; my $command=shift; if ($command eq "start") { $dynamicIsActive=1; $currentStatus="STARTING"; &sendCommand("clear"); &sendMessage($req, "0:${command}", $clientId); } elsif ($command eq "stop") { my $doClear=shift; $dynamicIsActive=0; $currentStatus="IDLE"; if ($doClear eq "true" || $doClear eq "1" || $doClear eq "clear") { &sendCommand("clear"); } &sendMessage($req, "0:${command}", $clientId); &sendStatus(); } else { &sendMessage($req, "5:${command}", $clientId); } } sub setActiveRules() { my $req=shift; my $clientId=shift; my $origName=shift; my $start=shift; my $name=&decodeString($origName); if ($name eq "") { &sendMessage($req, "1", $clientId); return; } my $rulesName=$name; my $active=&determineActiveRules(); if ($rulesName eq $active) { if (($start eq "start" || $start eq "1") && $currentStatus eq "IDLE") { $dynamicIsActive=1; $currentStatus="STARTING"; &sendCommand("clear"); &sendStatus(); } &sendMessage($req, "0:${origName}", $clientId); return; } $rulesName="${filesDir}/${rulesName}.rules"; if (-f $rulesName) { if (-l $activeFile) { if (!unlink($activeFile)) { &sendMessage($req, "6", $clientId); return; } } elsif (-f $activeFile) { &sendMessage($req, "7:${origName}", $clientId); return; } system("ln -s \"${rulesName}\" \"${activeFile}\""); if (0!=$?) { &sendMessage($req, "8:${origName}", $clientId); return; } if ($start eq "start" || $start eq "1") { $dynamicIsActive=1; $currentStatus="STARTING"; &sendCommand("clear"); } &sendMessage($req, "0:${origName}", $clientId); &sendStatus(); } else { &sendMessage($req, "9:${origName}", $clientId); } } sub statusResponse() { my $req=shift; my $clientId=shift; local $activeRules=&determineActiveRules(); $activeRules=&encodeString($activeRules); &sendMessage($req, "${currentStatus}:${currentStatusTime}:${activeRules}", $clientId); } sub sendStatus() { &statusResponse("status") } sub handleClientMessage() { $sock->send("readmessages\n"); my $sockData=&readReply($sock); my @lines = split("\n", $sockData); foreach my $line (@lines) { if ($line=~ m/^(message\:)/) { $line =~ s/message: //g; my @parts = split(":", $line); my $length=scalar(@parts); if ($testMode==1) { printf "Message: $line ($parts[0], $length)\n"; } if ($length>=2) { if ($parts[0]=~ m/(status)$/) { &statusResponse($parts[0], $parts[1]); } elsif ($parts[0]=~ m/(list)$/) { &listAllRules($parts[0], $parts[1], $parts[2]); } elsif ($parts[0]=~ m/^(get)/) { &getRulesContents($parts[0], $parts[1], $parts[2]) } elsif ($parts[0]=~ m/^(save)/) { &saveRulesToFile($parts[0], $parts[1], $parts[2], $parts[3]) } elsif ($parts[0]=~ m/^(delete)/) { &deleteRules($parts[0], $parts[1], $parts[2]) } elsif ($parts[0]=~ m/^(setActive)/) { &setActiveRules($parts[0], $parts[1], $parts[2], $parts[3]) } elsif ($parts[0]=~ m/^(control)/) { &control($parts[0], $parts[1], $parts[2], $parts[3]) } else { &sendMessage($parts[0], "11", $parts[1]); } } else { &sendMessage($parts[0], "10", $parts[1]); } } } } # # HTTP interface... # sub sendCommandViaMpd() { my $command = shift; my $param = shift; print "PAram:${param}\n"; $param = &encodeString($param); print "Encoded:${param}\n"; &sendCommand("sendmessage ${readChannel} \"${command}:http:${param}:1\""); sleep(1); } sub buildControlPage() { my $body="Dynamic Playlists

    Dynamic Playlists

    " . "

    Click on a playlist name to load

    "; my @rules=&listAllRules("http", 0, 0); my $active=&determineActiveRules("http"); $body = $body . "

      "; my $num=1; foreach my $rule (@rules) { $rule =~ s/FILENAME://; $rule =~ s/.rules//; $rule =~ s/\n//; if ($rule=~ m/^(TIME:)/) { } else { $body = $body . "
    • "; if ($rule eq $active) { $body = $body . ""; } $body = $body . "
      " . "" .$rule ."
      " . ""; if ($rule eq $active) { $body = $body . "
      "; } $body = $body ."
    • "; $num=$num+1; } } $body = $body . "

    "; $body = $body . "

    " . "

    "; $body = $body . ""; return $body; } sub writeToClient() { my $client=shift; my $message=shift; my $addCrlf=shift; if ($client->connected()) { if (1==$addCrlf) { print $client $message, Socket::CRLF; } else { print $client $message; } } } sub httpServer() { my $server = new IO::Socket::INET(Proto => 'tcp', LocalPort => $httpPort, Listen => SOMAXCONN, Reuse => 1); $server or die "ERROR: Unable to create HTTP socket (${httpPort}): $!"; print "Starting HTTP server on ${httpPort}\n"; while (my $client = $server->accept()) { $client->autoflush(1); my %request = (); my %data; local $/ = Socket::CRLF; while (<$client>) { chomp; # Main http request if (/\s*(\w+)\s*([^\s]+)\s*HTTP\/(\d.\d)/) { $request{METHOD} = uc $1; $request{URL} = $2; $request{HTTP_VERSION} = $3; } # Standard headers elsif (/:/) { (my $type, my $val) = split /:/, $_, 2; $type =~ s/^\s+//; foreach ($type, $val) { s/^\s+//; s/\s+$//; } $request{lc $type} = $val; } # POST data elsif (/^$/) { read($client, $request{CONTENT}, $request{'content-length'}) if defined $request{'content-length'}; last; } } local $response=""; local $responseType="text/plain"; $queryItems{statusTime}=0; if ($request{URL} =~ /(.*)\?(.*)/) { $request{URL} = $1; $request{QUERY} = $2; my @args=split("&", $request{QUERY}); for my $arg (@args) { (my $type, my $val) = split /=/, $arg, 2; $queryItems{$type}=$val; } } # print "${request{METHOD}} URL:${request{URL}} QUERY:${request{QUERY}}\n"; if ($request{METHOD} eq 'GET') { if ($request{URL} eq '/') { $responseType="text/html"; $response = &buildControlPage(); } else { $response="ERROR: Invalid URL"; } } elsif ($request{METHOD} eq 'POST') { if ($request{URL} eq '/setActive') { my @args=split("&", $request{QUERY}); &sendCommandViaMpd("setActive", uri_unescape($queryItems{name})); $response = &buildControlPage(); } elsif ($request{URL} eq '/stop') { &sendCommandViaMpd("control", "stop"); $response = &buildControlPage(); } } else { $responseType="text/html"; $response = &buildControlPage(); } if ($response eq "") { &writeToClient($client, "HTTP/1.0 404 Not Found", 1); &writeToClient($client, Socket::CRLF, 0); &writeToClient($client, "404 Not Found", 0); } elsif ($response =~ m/^ERROR/) { &writeToClient($client, "HTTP/1.0 404 Not Found", 1); &writeToClient($client, Socket::CRLF); &writeToClient($client, "${response}", 0); } elsif ($request{METHOD} eq 'POST') { &writeToClient($client, "HTTP/1.0 201 Created", 1); # Reload start page :-) $response = "" . ""; &writeToClient($client, "Content-type: text/html", 1); &writeToClient($client, Socket::CRLF); &writeToClient($client, $response, 1); } else { &writeToClient($client, "HTTP/1.0 200 OK", 1); &writeToClient($client, "Content-type: ${responseType}", 1); &writeToClient($client, Socket::CRLF); &writeToClient($client, $response); } close $client; } } sub serverMode() { &loadConfig(shift); if ($testMode != 1) { &daemonize($pidFile); } $isServerMode=1; $dynamicIsActive=0; if ($httpPort>0) { threads->create(\&httpServer); } &populatePlayQueue; } sub stopServer() { &loadConfig(shift); my $pid=&readPid($pidFile); if ($pid>0) { system("kill", $pid); system("pkill", "-P", $pid); } } if ($ARGV[0] eq "start") { &start(); } elsif ($ARGV[0] eq "stop") { &stop(); } elsif ($ARGV[0] eq "server") { &serverMode($ARGV[1]); } elsif ($ARGV[0] eq "stopserver") { &stopServer($ARGV[1]); } elsif ($ARGV[0] eq "test") { $testMode=1; &populatePlayQueue(); } elsif ($ARGV[0] eq "testserver") { $testMode=1; &serverMode($ARGV[1]); } else { print "Cantata MPD Dynamizer script\n"; print "\n"; print "Usage: $0 start|stop|server|stopserver\n"; } cantata-2.2.0/playlists/cantata-dynamic.conf000066400000000000000000000006701316350454000210620ustar00rootroot00000000000000# Location of PID file pidFile=/var/run/cantata-dynamic/pid # Location of dynamic rules files filesDir=/var/lib/mpd/dynamic # Name and location of smbolic link to currently active rules activeFile=/var/run/cantata-dynamic/rules # MPD connection details mpdHost=localhost mpdPort=6600 mpdPassword= # Port number on which a simple web page will be server at # allowing simple start/stopping of playlists. # Set to 0 to disable httpPort=0 cantata-2.2.0/playlists/cantata-dynamic.service000077500000000000000000000006221316350454000215750ustar00rootroot00000000000000[Unit] Description=Cantata Dynamic Server Wants=mpd.service After=mpd.service [Service] User=mpd Group=audio Type=forking ExecStart=/usr/share/cantata/scripts/cantata-dynamic server /etc/cantata-dynamic.conf ExecStop=/usr/share/cantata/scripts/cantata-dynamic stopserver /etc/cantata-dynamic.conf RuntimeDirectory=cantata-dynamic PIDFile=/run/cantata-dynamic/pid [Install] WantedBy=multi-user.target cantata-2.2.0/playlists/dynamicplaylists.cpp000066400000000000000000000515351316350454000212610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dynamicplaylists.h" #include "config.h" #include "support/utils.h" #include "mpd-interface/mpdconnection.h" #include "widgets/icons.h" #include "models/roles.h" #include "network/networkaccessmanager.h" #include "gui/settings.h" #include "support/actioncollection.h" #include "support/globalstatic.h" #include #include #include #include #include #include #include #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void DynamicPlaylists::enableDebug() { debugEnabled=true; } static const QString constActiveRules=QLatin1String("rules"); static const QString constLockFile=QLatin1String("lock"); static const QString constPingCmd=QLatin1String("ping"); static const QString constListCmd=QLatin1String("list"); static const QString constStatusCmd=QLatin1String("status"); static const QString constSaveCmd=QLatin1String("save"); static const QString constDeleteCmd=QLatin1String("delete"); static const QString constSetActiveCmd=QLatin1String("setActive"); static const QString constControlCmd=QLatin1String("control"); static QString remoteError(const QStringList &status) { if (!status.isEmpty()) { switch (status.at(0).toInt()) { case 1: return QObject::tr("Empty filename."); case 2: return QObject::tr("Invalid filename. (%1)").arg(status.length()<2 ? QString() : status.at(2)); case 3: return QObject::tr("Failed to save %1.").arg(status.length()<2 ? QString() : status.at(2)); case 4: return QObject::tr("Failed to delete rules file. (%1)").arg(status.length()<2 ? QString() : status.at(2)); case 5: return QObject::tr("Invalid command. (%1)").arg(status.length()<2 ? QString() : status.at(2)); case 6: return QObject::tr("Could not remove active rules link."); case 7: return QObject::tr("Active rules is not a link."); case 8: return QObject::tr("Could not create active rules link."); case 9: return QObject::tr("Rules file, %1, does not exist.").arg(status.length()<2 ? QString() : status.at(2)); case 10: return QObject::tr("Incorrect arguments supplied."); case 11: return QObject::tr("Unknown method called."); } } return QObject::tr("Unknown error"); } DynamicPlaylists::Command DynamicPlaylists::toCommand(const QString &cmd) { if (constListCmd==cmd) { return List; } if (constStatusCmd==cmd) { return Status; } if (constSaveCmd==cmd) { return Save; } if (constDeleteCmd==cmd) { return Del; } if (constSetActiveCmd==cmd) { return SetActive; } if (constControlCmd==cmd) { return Control; } return Unknown; } QString DynamicPlaylists::toString(Command cmd) { switch (cmd) { case Unknown: return QString(); case Ping: return constPingCmd; case List: return constListCmd; case Status: return constStatusCmd; case Save: return constSaveCmd; case Del: return constDeleteCmd; case SetActive: return constSetActiveCmd; case Control: return constControlCmd; } return QString(); } GLOBAL_STATIC(DynamicPlaylists, instance) const QString constOk=QLatin1String("0"); const QString constFilename=QLatin1String("FILENAME:"); DynamicPlaylists::DynamicPlaylists() : RulesPlaylists("dice", "dynamic") , localTimer(0) , usingRemote(false) , remoteTimer(0) , remotePollingEnabled(false) , statusTime(0) , currentJob(0) , currentCommand(Unknown) { connect(this, SIGNAL(clear()), MPDConnection::self(), SLOT(clear())); connect(MPDConnection::self(), SIGNAL(dynamicSupport(bool)), this, SLOT(remoteDynamicSupported(bool))); connect(this, SIGNAL(remoteMessage(QStringList)), MPDConnection::self(), SLOT(sendDynamicMessage(QStringList))); connect(MPDConnection::self(), SIGNAL(dynamicResponse(QStringList)), this, SLOT(remoteResponse(QStringList))); QTimer::singleShot(500, this, SLOT(checkHelper())); startAction = ActionCollection::get()->createAction("startdynamic", tr("Start Dynamic Playlist"), Icons::self()->replacePlayQueueIcon); stopAction = ActionCollection::get()->createAction("stopdynamic", tr("Stop Dynamic Mode"), Icons::self()->stopDynamicIcon); } QString DynamicPlaylists::name() const { return QLatin1String("dynamic"); } QString DynamicPlaylists::title() const { return tr("Dynamic Playlists"); } QString DynamicPlaylists::descr() const { return tr("Dynamically generated playlists"); } #define IS_ACTIVE(E) !currentEntry.isEmpty() && (E)==currentEntry && (!isRemote() || QLatin1String("IDLE")!=lastState) QVariant DynamicPlaylists::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return RulesPlaylists::data(index, role); } if (index.parent().isValid() || index.row()>=entryList.count()) { return QVariant(); } switch (role) { case Qt::DecorationRole: return IS_ACTIVE(entryList.at(index.row()).name) ? Icons::self()->replacePlayQueueIcon : Icons::self()->dynamicListIcon; case Cantata::Role_Actions: { QVariant v; v.setValue >(QList() << (IS_ACTIVE(entryList.at(index.row()).name) ? stopAction : startAction)); return v; } default: return RulesPlaylists::data(index, role); } } bool DynamicPlaylists::saveRemote(const QString &string, const Entry &e) { if (sendCommand(Save, QStringList() << e.name << string)) { currentSave=e; return true; } return false; } void DynamicPlaylists::del(const QString &name) { if (isRemote()) { if (sendCommand(Del, QStringList() << name)) { currentDelete=name; } } else { RulesPlaylists::del(name); } } void DynamicPlaylists::start(const QString &name) { if (isRemote()) { sendCommand(SetActive, QStringList() << name << "1"); return; } if (Utils::findExe("perl").isEmpty()) { emit error(tr("You need to install \"perl\" on your system in order for Cantata's dynamic mode to function.")); return; } QString fName(Utils::dataDir(rulesDir, false)+name+constExtension); if (!QFile::exists(fName)) { emit error(tr("Failed to locate rules file - %1").arg(fName)); return; } QString rules(Utils::cacheDir(rulesDir, true)+constActiveRules); QFile::remove(rules); if (QFile::exists(rules)) { emit error(tr("Failed to remove previous rules file - %1").arg(rules)); return; } if (!QFile::link(fName, rules)) { emit error(tr("Failed to install rules file - %1 -> %2").arg(fName).arg(rules)); return; } int i=currentEntry.isEmpty() ? -1 : entryList.indexOf(currentEntry); QModelIndex idx=index(i, 0, QModelIndex()); currentEntry=name; if (idx.isValid()) { emit dataChanged(idx, idx); } i=entryList.indexOf(currentEntry); idx=index(i, 0, QModelIndex()); if (idx.isValid()) { emit dataChanged(idx, idx); } if (isRunning()) { emit clear(); return; } if (controlApp(true)) { emit running(isRunning()); emit clear(); return; } } void DynamicPlaylists::stop(bool sendClear) { if (isRemote()) { if (sendClear) { sendCommand(Control, QStringList() << "stop" << "1"); } else { sendCommand(Control, QStringList() << "stop"); } return; } #if !defined Q_OS_WIN int i=currentEntry.isEmpty() ? -1 : entryList.indexOf(currentEntry); QModelIndex idx=index(i, 0, QModelIndex()); int pid=getPid(); if (!pid) { if (sendClear) { emit clear(); } currentEntry=QString(); emit running(false); if (idx.isValid()) { emit dataChanged(idx, idx); } return; } if (0!=::kill(pid, 0)) { if (sendClear) { emit clear(); } currentEntry=QString(); emit running(false); if (idx.isValid()) { emit dataChanged(idx, idx); } return; } if (controlApp(false)) { if (sendClear) { emit clear(); } currentEntry=QString(); emit running(isRunning()); if (idx.isValid()) { emit dataChanged(idx, idx); } return; } #endif } void DynamicPlaylists::toggle(const QString &name) { if(name==currentEntry) { stop(); } else { start(name); } } bool DynamicPlaylists::isRunning() { #if defined Q_OS_WIN return false; #else int pid=getPid(); return pid ? 0==::kill(pid, 0) : false; #endif } void DynamicPlaylists::enableRemotePolling(bool e) { remotePollingEnabled=e; if (remoteTimer && remoteTimer->isActive()) { if (remotePollingEnabled) { checkIfRemoteIsRunning(); } remoteTimer->start(remotePollingEnabled ? 5000 : 30000); } } int DynamicPlaylists::getPid() const { QFile pidFile(Utils::cacheDir(rulesDir, false)+constLockFile); if (pidFile.open(QIODevice::ReadOnly|QIODevice::Text)) { QTextStream str(&pidFile); int pid=0; str >> pid; return pid; } return 0; } bool DynamicPlaylists::controlApp(bool isStart) { QString cmd=CANTATA_SYS_SCRIPTS_DIR+QLatin1String("cantata-dynamic"); QProcess process; if (isStart) { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); MPDConnectionDetails details=MPDConnection::self()->getDetails(); env.insert("MPD_HOST", details.password.isEmpty() ? details.hostname : (details.password+'@'+details.hostname)); env.insert("MPD_PORT", QString::number(details.port)); process.setProcessEnvironment(env); } process.start(cmd, QStringList() << QLatin1String(isStart ? "start" : "stop"), QIODevice::WriteOnly); if (!localTimer) { localTimer=new QTimer(this); connect(localTimer, SIGNAL(timeout()), SLOT(checkHelper())); } bool rv=process.waitForFinished(1000); localTimer->start(1000); return rv; } void DynamicPlaylists::parseRemote(const QStringList &response) { DBUG << response; beginResetModel(); entryList.clear(); currentEntry=QString(); QStringList keys=QStringList() << constArtistKey << constSimilarArtistsKey << constAlbumArtistKey << constDateKey << constExactKey << constAlbumKey << constTitleKey << constGenreKey << constFileKey << constExcludeKey; Entry e; Rule r; foreach (const QString &part, response) { QStringList lines=part.split('\n', QString::SkipEmptyParts); foreach (const QString &s, lines) { QString str=s.trimmed(); if (str.isEmpty() || str.startsWith('#')) { continue; } if (str.startsWith(constFilename)) { if (!e.name.isEmpty()) { if (!r.isEmpty()) { e.rules.append(r); } entryList.append(e); } e.name=str.mid(9, str.length()-15); // Remove extension... e.ratingFrom=e.ratingTo=0; e.rules.clear(); r.clear(); } else if (str==constRuleKey) { if (!r.isEmpty()) { e.rules.append(r); r.clear(); } } else if (str.startsWith(constRatingKey+constKeyValSep)) { QStringList vals=str.mid(constRatingKey.length()+1).split(constRangeSep); if (2==vals.count()) { e.ratingFrom=vals.at(0).toUInt(); e.ratingTo=vals.at(1).toUInt(); } } else if (str.startsWith(constDurationKey+constKeyValSep)) { QStringList vals=str.mid(constDurationKey.length()+1).split(constRangeSep); if (2==vals.count()) { e.minDuration=vals.at(0).toUInt(); e.maxDuration=vals.at(1).toUInt(); } } else { foreach (const QString &k, keys) { if (str.startsWith(k+constKeyValSep)) { r.insert(k, str.mid(k.length()+1)); } } } } } if (!e.name.isEmpty()) { if (!r.isEmpty()) { e.rules.append(r); } entryList.append(e); } endResetModel(); } void DynamicPlaylists::parseStatus(QStringList response) { DBUG << response; if (response.isEmpty()) { return; } QString state=response.takeAt(0); QString prevEntry=currentEntry; int st=statusTime; if (!response.isEmpty()) { st=response.takeAt(0).toInt(); } if (!response.isEmpty()) { currentEntry=response.takeAt(0); } bool stateChanged=lastState!=state; bool noSongs=QLatin1String("NO_SONGS")==state; bool terminated=false; DBUG << "lastState" << lastState << "state" << state << "statusTime(before)" << statusTime << "statusTime(now)" << st; if (stateChanged) { lastState=state; if (noSongs) { bool sendError=!currentEntry.isEmpty(); emit running(true); if (sendError) { emit error(QLatin1String("NO_SONGS")); } //sendCommand(Control, QStringList() << "stop"); } else if (QLatin1String("HAVE_SONGS")==state || QLatin1String("STARTING")==state) { emit running(true); } else if (QLatin1String("IDLE")==state) { currentEntry.clear(); emit running(false); } else if (QLatin1String("TERMINATED")==state) { terminated=true; currentEntry.clear(); emit running(false); emit error(tr("Dynamizer has been terminated.")); pollRemoteHelper(); currentEntry=QString(); } } if (prevEntry!=currentEntry) { int prev=prevEntry.isEmpty() ? -1 : entryList.indexOf(prevEntry); int cur=currentEntry.isEmpty() ? -1 : entryList.indexOf(currentEntry); if (-1!=prev) { QModelIndex idx=index(prev, 0, QModelIndex()); emit dataChanged(idx, idx); } if (-1!=cur) { QModelIndex idx=index(cur, 0, QModelIndex()); emit dataChanged(idx, idx); } } else if (stateChanged) { int row=currentEntry.isEmpty() ? -1 : entryList.indexOf(currentEntry); if (-1!=row) { QModelIndex idx=index(row, 0, QModelIndex()); emit dataChanged(idx, idx); } } if (st>statusTime && !terminated) { statusTime=st; sendCommand(List, QStringList() << "1"); } } bool DynamicPlaylists::sendCommand(Command cmd, const QStringList &args) { DBUG << toString(cmd) << args; if (Ping==cmd) { emit remoteMessage(QStringList() << toString(cmd)); return true; } if (Status==currentCommand) { if (cmd==Status) { return true; } currentCommand=Unknown; } if (Status!=cmd && (Save==currentCommand || Del==currentCommand)) { emit error(tr("Awaiting response for previous command. (%1)").arg(Save==cmd ? tr("Saving rule") : tr("Deleting rule"))); return false; } currentCommand=cmd; emit remoteMessage(QStringList() << toString(cmd) << args); if (List==cmd) { emit loadingList(); } return true; } void DynamicPlaylists::checkHelper() { if (isRemote()) { return; } if (!isRunning()) { emit running(false); int i=currentEntry.isEmpty() ? -1 : entryList.indexOf(currentEntry); currentEntry=QString(); if (i>-1) { QModelIndex idx=index(i, 0, QModelIndex()); emit dataChanged(idx, idx); } if (localTimer) { localTimer->stop(); } } else { if (localTimer && localTimer->isActive()) { static const int constAppCheck=15*1000; if (localTimer->interval()start(constAppCheck); } } else { // No timer => app startup! // Attempt to read current name... QFileInfo inf(Utils::cacheDir(rulesDir, false)+constActiveRules); if (inf.exists() && inf.isSymLink()) { QString link=inf.readLink(); if (!link.isEmpty()) { QString fname=QFileInfo(link).fileName(); if (fname.endsWith(constExtension)) { currentEntry=fname.left(fname.length()-constExtension.length()); } } } emit running(true); } } } void DynamicPlaylists::pollRemoteHelper() { if (!remoteTimer) { remoteTimer=new QTimer(this); connect(remoteTimer, SIGNAL(timeout()), SLOT(checkIfRemoteIsRunning())); } beginResetModel(); entryList.clear(); currentEntry=QString(); endResetModel(); remoteTimer->start(remotePollingEnabled ? 5000 : 30000); } void DynamicPlaylists::checkIfRemoteIsRunning() { if (isRemote()) { if (remotePollingEnabled) { sendCommand(Ping); } } else if (remoteTimer) { remoteTimer->stop(); } } void DynamicPlaylists::updateRemoteStatus() { if (isRemote()) { sendCommand(Status); } } void DynamicPlaylists::remoteResponse(QStringList msg) { DBUG << msg; if (msg.isEmpty()) { return; } QString cmd=msg.takeAt(0); Command msgCmd=toCommand(cmd); switch (msgCmd) { case List: parseRemote(msg); emit loadedList(); break; case Status: parseStatus(msg); break; case Save: if (!msg.isEmpty() && msg.at(0)==constOk) { updateEntry(currentSave); emit saved(true); } else { emit error(tr("Failed to save %1. (%2)").arg(currentSave.name).arg(remoteError(msg))); emit saved(false); } currentSave.name=QString(); currentSave.rules.clear(); break; case Del: if (!msg.isEmpty() && msg.at(0)==constOk) { QList::Iterator it=find(currentDelete); if (it!=entryList.end()) { beginRemoveRows(QModelIndex(), it-entryList.begin(), it-entryList.begin()); entryList.erase(it); endRemoveRows(); } else { emit error(tr("Failed to delete rules file. (%1)").arg(remoteError(msg))); } } else { emit error(tr("Failed to delete rules file. (%1)").arg(remoteError(msg))); emit saved(false); } currentDelete.clear(); break; case Control: if (msg.isEmpty() || msg.at(0)!=constOk) { emit error(tr("Failed to control dynamizer state. (%1)").arg(remoteError(msg))); } break; case SetActive: if (!msg.isEmpty() && msg.at(0)==constOk) { QTimer::singleShot(1000, this, SLOT(updateRemoteStatus())); } else { emit error(tr("Failed to set the current dynamic rules. (%1)").arg(remoteError(msg))); } break; default: break; } if (msgCmd==currentCommand) { currentCommand=Unknown; } } void DynamicPlaylists::remoteDynamicSupported(bool s) { if (usingRemote==s) { return; } // Stop any local dynamizer before switching to remote... if (!isRemote()) { stop(); } usingRemote=s; if (s) { if (localTimer) { localTimer->stop(); } pollRemoteHelper(); sendCommand(List); sendCommand(Status); } else { if (remoteTimer) { remoteTimer->stop(); } loadLocal(); // #ifndef Q_OS_WIN // emit error(tr("Communication with the remote dynamizer has been lost, reverting to local mode.")); // #endif } } cantata-2.2.0/playlists/dynamicplaylists.h000066400000000000000000000067201316350454000207220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DYNAMIC_PLAYLISTS_H #define DYNAMIC_PLAYLISTS_H #include #include #include #include #include #include "rulesplaylists.h" #include "models/actionmodel.h" #include "support/icon.h" class QTimer; class NetworkJob; class DynamicPlaylists : public RulesPlaylists { Q_OBJECT public: enum Command { Unknown, Ping, List, Status, Save, Del, SetActive, Control }; static Command toCommand(const QString &cmd); static QString toString(Command cmd); static void enableDebug(); static DynamicPlaylists * self(); DynamicPlaylists(); virtual ~DynamicPlaylists() { } QString name() const; QString title() const; QString descr() const; bool isDynamic() const { return true; } QVariant data(const QModelIndex &index, int role) const; const Icon & icon() const { return icn; } bool isRemote() const { return usingRemote; } bool saveRemote(const QString &string, const Entry &e); void del(const QString &name); void start(const QString &name); void stop(bool sendClear=false); void toggle(const QString &name); bool isRunning(); void helperMessage(const QString &message) { Q_UNUSED(message) checkHelper(); } Action * startAct() const { return startAction; } Action * stopAct() const { return stopAction; } void enableRemotePolling(bool e); Q_SIGNALS: void running(bool status); void error(const QString &str); // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void clear(); void remoteMessage(const QStringList &args); // These are as the result of asynchronous HTTP calls void saved(bool s); void loadingList(); void loadedList(); private Q_SLOTS: void checkHelper(); void checkIfRemoteIsRunning(); void updateRemoteStatus(); void remoteResponse(QStringList msg); void remoteDynamicSupported(bool s); void parseStatus(QStringList response); private: void pollRemoteHelper(); int getPid() const; bool controlApp(bool isStart); bool sendCommand(Command cmd, const QStringList &args=QStringList()); void parseRemote(const QStringList &response); private: QTimer *localTimer; Action *startAction; Action *stopAction; // For remote dynamic servers... bool usingRemote; QTimer *remoteTimer; bool remotePollingEnabled; int statusTime; QString lastState; QString dynamicUrl; NetworkJob *currentJob; Command currentCommand; QString currentDelete; Entry currentSave; }; #endif cantata-2.2.0/playlists/dynamicplaylistspage.cpp000066400000000000000000000166201316350454000221120ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dynamicplaylistspage.h" #include "dynamicplaylists.h" #include "playlistrulesdialog.h" #include "widgets/icons.h" #include "support/action.h" #include "support/configuration.h" #include "mpd-interface/mpdconnection.h" #include "support/messagebox.h" #include "gui/stdactions.h" DynamicPlaylistsPage::DynamicPlaylistsPage(QWidget *p) : SinglePageWidget(p) { addAction = new Action(Icons::self()->addNewItemIcon, tr("Add"), this); editAction = new Action(Icons::self()->editIcon, tr("Edit"), this); removeAction = new Action(Icons::self()->removeIcon, tr("Remove"), this); toggleAction = new Action(this); ToolButton *addBtn=new ToolButton(this); ToolButton *editBtn=new ToolButton(this); ToolButton *removeBtn=new ToolButton(this); ToolButton *startBtn=new ToolButton(this); addBtn->setDefaultAction(addAction); editBtn->setDefaultAction(editAction); removeBtn->setDefaultAction(removeAction); startBtn->setDefaultAction(DynamicPlaylists::self()->startAct()); view->addAction(editAction); view->addAction(removeAction); view->addAction(DynamicPlaylists::self()->startAct()); view->alwaysShowHeader(); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(toggle())); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(MPDConnection::self(), SIGNAL(dynamicSupport(bool)), this, SLOT(remoteDynamicSupport(bool))); connect(addAction, SIGNAL(triggered()), SLOT(add())); connect(editAction, SIGNAL(triggered()), SLOT(edit())); connect(removeAction, SIGNAL(triggered()), SLOT(remove())); connect(DynamicPlaylists::self()->startAct(), SIGNAL(triggered()), SLOT(start())); connect(DynamicPlaylists::self()->stopAct(), SIGNAL(triggered()), SLOT(stop())); connect(toggleAction, SIGNAL(triggered()), SLOT(toggle())); connect(DynamicPlaylists::self(), SIGNAL(running(bool)), SLOT(running(bool))); connect(DynamicPlaylists::self(), SIGNAL(loadingList()), view, SLOT(showSpinner())); connect(DynamicPlaylists::self(), SIGNAL(loadedList()), view, SLOT(hideSpinner())); #ifdef Q_OS_WIN remoteRunningLabel=new QLabel(this); remoteRunningLabel->setStyleSheet(QString(".QLabel {" "background-color: rgba(235, 187, 187, 196);" "border-radius: 3px;" "border: 1px solid red;" "padding: 4px;" "margin: 1px;" "color: black; }")); remoteRunningLabel->setText(tr("Remote dynamizer is not running.")); #endif DynamicPlaylists::self()->stopAct()->setEnabled(false); proxy.setSourceModel(DynamicPlaylists::self()); view->setModel(&proxy); view->setDeleteAction(removeAction); view->setMode(ItemView::Mode_List); controlActions(); Configuration config(metaObject()->className()); view->load(config); controls=QList() << addBtn << editBtn << removeBtn << startBtn; init(0, QList(), controls); #ifdef Q_OS_WIN addWidget(remoteRunningLabel); enableWidgets(false); #endif } DynamicPlaylistsPage::~DynamicPlaylistsPage() { Configuration config(metaObject()->className()); view->save(config); } void DynamicPlaylistsPage::doSearch() { QString text=view->searchText().trimmed(); proxy.update(text); if (proxy.enabled() && !proxy.filterText().isEmpty()) { view->expandAll(); } } void DynamicPlaylistsPage::controlActions() { QModelIndexList selected=qobject_cast(sender()) ? QModelIndexList() : view->selectedIndexes(false); // Dont need sorted selection here... editAction->setEnabled(1==selected.count()); DynamicPlaylists::self()->startAct()->setEnabled(1==selected.count()); removeAction->setEnabled(selected.count()); } void DynamicPlaylistsPage::remoteDynamicSupport(bool s) { #ifdef Q_OS_WIN remoteRunningLabel->setVisible(!s); enableWidgets(s); #endif view->setBackgroundImage(s ? Icon(QStringList() << "network-server-database.svg" << "applications-internet") : Icon()); } void DynamicPlaylistsPage::add() { PlaylistRulesDialog *dlg=new PlaylistRulesDialog(this, DynamicPlaylists::self()); dlg->edit(QString()); } void DynamicPlaylistsPage::edit() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } PlaylistRulesDialog *dlg=new PlaylistRulesDialog(this, DynamicPlaylists::self()); dlg->edit(selected.at(0).data(Qt::DisplayRole).toString()); } void DynamicPlaylistsPage::remove() { QModelIndexList selected=view->selectedIndexes(); if (selected.isEmpty() || MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the selected rules?\n\nThis cannot be undone."), tr("Remove Dynamic Rules"), StdGuiItem::remove(), StdGuiItem::cancel())) { return; } QStringList names; foreach (const QModelIndex &idx, selected) { names.append(idx.data(Qt::DisplayRole).toString()); } foreach (const QString &name, names) { DynamicPlaylists::self()->del(name); } } void DynamicPlaylistsPage::start() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } DynamicPlaylists::self()->start(selected.at(0).data(Qt::DisplayRole).toString()); } void DynamicPlaylistsPage::stop() { DynamicPlaylists::self()->stop(); } void DynamicPlaylistsPage::toggle() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } DynamicPlaylists::self()->toggle(selected.at(0).data(Qt::DisplayRole).toString()); } void DynamicPlaylistsPage::running(bool status) { DynamicPlaylists::self()->stopAct()->setEnabled(status); } void DynamicPlaylistsPage::headerClicked(int level) { if (0==level) { emit close(); } } void DynamicPlaylistsPage::enableWidgets(bool enable) { foreach (QWidget *c, controls) { c->setEnabled(enable); } view->setEnabled(enable); } void DynamicPlaylistsPage::showEvent(QShowEvent *e) { view->focusView(); DynamicPlaylists::self()->enableRemotePolling(true); SinglePageWidget::showEvent(e); } void DynamicPlaylistsPage::hideEvent(QHideEvent *e) { DynamicPlaylists::self()->enableRemotePolling(false); SinglePageWidget::hideEvent(e); } cantata-2.2.0/playlists/dynamicplaylistspage.h000066400000000000000000000034421316350454000215550ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DYNAMIC_RULES_PAGE_H #define DYNAMIC_RULES_PAGE_H #include "widgets/singlepagewidget.h" #include "playlistproxymodel.h" class Action; class QLabel; class DynamicPlaylistsPage : public SinglePageWidget { Q_OBJECT public: DynamicPlaylistsPage(QWidget *p); virtual ~DynamicPlaylistsPage(); void setView(int) { } private Q_SLOTS: void remoteDynamicSupport(bool s); void add(); void edit(); void remove(); void start(); void stop(); void toggle(); void running(bool status); void headerClicked(int level); private: void doSearch(); void controlActions(); void enableWidgets(bool enable); void showEvent(QShowEvent *e); void hideEvent(QHideEvent *e); private: PlaylistProxyModel proxy; Action *addAction; Action *editAction; Action *removeAction; Action *toggleAction; QList controls; #ifdef Q_OS_WIN QLabel *remoteRunningLabel; #endif }; #endif cantata-2.2.0/playlists/playlistproxymodel.cpp000066400000000000000000000041031316350454000216410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistproxymodel.h" #include "dynamicplaylists.h" PlaylistProxyModel::PlaylistProxyModel(QObject *parent) : ProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); sort(0); } bool PlaylistProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { return true; } if (!isChildOfRoot(sourceParent)) { return true; } if (matchesFilter(QStringList() << sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent), Qt::DisplayRole).toString())) { return true; } RulesPlaylists *rules = qobject_cast(sourceModel()); if (rules) { RulesPlaylists::Entry item = rules->entry(sourceRow); foreach (const RulesPlaylists::Rule & r, item.rules) { RulesPlaylists::Rule::ConstIterator it=r.constBegin(); RulesPlaylists::Rule::ConstIterator end=r.constEnd(); for (; it!=end; ++it) { if (matchesFilter(QStringList() << it.value())) { return true; } } } } return false; } cantata-2.2.0/playlists/playlistproxymodel.h000066400000000000000000000021621316350454000213110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYLIST_PROXY_MODEL_H #define PLAYLIST_PROXY_MODEL_H #include "models/proxymodel.h" class PlaylistProxyModel : public ProxyModel { public: PlaylistProxyModel(QObject *parent = 0); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; }; #endif cantata-2.2.0/playlists/playlistrule.ui000066400000000000000000000201041316350454000202400ustar00rootroot00000000000000 PlaylistRule 0 0 0 0 QFormLayout::ExpandingFieldsGrow Type: typeCombo Include songs that match the following: Exclude songs that match the following: Artist: artistText Artists similar to: similarArtistsText Album Artist: albumArtistText Composer: composerText Album: albumText Title: titleText Genre genreText From Year: dateFromSpin 72 0 Any To Year: dateToSpin 72 0 Any Comment: commentText Filename / path: filenameText Exact match Qt::Vertical QSizePolicy::Fixed 20 4 Only enter values for the tags you wish to be search on. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Qt::Vertical QSizePolicy::MinimumExpanding 20 0 BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    LineEdit QLineEdit
    support/lineedit.h
    CompletionCombo QComboBox
    widgets/completioncombo.h
    cantata-2.2.0/playlists/playlistruledialog.cpp000066400000000000000000000226641316350454000216020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistruledialog.h" #include "support/monoicon.h" #include "models/mpdlibrarymodel.h" static const int constMinDate=1800; static const int constMaxDate=2100; #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; PlaylistRuleDialog::PlaylistRuleDialog(QWidget *parent, bool isDynamic) : Dialog(parent) , addingRules(false) { QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); setButtons(Ok|Cancel); enableButton(Ok, false); setCaption(isDynamic ? tr("Dynamic Rule") : tr("Smart Rule")); connect(artistText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(composerText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(commentText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); if (isDynamic) { connect(similarArtistsText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); } else { REMOVE(similarArtistsText) REMOVE(similarArtistsTextLabel) } connect(albumArtistText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(albumText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(titleText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(genreText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(filenameText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(dateFromSpin, SIGNAL(valueChanged(int)), SLOT(enableOkButton())); connect(dateToSpin, SIGNAL(valueChanged(int)), SLOT(enableOkButton())); connect(exactCheck, SIGNAL(toggled(bool)), SLOT(enableOkButton())); QSet artists; QSet albumArtists; QSet composers; QSet albums; QSet genres; MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres); QStringList strings=artists.toList(); strings.sort(); artistText->clear(); artistText->insertItems(0, strings); if (similarArtistsText) { similarArtistsText->clear(); similarArtistsText->insertItems(0, strings); } strings=albumArtists.toList(); strings.sort(); albumArtistText->clear(); albumArtistText->insertItems(0, strings); strings=composers.toList(); strings.sort(); composerText->clear(); composerText->insertItems(0, strings); strings=albums.toList(); strings.sort(); albumText->clear(); albumText->insertItems(0, strings); strings=genres.toList(); strings.sort(); genreText->clear(); genreText->insertItems(0, strings); commentText->clear(); dateFromSpin->setRange(constMinDate-1, constMaxDate); dateToSpin->setRange(constMinDate-1, constMaxDate); artistText->setFocus(); errorLabel->setVisible(false); errorLabel->setStyleSheet(QLatin1String("QLabel{color:red;}")); adjustSize(); int h=height(); int w=width(); int minW=Utils::scaleForDpi(500); setMinimumWidth(minW); setMinimumHeight(h); if (wsetCurrentIndex(QLatin1String("true")==rule[RulesPlaylists::constExcludeKey] ? 1 : 0); artistText->setText(rule[RulesPlaylists::constArtistKey]); if (similarArtistsText) { similarArtistsText->setText(rule[RulesPlaylists::constSimilarArtistsKey]); } albumArtistText->setText(rule[RulesPlaylists::constAlbumArtistKey]); composerText->setText(rule[RulesPlaylists::constComposerKey]); commentText->setText(rule[RulesPlaylists::constCommentKey]); albumText->setText(rule[RulesPlaylists::constAlbumKey]); titleText->setText(rule[RulesPlaylists::constTitleKey]); genreText->setText(rule[RulesPlaylists::constGenreKey]); filenameText->setText(rule[RulesPlaylists::constFileKey]); QString date=rule[RulesPlaylists::constDateKey]; int dateFrom=0; int dateTo=0; if (!date.isEmpty()) { int idx=date.indexOf(RulesPlaylists::constRangeSep); if (-1==idx) { dateFrom=date.toInt(); } else { dateFrom=date.left(idx).toInt(); dateTo=date.mid(idx+1).toInt(); } } if (dateFromconstMaxDate) { dateFrom=constMinDate-1; } if (dateToconstMaxDate) { dateTo=constMinDate-1; } dateFromSpin->setValue(dateFrom); dateToSpin->setValue(dateTo); exactCheck->setChecked(QLatin1String("false")!=rule[RulesPlaylists::constExactKey]); errorLabel->setVisible(false); setButtons(isAdd ? User1|Ok|Close : Ok|Cancel); setButtonText(User1, tr("Add")); setButtonGuiItem(User1, GuiItem(tr("Add"), FontAwesome::plus)); enableOkButton(); return QDialog::Accepted==exec(); } RulesPlaylists::Rule PlaylistRuleDialog::rule() const { RulesPlaylists::Rule r; if (!artist().isEmpty()) { r.insert(RulesPlaylists::constArtistKey, artist()); } if (!similarArtists().isEmpty()) { r.insert(RulesPlaylists::constSimilarArtistsKey, similarArtists()); } if (!albumArtist().isEmpty()) { r.insert(RulesPlaylists::constAlbumArtistKey, albumArtist()); } if (!composer().isEmpty()) { r.insert(RulesPlaylists::constComposerKey, composer()); } if (!comment().isEmpty()) { r.insert(RulesPlaylists::constCommentKey, comment()); } if (!album().isEmpty()) { r.insert(RulesPlaylists::constAlbumKey, album()); } if (!title().isEmpty()) { r.insert(RulesPlaylists::constTitleKey, title()); } if (!genre().isEmpty()) { r.insert(RulesPlaylists::constGenreKey, genre()); } if (!filename().isEmpty()) { r.insert(RulesPlaylists::constFileKey, filename()); } int dateFrom=dateFromSpin->value(); int dateTo=dateToSpin->value(); bool haveFrom=dateFrom>=constMinDate && dateFrom<=constMaxDate; bool haveTo=dateTo>=constMinDate && dateTo<=constMaxDate && dateTo!=dateFrom; if (haveFrom && haveTo) { r.insert(RulesPlaylists::constDateKey, QString::number(dateFrom)+RulesPlaylists::constRangeSep+QString::number(dateTo)); } else if (haveFrom) { r.insert(RulesPlaylists::constDateKey, QString::number(dateFrom)); } else if (haveTo) { r.insert(RulesPlaylists::constDateKey, QString::number(dateTo)); } if (!exactCheck->isChecked()) { r.insert(RulesPlaylists::constExactKey, QLatin1String("false")); } if (1==typeCombo->currentIndex()) { r.insert(RulesPlaylists::constExcludeKey, QLatin1String("true")); } return r; } void PlaylistRuleDialog::enableOkButton() { static const int constMaxDateRange=20; int dateFrom=dateFromSpin->value(); int dateTo=dateToSpin->value(); bool haveFrom=dateFrom>=constMinDate && dateFrom<=constMaxDate; bool haveTo=dateTo>=constMinDate && dateTo<=constMaxDate && dateTo!=dateFrom; bool enable=(!haveFrom || !haveTo || (dateTo>=dateFrom && (dateTo-dateFrom)<=constMaxDateRange)) && (haveFrom || haveTo || !artist().isEmpty() || !similarArtists().isEmpty() || !albumArtist().isEmpty() || !composer().isEmpty() || !comment().isEmpty() || !album().isEmpty() || !title().isEmpty() || !genre().isEmpty() || !filename().isEmpty()); if (enable && exactCheck->isChecked() && !filename().isEmpty()) { enable=false; } errorLabel->setVisible(false); if (!enable) { if (haveFrom && haveTo) { if (dateTosetText(tr("ERROR: 'From Year' should be less than 'To Year'")); errorLabel->setVisible(true); } else if (dateTo-dateFrom>constMaxDateRange) { errorLabel->setText(tr("ERROR: Date range is too large (can only be a maximum of %1 years)").arg(constMaxDateRange)); errorLabel->setVisible(true); } } if (!filename().isEmpty() && exactCheck->isChecked() && !errorLabel->isVisible()) { errorLabel->setText(tr("ERROR: You can only match on filename / path if 'Exact match' is not checked")); errorLabel->setVisible(true); } } enableButton(Ok, enable); if (addingRules) { enableButton(User1, enable); } } void PlaylistRuleDialog::slotButtonClicked(int button) { if (addingRules && (User1==button || Ok==button)) { emit addRule(rule()); } Dialog::slotButtonClicked(button); } cantata-2.2.0/playlists/playlistruledialog.h000066400000000000000000000043001316350454000212320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYLIST_RULE_DIALOG_H #define PLAYLIST_RULE_DIALOG_H #include "config.h" #include "support/dialog.h" #include "ui_playlistrule.h" #include "dynamicplaylists.h" class PlaylistRuleDialog : public Dialog, Ui::PlaylistRule { Q_OBJECT public: PlaylistRuleDialog(QWidget *parent, bool isDynamic); virtual ~PlaylistRuleDialog(); void createNew() { edit(RulesPlaylists::Rule(), true); } bool edit(const RulesPlaylists::Rule &rule, bool isAdd=false); RulesPlaylists::Rule rule() const; QString artist() const { return artistText->text().trimmed(); } QString similarArtists() const { return similarArtistsText ? similarArtistsText->text().trimmed() : QString(); } QString albumArtist() const { return albumArtistText->text().trimmed(); } QString composer() const { return composerText->text().trimmed(); } QString comment() const { return commentText->text().trimmed(); } QString album() const { return albumText->text().trimmed(); } QString title() const { return titleText->text().trimmed(); } QString genre() const { return genreText->text().trimmed(); } QString filename() const { return filenameText->text().trimmed(); } Q_SIGNALS: void addRule(const RulesPlaylists::Rule &r); private Q_SLOTS: void enableOkButton(); private: void slotButtonClicked(int button); private: bool addingRules; }; #endif cantata-2.2.0/playlists/playlistrules.ui000066400000000000000000000203561316350454000204340ustar00rootroot00000000000000 PlaylistRules 0 0 689 495 0 0 0 0 QFrame::StyledPanel QFrame::Raised Name of Dynamic Rules 0 0 0 0 1 0 Add Edit Remove Qt::Vertical 20 53 Songs with ratings between: - Qt::Horizontal 2 2 Songs with duration between: seconds 1800 10 - seconds 1800 10 Qt::Horizontal 2 2 Number of songs in play queue: 10 Order songs: 0 0 About Rules Qt::Horizontal 220 20 UrlLabel QLabel
    support/urllabel.h
    LineEdit QWidget
    support/lineedit.h
    ListView QListView
    widgets/listview.h
    MessageWidget QFrame
    support/messagewidget.h
    1
    RatingWidget QWidget
    widgets/ratingwidget.h
    cantata-2.2.0/playlists/playlistrulesdialog.cpp000066400000000000000000000403371316350454000217620ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistrulesdialog.h" #include "playlistruledialog.h" #include "support/messagebox.h" #include "widgets/basicitemdelegate.h" #include #include #include #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; class RulesSort : public QSortFilterProxyModel { public: RulesSort(QObject *parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); setSortLocaleAware(true); sort(0); } virtual ~RulesSort() { } bool lessThan(const QModelIndex &left, const QModelIndex &right) const { bool lInc=left.data(Qt::UserRole+2).toBool(); bool rInc=right.data(Qt::UserRole+2).toBool(); if (lInc==rInc) { return left.data(Qt::DisplayRole).toString().localeAwareCompare(right.data(Qt::DisplayRole).toString())<0; } else { return lInc ? true : false; } } }; static QString translateStr(const QString &key) { if (RulesPlaylists::constArtistKey==key) { return QObject::tr("Artist"); } else if (RulesPlaylists::constSimilarArtistsKey==key) { return QObject::tr("SimilarArtists"); } else if (RulesPlaylists::constAlbumArtistKey==key) { return QObject::tr("AlbumArtist"); } else if (RulesPlaylists::constComposerKey==key) { return QObject::tr("Composer"); } else if (RulesPlaylists::constCommentKey==key) { return QObject::tr("Comment"); } else if (RulesPlaylists::constAlbumKey==key) { return QObject::tr("Album"); } else if (RulesPlaylists::constTitleKey==key) { return QObject::tr("Title"); } else if (RulesPlaylists::constGenreKey==key) { return QObject::tr("Genre"); } else if (RulesPlaylists::constDateKey==key) { return QObject::tr("Date"); } else if (RulesPlaylists::constFileKey==key) { return QObject::tr("File"); } else { return key; } } static void update(QStandardItem *i, const RulesPlaylists::Rule &rule) { RulesPlaylists::Rule::ConstIterator it(rule.constBegin()); RulesPlaylists::Rule::ConstIterator end(rule.constEnd()); QMap v; QString str; QString type=QObject::tr("Include"); bool exact=true; bool include=true; for (int count=0; it!=end; ++it, ++count) { if (RulesPlaylists::constExcludeKey==it.key()) { if (QLatin1String("true")==it.value()) { type=QObject::tr("Exclude"); include=false; } } else if (RulesPlaylists::constExactKey==it.key()) { if (QLatin1String("false")==it.value()) { exact=false; } } else { str+=QString("%1=%2").arg(translateStr(it.key()), it.value()); if (countsetText(str); i->setData(v); i->setData(include, Qt::UserRole+2); i->setFlags(Qt::ItemIsSelectable| Qt::ItemIsEnabled); } PlaylistRulesDialog::PlaylistRulesDialog(QWidget *parent, RulesPlaylists *m) : Dialog(parent, "PlaylistRulesDialog") , rules(m) , dlg(0) { QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); setButtons(Ok|Cancel); enableButton(Ok, false); setCaption(rules->isDynamic() ? tr("Dynamic Rules") : tr("Smart Rules")); setAttribute(Qt::WA_DeleteOnClose); connect(addBtn, SIGNAL(clicked()), SLOT(add())); connect(editBtn, SIGNAL(clicked()), SLOT(edit())); connect(removeBtn, SIGNAL(clicked()), SLOT(remove())); connect(rulesList, SIGNAL(itemsSelected(bool)), SLOT(controlButtons())); connect(nameText, SIGNAL(textChanged(const QString &)), SLOT(enableOkButton())); connect(aboutLabel, SIGNAL(leftClickedUrl()), this, SLOT(showAbout())); if (rules->isDynamic()) { connect(rules, SIGNAL(saved(bool)), SLOT(saved(bool))); } messageWidget->setVisible(false); model=new QStandardItemModel(this); proxy=new RulesSort(this); proxy->setSourceModel(model); rulesList->setModel(proxy); rulesList->setItemDelegate(new BasicItemDelegate(rulesList)); rulesList->setAlternatingRowColors(false); minDuration->setSpecialValueText(tr("No Limit")); maxDuration->setSpecialValueText(tr("No Limit")); numTracks->setRange(rules->minTracks(), rules->maxTracks()); if (rules->isDynamic()) { REMOVE(orderLabel) REMOVE(order) REMOVE(orderAscending) orderLayout->deleteLater(); numTracks->setValue(qMax(qMin(10, rules->maxTracks()), rules->minTracks())); } else { numTracks->setValue(qMax(qMin(100, rules->maxTracks()), rules->minTracks())); for (int i=0; iaddItem(RulesPlaylists::orderName((RulesPlaylists::Order)i), i); } order->setCurrentIndex(RulesPlaylists::Order_Random); orderAscending->addItem(tr("Ascending")); orderAscending->addItem(tr("Descending")); orderAscending->setEnabled(false); connect(order, SIGNAL(currentIndexChanged(int)), this, SLOT(setOrder())); } controlButtons(); resize(500, 240); if (!rules->isDynamic()) { nameText->setPlaceholderText(tr("Name of Smart Rules")); numberOfSongsLabel->setText(tr("Number of songs")); } static bool registered=false; if (!registered) { qRegisterMetaType("RulesPlaylists::Rule"); registered=true; } } PlaylistRulesDialog::~PlaylistRulesDialog() { } void PlaylistRulesDialog::edit(const QString &name) { RulesPlaylists::Entry e=rules->entry(name); if (model->rowCount()) { model->removeRows(0, model->rowCount()); } nameText->setText(name); foreach (const RulesPlaylists::Rule &r, e.rules) { QStandardItem *item = new QStandardItem(); ::update(item, r); model->setItem(model->rowCount(), 0, item); } origName=name; ratingFrom->setValue(e.ratingFrom); ratingTo->setValue(e.ratingTo); minDuration->setValue(e.minDuration); maxDuration->setValue(e.maxDuration); numTracks->setValue(e.numTracks); if (order) { order->setCurrentIndex(e.order); setOrder(); orderAscending->setCurrentIndex(e.orderAscending ? 0 : 1); } show(); } void PlaylistRulesDialog::enableOkButton() { bool enable=!nameText->text().trimmed().isEmpty(); enableButton(Ok, enable); } void PlaylistRulesDialog::slotButtonClicked(int button) { switch (button) { case Ok: { if (save()) { accept(); } break; } case Cancel: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } void PlaylistRulesDialog::controlButtons() { int numSel=rulesList->selectedIndexes().count(); removeBtn->setEnabled(numSel>0); editBtn->setEnabled(1==numSel); } void PlaylistRulesDialog::add() { if (!dlg) { dlg=new PlaylistRuleDialog(this, rules->isDynamic()); connect(dlg, SIGNAL(addRule(const RulesPlaylists::Rule&)), SLOT(addRule(const RulesPlaylists::Rule&))); } dlg->createNew(); } void PlaylistRulesDialog::addRule(const RulesPlaylists::Rule &rule) { QStandardItem *item = new QStandardItem(); ::update(item, rule); int index=indexOf(item); if (-1!=index) { delete item; } else { model->setItem(model->rowCount(), 0, item); } } void PlaylistRulesDialog::edit() { QModelIndexList items=rulesList->selectedIndexes(); if (1!=items.count()) { return; } if (!dlg) { dlg=new PlaylistRuleDialog(this, rules->isDynamic()); connect(dlg, SIGNAL(addRule(const RulesPlaylists::Rule&)), SLOT(addRule(const RulesPlaylists::Rule&))); } QModelIndex index=proxy->mapToSource(items.at(0)); QStandardItem *item=model->itemFromIndex(index); RulesPlaylists::Rule rule; QMap v=item->data().toMap(); QMap::ConstIterator it(v.constBegin()); QMap::ConstIterator end(v.constEnd()); for (; it!=end; ++it) { rule.insert(it.key(), it.value().toString()); } if (dlg->edit(rule)) { ::update(item, dlg->rule()); int idx=indexOf(item, true); if (-1!=idx && idx!=index.row()) { model->removeRow(index.row()); } } } void PlaylistRulesDialog::remove() { QModelIndexList items=rulesList->selectedIndexes(); QList rows; foreach (const QModelIndex &i, items) { rows.append(proxy->mapToSource(i).row()); } qSort(rows); while (rows.count()) { model->removeRow(rows.takeLast()); } } void PlaylistRulesDialog::showAbout() { if (rules->isDynamic()) { MessageBox::information(this, #ifdef Q_OS_MAC tr("About dynamic rules")+QLatin1String("

    ")+ #endif tr("

    Cantata will query your library using all of the rules listed. " "The list of Include rules will be used to build a set of songs that can be used. " "The list of Exclude rules will be used to build a set of songs that cannot be used. " "If there are no Include rules, Cantata will assume that all songs (bar those from Exclude) can be used.

    " "

    e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: " "

    • Include AlbumArtist=Wibble Genre=Rock
    • Include AlbumArtist=Various Artists
    " "To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: " "
    • Include AlbumArtist=Wibble
    • Exclude AlbumArtist=Wibble Album=Abc
    " "After the set of usable songs has been created, Cantata will randomly select songs to " "keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then " "only songs with a rating within this range will be used. Likewise, if a duration has been set.

    ") ); } else { MessageBox::information(this, #ifdef Q_OS_MAC tr("About smart rules")+QLatin1String("

    ")+ #endif tr("

    Cantata will query your library using all of the rules listed. " "The list of Include rules will be used to build a set of songs that can be used. " "The list of Exclude rules will be used to build a set of songs that cannot be used. " "If there are no Include rules, Cantata will assume that all songs (bar those from Exclude) can be used.

    " "

    e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: " "

    • Include AlbumArtist=Wibble Genre=Rock
    • Include AlbumArtist=Various Artists
    " "To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: " "
    • Include AlbumArtist=Wibble
    • Exclude AlbumArtist=Wibble Album=Abc
    " "After the set of usable songs has been created, Cantata will add the desired number of songs to " "the play queue. If a range of ratings has been specified, then " "only songs with a rating within this range will be used. Likewise, if a duration has been set.

    ") ); } } void PlaylistRulesDialog::setOrder() { orderAscending->setEnabled(RulesPlaylists::Order_Random!=order->currentData().toInt()); if (RulesPlaylists::Order_Rating==order->currentData().toInt()) { orderAscending->setCurrentIndex(1); } } void PlaylistRulesDialog::saved(bool s) { if (s) { accept(); } else { messageWidget->setError(tr("Failed to save %1").arg(nameText->text().trimmed())); controls->setEnabled(true); } } bool PlaylistRulesDialog::save() { if (!controls->isEnabled()) { return false; } QString name=nameText->text().trimmed(); if (name.isEmpty()) { return false; } if (name!=origName && rules->exists(name) && MessageBox::No==MessageBox::warningYesNo(this, tr("A set of rules named '%1' already exists!\n\nOverwrite?").arg(name), tr("Overwrite Rules"), StdGuiItem::overwrite(), StdGuiItem::cancel())) { return false; } RulesPlaylists::Entry entry; entry.name=name; int from=ratingFrom->value(); int to=ratingTo->value(); entry.ratingFrom=qMin(from, to); entry.ratingTo=qMax(from, to); if (order) { entry.order=(RulesPlaylists::Order)order->currentData().toInt(); entry.orderAscending=0==orderAscending->currentIndex(); } from=minDuration->value(); to=maxDuration->value(); if (to>0) { entry.minDuration=qMin(from, to); entry.maxDuration=qMax(from, to); } else { entry.minDuration=from; entry.maxDuration=to; } entry.numTracks=numTracks->value(); for (int i=0; irowCount(); ++i) { QStandardItem *itm=model->item(i); if (itm) { QMap v=itm->data().toMap(); QMap::ConstIterator it(v.constBegin()); QMap::ConstIterator end(v.constEnd()); RulesPlaylists::Rule rule; for (; it!=end; ++it) { rule.insert(it.key(), it.value().toString()); } entry.rules.append(rule); } } bool saved=rules->save(entry); if (rules->isRemote()) { if (saved) { messageWidget->setInformation(tr("Saving %1").arg(name)); controls->setEnabled(false); enableButton(Ok, false); } return false; } else { if (saved && !origName.isEmpty() && entry.name!=origName) { rules->del(origName); } return saved; } } int PlaylistRulesDialog::indexOf(QStandardItem *item, bool diff) { QMap v=item->data().toMap(); for (int i=0; irowCount(); ++i) { QStandardItem *itm=model->item(i); if (itm) { if (itm->data().toMap()==v && (!diff || itm!=item)) { return i; } } } return -1; } cantata-2.2.0/playlists/playlistrulesdialog.h000066400000000000000000000034741316350454000214300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYLIST_RULES_DIALOG_H #define PLAYLIST_RULES_DIALOG_H #include "config.h" #include "support/dialog.h" #include "ui_playlistrules.h" #include "dynamicplaylists.h" class PlaylistRuleDialog; class QStandardItemModel; class QStandardItem; class RulesSort; class PlaylistRulesDialog : public Dialog, Ui::PlaylistRules { Q_OBJECT public: PlaylistRulesDialog(QWidget *parent, RulesPlaylists *m); virtual ~PlaylistRulesDialog(); void edit(const QString &name); private: void slotButtonClicked(int button); bool save(); int indexOf(QStandardItem *item, bool diff=false); private Q_SLOTS: void saved(bool s); void enableOkButton(); void controlButtons(); void add(); void addRule(const RulesPlaylists::Rule &rule); void edit(); void remove(); void showAbout(); void setOrder(); private: RulesPlaylists *rules; RulesSort *proxy; QStandardItemModel *model; QString origName; PlaylistRuleDialog *dlg; }; #endif cantata-2.2.0/playlists/playlistspage.cpp000066400000000000000000000043441316350454000205450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistspage.h" #include "support/configuration.h" #include "models/playlistsmodel.h" #include "dynamicplaylists.h" #include "dynamicplaylistspage.h" #include "smartplaylists.h" #include "smartplaylistspage.h" #include "storedplaylistspage.h" #include "gui/settings.h" PlaylistsPage::PlaylistsPage(QWidget *p) : MultiPageWidget(p) { stored=new StoredPlaylistsPage(this); addPage(PlaylistsModel::self()->name(), PlaylistsModel::self()->icon(), PlaylistsModel::self()->title(), PlaylistsModel::self()->descr(), stored); dynamic=new DynamicPlaylistsPage(this); addPage(DynamicPlaylists::self()->name(), DynamicPlaylists::self()->icon(), DynamicPlaylists::self()->title(), DynamicPlaylists::self()->descr(), dynamic); smart=new SmartPlaylistsPage(this); addPage(SmartPlaylists::self()->name(), SmartPlaylists::self()->icon(), SmartPlaylists::self()->title(), SmartPlaylists::self()->descr(), smart); connect(stored, SIGNAL(addToDevice(QString,QString,QList)), SIGNAL(addToDevice(QString,QString,QList))); Configuration config(metaObject()->className()); load(config); } PlaylistsPage::~PlaylistsPage() { Configuration config(metaObject()->className()); save(config); } #ifdef ENABLE_DEVICES_SUPPORT void PlaylistsPage::addSelectionToDevice(const QString &udi) { if (stored==currentWidget()) { stored->addSelectionToDevice(udi); } } #endif cantata-2.2.0/playlists/playlistspage.h000066400000000000000000000027121316350454000202070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYLISTS_PAGE_H #define PLAYLISTS_PAGE_H #include "widgets/multipagewidget.h" class Action; class StoredPlaylistsPage; class DynamicPlaylistsPage; class SmartPlaylistsPage; class PlaylistsPage : public MultiPageWidget { Q_OBJECT public: PlaylistsPage(QWidget *p); virtual ~PlaylistsPage(); #ifdef ENABLE_DEVICES_SUPPORT void addSelectionToDevice(const QString &udi); #endif Q_SIGNALS: void addToDevice(const QString &from, const QString &to, const QList &songs); private: StoredPlaylistsPage *stored; DynamicPlaylistsPage *dynamic; SmartPlaylistsPage *smart; }; #endif cantata-2.2.0/playlists/rulesplaylists.cpp000066400000000000000000000302121316350454000207540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dynamicplaylists.h" #include "config.h" #include "support/utils.h" #include "widgets/icons.h" #include "models/roles.h" #include "gui/settings.h" #include #include #include #include #include #include #include #include #include #include const QString RulesPlaylists::constExtension=QLatin1String(".rules"); const QString RulesPlaylists::constRuleKey=QLatin1String("Rule"); const QString RulesPlaylists::constArtistKey=QLatin1String("Artist"); const QString RulesPlaylists::constSimilarArtistsKey=QLatin1String("SimilarArtists"); const QString RulesPlaylists::constAlbumArtistKey=QLatin1String("AlbumArtist"); const QString RulesPlaylists::constComposerKey=QLatin1String("Composer"); const QString RulesPlaylists::constCommentKey=QLatin1String("Comment"); const QString RulesPlaylists::constAlbumKey=QLatin1String("Album"); const QString RulesPlaylists::constTitleKey=QLatin1String("Title"); const QString RulesPlaylists::constGenreKey=QLatin1String("Genre"); const QString RulesPlaylists::constDateKey=QLatin1String("Date"); const QString RulesPlaylists::constRatingKey=QLatin1String("Rating"); const QString RulesPlaylists::constDurationKey=QLatin1String("Duration"); const QString RulesPlaylists::constNumTracksKey=QLatin1String("NumTracks"); const QString RulesPlaylists::constFileKey=QLatin1String("File"); const QString RulesPlaylists::constExactKey=QLatin1String("Exact"); const QString RulesPlaylists::constExcludeKey=QLatin1String("Exclude"); const QString RulesPlaylists::constOrderKey=QLatin1String("Order"); const QString RulesPlaylists::constOrderAscendingKey=QLatin1String("OrderAscending"); const QChar RulesPlaylists::constRangeSep=QLatin1Char('-'); const QChar RulesPlaylists::constKeyValSep=QLatin1Char(':'); RulesPlaylists::Order RulesPlaylists::toOrder(const QString &str) { for (int i=0; i=entryList.count()) { return QModelIndex(); } return createIndex(row, column); } QVariant RulesPlaylists::data(const QModelIndex &index, int role) const { if (!index.isValid()) { switch (role) { case Cantata::Role_TitleText: return title(); case Cantata::Role_SubText: return descr(); case Qt::DecorationRole: return icon(); } return QVariant(); } if (index.parent().isValid() || index.row()>=entryList.count()) { return QVariant(); } switch (role) { case Qt::ToolTipRole: if (!Settings::self()->infoTooltips()) { return QVariant(); } case Qt::DisplayRole: return entryList.at(index.row()).name; case Cantata::Role_SubText: { const Entry &e=entryList.at(index.row()); return tr("%n Rule(s)", "", e.rules.count())+(e.haveRating() ? tr(", Rating: %1..%2") .arg((double)e.ratingFrom/Song::Rating_Step).arg((double)e.ratingTo/Song::Rating_Step) : QString()) + (isDynamic() ? QString() : (QLatin1String(", ") + orderName(e.order))) + (isDynamic() || Order_Random==e.order ? QString() : (" ("+(e.orderAscending ? tr("Ascending") : tr("Descending"))+")")); } default: return QVariant(); } } Qt::ItemFlags RulesPlaylists::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return Qt::NoItemFlags; } RulesPlaylists::Entry RulesPlaylists::entry(const QString &e) { if (!e.isEmpty()) { QList::Iterator it=find(e); if (it!=entryList.end()) { return *it; } } Entry def; def.numTracks=defaultNumTracks(); return def; } bool RulesPlaylists::save(const Entry &e) { if (e.name.isEmpty()) { return false; } QString string; QTextStream str(&string); if (e.numTracks >= minTracks() && e.numTracks <= maxTracks()) { str << constNumTracksKey << constKeyValSep << e.numTracks << '\n'; } if (e.ratingFrom!=0 || e.ratingTo!=0) { str << constRatingKey << constKeyValSep << e.ratingFrom << constRangeSep << e.ratingTo << '\n'; } if (e.minDuration!=0 || e.maxDuration!=0) { str << constDurationKey << constKeyValSep << e.minDuration << constRangeSep << e.maxDuration << '\n'; } if (Order_Random!=e.order) { str << constOrderKey << constKeyValSep << orderStr(e.order) << '\n'; } foreach (const Rule &rule, e.rules) { if (!rule.isEmpty()) { str << constRuleKey << '\n'; Rule::ConstIterator it(rule.constBegin()); Rule::ConstIterator end(rule.constEnd()); for (; it!=end; ++it) { str << it.key() << constKeyValSep << it.value() << '\n'; } } } if (isRemote()) { return saveRemote(string, e); } QFile f(Utils::dataDir(rulesDir, true)+e.name+constExtension); if (f.open(QIODevice::WriteOnly|QIODevice::Text)) { QTextStream out(&f); out.setCodec("UTF-8"); out << string; updateEntry(e); return true; } return false; } void RulesPlaylists::updateEntry(const Entry &e) { QList::Iterator it=find(e.name); if (it!=entryList.end()) { entryList.replace(it-entryList.begin(), e); QModelIndex idx=index(it-entryList.begin(), 0, QModelIndex()); emit dataChanged(idx, idx); } else { beginInsertRows(QModelIndex(), entryList.count(), entryList.count()); entryList.append(e); endInsertRows(); } } void RulesPlaylists::del(const QString &name) { QList::Iterator it=find(name); if (it==entryList.end()) { return; } QString fName(Utils::dataDir(rulesDir, false)+name+constExtension); bool isCurrent=currentEntry==name; if (!QFile::exists(fName) || QFile::remove(fName)) { if (isCurrent) { stop(); } beginRemoveRows(QModelIndex(), it-entryList.begin(), it-entryList.begin()); entryList.erase(it); endRemoveRows(); return; } } QList::Iterator RulesPlaylists::find(const QString &e) { QList::Iterator it(entryList.begin()); QList::Iterator end(entryList.end()); for (; it!=end; ++it) { if ((*it).name==e) { break; } } return it; } void RulesPlaylists::loadLocal() { beginResetModel(); entryList.clear(); currentEntry=QString(); // Load all current enttries... QString dirName=Utils::dataDir(rulesDir); QDir d(dirName); if (d.exists()) { QStringList rulesFiles=d.entryList(QStringList() << QChar('*')+constExtension); foreach (const QString &rf, rulesFiles) { QFile f(dirName+rf); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { QStringList keys=QStringList() << constArtistKey << constSimilarArtistsKey << constAlbumArtistKey << constDateKey << constExactKey << constAlbumKey << constTitleKey << constGenreKey << constFileKey << constExcludeKey; Entry e; e.name=rf.left(rf.length()-constExtension.length()); e.numTracks=defaultNumTracks(); Rule r; QTextStream in(&f); in.setCodec("UTF-8"); QStringList lines = in.readAll().split('\n', QString::SkipEmptyParts); foreach (const QString &line, lines) { QString str=line.trimmed(); if (str.isEmpty() || str.startsWith('#')) { continue; } if (str==constRuleKey) { if (!r.isEmpty()) { e.rules.append(r); r.clear(); } } else if (str.startsWith(constRatingKey+constKeyValSep)) { QStringList vals=str.mid(constRatingKey.length()+1).split(constRangeSep); if (2==vals.count()) { e.ratingFrom=vals.at(0).toUInt(); e.ratingTo=vals.at(1).toUInt(); } } else if (str.startsWith(constDurationKey+constKeyValSep)) { QStringList vals=str.mid(constDurationKey.length()+1).split(constRangeSep); if (2==vals.count()) { e.minDuration=vals.at(0).toUInt(); e.maxDuration=vals.at(1).toUInt(); } } else if (str.startsWith(constOrderKey+constKeyValSep)) { e.order=toOrder(str.mid(constOrderKey.length()+1)); } else if (str.startsWith(constOrderAscendingKey+constKeyValSep)) { e.orderAscending="true"==str.mid(constOrderAscendingKey.length()+1); } else { foreach (const QString &k, keys) { if (str.startsWith(k+constKeyValSep)) { r.insert(k, str.mid(k.length()+1)); } } } } if (!r.isEmpty()) { e.rules.append(r); r.clear(); } entryList.append(e); } } } endResetModel(); } cantata-2.2.0/playlists/rulesplaylists.h000066400000000000000000000112351316350454000204250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef RULES_PLAYLISTS_H #define RULES_PLAYLISTS_H #include #include #include #include #include #include "models/actionmodel.h" #include "support/icon.h" class RulesPlaylists : public ActionModel { Q_OBJECT public: enum Order { Order_AlbumArtist, Order_Artist, Order_Album, Order_Composer, Order_Date, Order_Genre, Order_Rating, Order_Age, Order_Random, Order_Count }; static Order toOrder(const QString &str); static QString orderStr(Order order); static QString orderName(Order order); typedef QMap Rule; struct Entry { Entry(const QString &n=QString()) : name(n) { } bool operator==(const Entry &o) const { return name==o.name; } bool haveRating() const { return ratingFrom>=0 && ratingTo>0; } QString name; QList rules; int ratingFrom = 0; int ratingTo = 0; int minDuration = 0; int maxDuration = 0; int numTracks = 10; Order order = Order_Random; bool orderAscending = true; }; static const QString constExtension; static const QString constRuleKey; static const QString constArtistKey; static const QString constSimilarArtistsKey; static const QString constAlbumArtistKey; static const QString constComposerKey; static const QString constCommentKey; static const QString constAlbumKey; static const QString constTitleKey; static const QString constGenreKey; static const QString constDateKey; static const QString constRatingKey; static const QString constDurationKey; static const QString constNumTracksKey; static const QString constFileKey; static const QString constExactKey; static const QString constExcludeKey; static const QString constOrderKey; static const QString constOrderAscendingKey; static const QChar constRangeSep; static const QChar constKeyValSep; RulesPlaylists(const QString &iconFile, const QString &dir); virtual ~RulesPlaylists() { } virtual QString name() const =0; virtual QString title() const =0; virtual QString descr() const =0; virtual bool isDynamic() const { return false; } const Icon & icon() const { return icn; } virtual bool isRemote() const { return false; } virtual int minTracks() const { return 10; } virtual int maxTracks() const { return 500; } virtual int defaultNumTracks() const { return 10; } virtual bool saveRemote(const QString &string, const Entry &e) { Q_UNUSED(string); Q_UNUSED(e); return false; } virtual void stop(bool sendClear=false) { Q_UNUSED(sendClear) } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex&) const { return 1; } bool hasChildren(const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &index) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; Entry entry(const QString &e); Entry entry(int row) const { return row>=0 && row & entries() const { return entryList; } protected: QList::Iterator find(const QString &e); void loadLocal(); void updateEntry(const Entry &e); protected: Icon icn; QString rulesDir; QList entryList; QString currentEntry; }; #endif cantata-2.2.0/playlists/smartplaylists.cpp000066400000000000000000000035571316350454000207640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "smartplaylists.h" #include "support/monoicon.h" #include "support/globalstatic.h" #include "models/roles.h" GLOBAL_STATIC(SmartPlaylists, instance) SmartPlaylists::SmartPlaylists() : RulesPlaylists("gradcap", "smart") { playlistIcon=MonoIcon::icon(FontAwesome::graduationcap, Utils::monoIconColor()); } QString SmartPlaylists::name() const { return QLatin1String("smart"); } QString SmartPlaylists::title() const { return tr("Smart Playlists"); } QString SmartPlaylists::descr() const { return tr("Rules based playlists"); } QVariant SmartPlaylists::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return RulesPlaylists::data(index, role); } if (index.parent().isValid() || index.row()>=entryList.count()) { return QVariant(); } switch (role) { case Qt::DecorationRole: return playlistIcon; case Cantata::Role_Actions: return ActionModel::data(index, role); default: return RulesPlaylists::data(index, role); } } cantata-2.2.0/playlists/smartplaylists.h000066400000000000000000000025601316350454000204220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SMART_PLAYLISTS_H #define SMART_PLAYLISTS_H #include #include "rulesplaylists.h" class SmartPlaylists : public RulesPlaylists { Q_OBJECT public: static SmartPlaylists * self(); SmartPlaylists(); virtual ~SmartPlaylists() { } QString name() const; QString title() const; QString descr() const; QVariant data(const QModelIndex &index, int role) const; int maxTracks() const { return 10000; } int defaultNumTracks() const { return 100; } private: QIcon playlistIcon; }; #endif cantata-2.2.0/playlists/smartplaylistspage.cpp000066400000000000000000000406061316350454000216150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "smartplaylistspage.h" #include "smartplaylists.h" #include "playlistrulesdialog.h" #include "widgets/icons.h" #include "support/action.h" #include "support/configuration.h" #include "mpd-interface/mpdconnection.h" #include "support/messagebox.h" #include "gui/stdactions.h" #include "models/mpdlibrarymodel.h" SmartPlaylistsPage::SmartPlaylistsPage(QWidget *p) : SinglePageWidget(p) { addAction = new Action(Icons::self()->addNewItemIcon, tr("Add"), this); editAction = new Action(Icons::self()->editIcon, tr("Edit"), this); removeAction = new Action(Icons::self()->removeIcon, tr("Remove"), this); ToolButton *addBtn=new ToolButton(this); ToolButton *editBtn=new ToolButton(this); ToolButton *removeBtn=new ToolButton(this); addBtn->setDefaultAction(addAction); editBtn->setDefaultAction(editAction); removeBtn->setDefaultAction(removeAction); connect(this, SIGNAL(search(QByteArray,QString)), MPDConnection::self(), SLOT(search(QByteArray,QString))); connect(MPDConnection::self(), SIGNAL(searchResponse(QString,QList)), this, SLOT(searchResponse(QString,QList))); connect(this, SIGNAL(getRating(QString)), MPDConnection::self(), SLOT(getRating(QString))); connect(MPDConnection::self(), SIGNAL(rating(QString,quint8)), this, SLOT(rating(QString,quint8))); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(addAction, SIGNAL(triggered()), SLOT(addNew())); connect(editAction, SIGNAL(triggered()), SLOT(edit())); connect(removeAction, SIGNAL(triggered()), SLOT(remove())); proxy.setSourceModel(SmartPlaylists::self()); view->setModel(&proxy); view->setDeleteAction(removeAction); view->setMode(ItemView::Mode_List); controlActions(); Configuration config(metaObject()->className()); view->load(config); controls=QList() << addBtn << editBtn << removeBtn; init(ReplacePlayQueue|AppendToPlayQueue, QList(), controls); view->addAction(editAction); view->addAction(removeAction); view->alwaysShowHeader(); } SmartPlaylistsPage::~SmartPlaylistsPage() { Configuration config(metaObject()->className()); view->save(config); } void SmartPlaylistsPage::doSearch() { QString text=view->searchText().trimmed(); proxy.update(text); if (proxy.enabled() && !proxy.filterText().isEmpty()) { view->expandAll(); } } void SmartPlaylistsPage::controlActions() { QModelIndexList selected=qobject_cast(sender()) ? QModelIndexList() : view->selectedIndexes(false); // Dont need sorted selection here... StdActions::self()->enableAddToPlayQueue(1==selected.count()); editAction->setEnabled(1==selected.count()); removeAction->setEnabled(selected.count()); } void SmartPlaylistsPage::addNew() { PlaylistRulesDialog *dlg=new PlaylistRulesDialog(this, SmartPlaylists::self()); dlg->edit(QString()); } void SmartPlaylistsPage::edit() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } PlaylistRulesDialog *dlg=new PlaylistRulesDialog(this, SmartPlaylists::self()); dlg->edit(selected.at(0).data(Qt::DisplayRole).toString()); } void SmartPlaylistsPage::remove() { QModelIndexList selected=view->selectedIndexes(); if (selected.isEmpty() || MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the selected rules?\n\nThis cannot be undone."), tr("Remove Smart Rules"), StdGuiItem::remove(), StdGuiItem::cancel())) { return; } QStringList names; foreach (const QModelIndex &idx, selected) { names.append(idx.data(Qt::DisplayRole).toString()); } foreach (const QString &name, names) { DynamicPlaylists::self()->del(name); } } void SmartPlaylistsPage::headerClicked(int level) { if (0==level) { emit close(); } } void SmartPlaylistsPage::enableWidgets(bool enable) { foreach (QWidget *c, controls) { c->setEnabled(enable); } view->setEnabled(enable); } void SmartPlaylistsPage::searchResponse(const QString &id, const QList &songs) { if (id.length()<3 || id.mid(2).toInt()!=command.id || command.isEmpty()) { return; } if (id.startsWith("I:")) { command.songs.unite(songs.toSet()); } else if (id.startsWith("E:")) { command.songs.subtract(songs.toSet()); } if (command.includeRules.isEmpty()) { if (command.songs.isEmpty()) { command.clear(); MessageBox::error(this, tr("Failed to locate any matching songs")); return; } if (command.excludeRules.isEmpty()) { filterCommand(); } else { emit search(command.excludeRules.takeFirst(), "E:"+QString::number(command.id)); } } else { emit search(command.includeRules.takeFirst(), "I:"+QString::number(command.id)); } } void SmartPlaylistsPage::filterCommand() { if (command.minDuration>0 || command.maxDuration>0) { QSet toRemove; for (const auto &s: command.songs) { if (command.minDuration>s.time || (command.maxDuration>0 && s.time>command.maxDuration)) { toRemove.insert(s); } else { command.toCheck.append(s.file); } } command.songs.subtract(toRemove); if (command.songs.isEmpty()) { command.clear(); MessageBox::error(this, tr("Failed to locate any matching songs")); return; } } if (command.filterRating || command.fetchRatings) { if (command.toCheck.isEmpty()) { for (const auto &s: command.songs) { command.toCheck.append(s.file); } } command.checking=command.toCheck.takeFirst(); emit getRating(command.checking); } else { addSongsToPlayQueue(); } } void SmartPlaylistsPage::rating(const QString &file, quint8 val) { if (command.isEmpty() || file!=command.checking) { return; } for (auto &s: command.songs) { if (s.file==file) { s.rating=val; if (command.filterRating && (valcommand.ratingTo)) { command.songs.remove(s); } break; } } if (command.toCheck.isEmpty()) { command.checking.clear(); addSongsToPlayQueue(); } else { command.checking=command.toCheck.takeFirst(); emit getRating(command.checking); } } static bool sortAscending = true; static bool composerSort(const Song &s1, const Song &s2) { const QString v1=s1.hasComposer() ? s1.composer() : QString(); const QString v2=s2.hasComposer() ? s2.composer() : QString(); int c=v1.localeAwareCompare(v2); return sortAscending ? (c<0 || (c==0 && s10 || (c==0 && s10 || (c==0 && s10 || (c==0 && s10 || (c==0 && s10 || (c==0 && s1s2.year || (s1.year==s2.year && s1s2.rating || (s1.rating==s2.rating && s1s2.lastModified || (s1.lastModified==s2.lastModified && s1 songs = command.songs.toList(); command.songs.clear(); sortAscending = command.orderAscending; switch(command.order) { case RulesPlaylists::Order_AlbumArtist: qSort(songs.begin(), songs.end(), albumArtistSort); break; case RulesPlaylists::Order_Artist: qSort(songs.begin(), songs.end(), artistSort); break; case RulesPlaylists::Order_Album: qSort(songs.begin(), songs.end(), albumSort); break; case RulesPlaylists::Order_Composer: qSort(songs.begin(), songs.end(), composerSort); break; case RulesPlaylists::Order_Date: qSort(songs.begin(), songs.end(), dateSort); break; case RulesPlaylists::Order_Genre: qSort(songs.begin(), songs.end(), genreSort); break; case RulesPlaylists::Order_Rating: qSort(songs.begin(), songs.end(), ratingSort); break; case RulesPlaylists::Order_Age: qSort(songs.begin(), songs.end(), ageSort); break; default: case RulesPlaylists::Order_Random: std::random_shuffle(songs.begin(), songs.end()); } QStringList files; for (int i=0; iclearSelection(); } command.clear(); } void SmartPlaylistsPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { if (!name.isEmpty()) { return; } QModelIndexList selected=view->selectedIndexes(false); if (1!=selected.count()) { return; } QModelIndex idx = proxy.mapToSource(selected.at(0)); if (!idx.isValid()) { return; } RulesPlaylists::Entry pl = SmartPlaylists::self()->entry(idx.row()); if (pl.name.isEmpty() || pl.numTracks<=0) { return; } command = Command(pl, action, priorty, decreasePriority, command.id+1); QList::ConstIterator it = pl.rules.constBegin(); QList::ConstIterator end = pl.rules.constEnd(); QSet mpdGenres; for (; it!=end; ++it) { QList dates; QByteArray match = "find"; bool isInclude = true; RulesPlaylists::Rule::ConstIterator rIt = (*it).constBegin(); RulesPlaylists::Rule::ConstIterator rEnd = (*it).constEnd(); QByteArray baseRule; QStringList genres; for (; rIt!=rEnd; ++rIt) { if (RulesPlaylists::constDateKey==rIt.key()) { QStringList parts=rIt.value().trimmed().split(RulesPlaylists::constRangeSep); if (2==parts.length()) { int from = parts.at(0).toInt(); int to = parts.at(1).toInt(); if (from > to) { for (int i=to; i<=from; ++i) { dates.append(i); } } else { for (int i=from; i<=to; ++i) { dates.append(i); } } } else if (1==parts.length()) { dates.append(parts.at(0).toInt()); } } else if (RulesPlaylists::constGenreKey==rIt.key() && rIt.value().trimmed().endsWith("*")) { QString find=rIt.value().left(rIt.value().length()-1); if (!find.isEmpty()) { if (mpdGenres.isEmpty()) { mpdGenres = MpdLibraryModel::self()->getGenres(); } foreach (const QString &g, mpdGenres) { if (g.startsWith(find)) { genres.append(g); } } } } else if (RulesPlaylists::constArtistKey==rIt.key() || RulesPlaylists::constAlbumKey==rIt.key() || RulesPlaylists::constAlbumArtistKey==rIt.key() || RulesPlaylists::constComposerKey==rIt.key() || RulesPlaylists::constCommentKey==rIt.key() || RulesPlaylists::constTitleKey==rIt.key() || RulesPlaylists::constArtistKey==rIt.key() || RulesPlaylists::constGenreKey==rIt.key() || RulesPlaylists::constFileKey==rIt.key()) { baseRule += " " + rIt.key() + " " + MPDConnection::encodeName(rIt.value()); } else if (RulesPlaylists::constExactKey==rIt.key()) { if ("false" == rIt.value()) { match = "search"; } } else if (RulesPlaylists::constExcludeKey==rIt.key()) { if ("true" == rIt.value()) { isInclude = false; } } } if (!baseRule.isEmpty() || !genres.isEmpty() || !dates.isEmpty()) { QList rules; if (genres.isEmpty()) { if (dates.isEmpty()) { rules.append(match + baseRule); } else { foreach(int d, dates) { rules.append(match + baseRule + " Date \"" + QByteArray::number(d) + "\""); } } } else { foreach (const QString &genre, genres) { QByteArray rule = match + baseRule + " Genre " + MPDConnection::encodeName(genre); if (dates.isEmpty()) { rules.append(rule); } else { foreach(int d, dates) { rules.append(rule + " Date \"" + QByteArray::number(d) + "\""); } } } } if (!rules.isEmpty()) { if (isInclude) { command.includeRules += rules; } else { command.excludeRules += rules; } } } } command.filterRating = command.haveRating(); command.fetchRatings = RulesPlaylists::Order_Rating == command.order; if (command.includeRules.isEmpty()) { if (command.haveRating()) { command.includeRules.append("RATING:"+QByteArray::number(command.ratingFrom)+":"+QByteArray::number(command.ratingTo)); command.filterRating = false; command.fetchRatings = false; } else { command.includeRules.append(QByteArray()); } } emit search(command.includeRules.takeFirst(), "I:"+QString::number(command.id)); } cantata-2.2.0/playlists/smartplaylistspage.h000066400000000000000000000063601316350454000212610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SMART_PLAYLISTS_PAGE_H #define SMART_PLAYLISTS_PAGE_H #include "widgets/singlepagewidget.h" #include "playlistproxymodel.h" #include "rulesplaylists.h" class Action; class QLabel; class SmartPlaylistsPage : public SinglePageWidget { Q_OBJECT struct Command { Command(const RulesPlaylists::Entry &e=RulesPlaylists::Entry(), int a=0, quint8 prio=0, bool dec=false, quint32 i=0) : playlist(e.name), action(a), priorty(prio), decreasePriority(dec), ratingFrom(e.ratingFrom), ratingTo(e.ratingTo), minDuration(e.minDuration), maxDuration(e.maxDuration), numTracks(e.numTracks), order(e.order), orderAscending(e.orderAscending), id(i) { } bool isEmpty() const { return playlist.isEmpty(); } void clear() { playlist.clear(); includeRules.clear(); excludeRules.clear(); songs.clear(); toCheck.clear(); checking.clear(); } bool haveRating() const { return ratingFrom>=0 && ratingTo>0; } QString playlist; int action; quint8 priorty; bool decreasePriority; QList includeRules; QList excludeRules; bool filterRating = false; bool fetchRatings = false; int ratingFrom = 0; int ratingTo = 0; int minDuration = 0; int maxDuration = 0; int numTracks = 0; RulesPlaylists::Order order = RulesPlaylists::Order_Random; bool orderAscending = true; quint32 id; QString checking; QSet songs; QStringList toCheck; }; public: SmartPlaylistsPage(QWidget *p); virtual ~SmartPlaylistsPage(); void setView(int) { } Q_SIGNALS: void search(const QByteArray &query, const QString &id); void getRating(const QString &file); private Q_SLOTS: void addNew(); void edit(); void remove(); void headerClicked(int level); void searchResponse(const QString &id, const QList &songs); void rating(const QString &file, quint8 val); private: void doSearch(); void controlActions(); void enableWidgets(bool enable); void filterCommand(); void addSongsToPlayQueue(); void addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority); private: PlaylistProxyModel proxy; Action *addAction; Action *editAction; Action *removeAction; QList controls; Command command; }; #endif cantata-2.2.0/playlists/storedplaylistspage.cpp000066400000000000000000000410171316350454000217640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "storedplaylistspage.h" #include "models/playlistsmodel.h" #include "mpd-interface/mpdconnection.h" #include "support/messagebox.h" #include "support/inputdialog.h" #include "widgets/icons.h" #include "gui/stdactions.h" #include "gui/customactions.h" #include "support/actioncollection.h" #include "support/configuration.h" #include "widgets/tableview.h" #include "widgets/spacerwidget.h" #include "widgets/menubutton.h" #include "gui/settings.h" #include #include class PlaylistTableView : public TableView { public: PlaylistTableView(QWidget *p) : TableView(QLatin1String("playlist"), p) { setUseSimpleDelegate(); setIndentation(fontMetrics().width(QLatin1String("XX"))); } virtual ~PlaylistTableView() { } }; StoredPlaylistsPage::StoredPlaylistsPage(QWidget *p) : SinglePageWidget(p) { renamePlaylistAction = new Action(Icons::self()->editIcon, tr("Rename"), this); removeDuplicatesAction=new Action(tr("Remove Duplicates"), this); removeDuplicatesAction->setEnabled(false); view->allowGroupedView(); view->allowTableView(new PlaylistTableView(view)); view->setAcceptDrops(true); view->setDragDropOverwriteMode(false); view->setDragDropMode(QAbstractItemView::DragDrop); proxy.setSourceModel(PlaylistsModel::self()); view->setModel(&proxy); view->setDeleteAction(StdActions::self()->removeAction); view->alwaysShowHeader(); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions())); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(this, SIGNAL(loadPlaylist(const QString &, bool)), MPDConnection::self(), SLOT(loadPlaylist(const QString &, bool))); connect(this, SIGNAL(removePlaylist(const QString &)), MPDConnection::self(), SLOT(removePlaylist(const QString &))); connect(this, SIGNAL(savePlaylist(const QString &)), MPDConnection::self(), SLOT(savePlaylist(const QString &))); connect(this, SIGNAL(renamePlaylist(const QString &, const QString &)), MPDConnection::self(), SLOT(renamePlaylist(const QString &, const QString &))); connect(this, SIGNAL(removeFromPlaylist(const QString &, const QList &)), MPDConnection::self(), SLOT(removeFromPlaylist(const QString &, const QList &))); connect(StdActions::self()->savePlayQueueAction, SIGNAL(triggered()), this, SLOT(savePlaylist())); connect(renamePlaylistAction, SIGNAL(triggered()), this, SLOT(renamePlaylist())); connect(removeDuplicatesAction, SIGNAL(triggered()), this, SLOT(removeDuplicates())); connect(PlaylistsModel::self(), SIGNAL(updated(const QModelIndex &)), this, SLOT(updated(const QModelIndex &))); connect(PlaylistsModel::self(), SIGNAL(playlistRemoved(quint32)), view, SLOT(collectionRemoved(quint32))); intitiallyCollapseAction=new Action(tr("Initially Collapse Albums"), this); intitiallyCollapseAction->setCheckable(true); Configuration config(metaObject()->className()); view->setMode(ItemView::Mode_DetailedTree); view->load(config); intitiallyCollapseAction->setChecked(view->isStartClosed()); connect(intitiallyCollapseAction, SIGNAL(toggled(bool)), SLOT(setStartClosed(bool))); MenuButton *menu=new MenuButton(this); menu->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List << ItemView::Mode_GroupedTree << ItemView::Mode_Table)); menu->addAction(intitiallyCollapseAction); init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu); view->addAction(StdActions::self()->addToStoredPlaylistAction); view->addAction(CustomActions::self()); #ifdef ENABLE_DEVICES_SUPPORT view->addAction(StdActions::self()->copyToDeviceAction); #endif view->addAction(renamePlaylistAction); view->addAction(StdActions::self()->removeAction); view->addAction(removeDuplicatesAction); connect(view, SIGNAL(updateToPlayQueue(QModelIndex,bool)), this, SLOT(updateToPlayQueue(QModelIndex,bool))); } StoredPlaylistsPage::~StoredPlaylistsPage() { Configuration config(metaObject()->className()); view->save(config); } void StoredPlaylistsPage::updateRows() { view->updateRows(); } void StoredPlaylistsPage::clear() { PlaylistsModel::self()->clear(); } //QStringList StoredPlaylistsPage::selectedFiles() const //{ // QModelIndexList indexes=view->selectedIndexes(); // if (indexes.isEmpty()) { // return QStringList(); // } // // QModelIndexList mapped; // foreach (const QModelIndex &idx, indexes) { // mapped.append(proxy.mapToSource(idx)); // } // // return PlaylistsModel::self()->filenames(mapped, true); //} void StoredPlaylistsPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { addItemsToPlayList(view->selectedIndexes(), name, action, priorty, decreasePriority); } void StoredPlaylistsPage::setView(int mode) { PlaylistsModel::self()->setMultiColumn(ItemView::Mode_Table==mode); SinglePageWidget::setView(mode); intitiallyCollapseAction->setEnabled(ItemView::Mode_GroupedTree==mode); } void StoredPlaylistsPage::removeItems() { QSet remPlaylists; QMap > remSongs; QModelIndexList selected = view->selectedIndexes(); foreach(const QModelIndex &index, selected) { if(index.isValid()) { QModelIndex realIndex(proxy.mapToSource(index)); if(realIndex.isValid()) { PlaylistsModel::Item *item=static_cast(realIndex.internalPointer()); if (item->isPlaylist()) { PlaylistsModel::PlaylistItem *pl=static_cast(item); if (!pl->isSmartPlaylist) { remPlaylists.insert(pl->name); } if (remSongs.contains(pl->name)) { remSongs.remove(pl->name); } } else { PlaylistsModel::SongItem *song=static_cast(item); if (!song->parent->isSmartPlaylist && !remPlaylists.contains(song->parent->name)) { remSongs[song->parent->name].append(song->parent->songs.indexOf(song)); } } } } } // Should not happen! if (remPlaylists.isEmpty() && remSongs.isEmpty()) { return; } if (remPlaylists.count() && MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the selected playlists?\n\nThis cannot be undone."), tr("Remove Playlists"), StdGuiItem::remove(), StdGuiItem::cancel())) { return; } foreach (const QString &pl, remPlaylists) { emit removePlaylist(pl); } QMap >::ConstIterator it=remSongs.constBegin(); QMap >::ConstIterator end=remSongs.constEnd(); for (; it!=end; ++it) { emit removeFromPlaylist(it.key(), it.value()); } } void StoredPlaylistsPage::savePlaylist() { QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), QString(), 0, this); if (!name.isEmpty()) { if (PlaylistsModel::self()->exists(name)) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("A playlist named '%1' already exists!\n\nOverwrite?").arg(name), tr("Overwrite Playlist"), StdGuiItem::overwrite(), StdGuiItem::cancel())) { return; } else { emit removePlaylist(name); } } emit savePlaylist(name); } } void StoredPlaylistsPage::renamePlaylist() { const QModelIndexList items = view->selectedIndexes(false); // Dont need sorted selection here... if (1==items.size()) { QModelIndex index = proxy.mapToSource(items.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (!item->isPlaylist()) { return; } QString name = static_cast(item)->name; QString newName = InputDialog::getText(tr("Rename Playlist"), tr("Enter new name for playlist:"), name, 0, this); if (!newName.isEmpty() && name!=newName) { if (PlaylistsModel::self()->exists(newName)) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("A playlist named '%1' already exists!\n\nOverwrite?").arg(newName), tr("Overwrite Playlist"), StdGuiItem::overwrite(), StdGuiItem::cancel())) { return; } else { emit removePlaylist(newName); } } emit renamePlaylist(name, newName); } } } void StoredPlaylistsPage::removeDuplicates() { const QModelIndexList items = view->selectedIndexes(false); // Dont need sorted selection here... if (1==items.size()) { QModelIndex index = proxy.mapToSource(items.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (!item->isPlaylist()) { return; } PlaylistsModel::PlaylistItem *pl=static_cast(item); QMap > map; for (int i=0; isongs.count(); ++i) { Song song=*(pl->songs.at(i)); song.id=i; map[song.albumKey()+"-"+song.artistSong()+"-"+song.track].append(song); } QList toRemove; foreach (const QString &key, map.keys()) { QList values=map.value(key); if (values.size()>1) { Song::sortViaType(values); for (int i=1; iname, toRemove); } } } void StoredPlaylistsPage::itemDoubleClicked(const QModelIndex &index) { if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this) || !static_cast(proxy.mapToSource(index).internalPointer())->isPlaylist()) { QModelIndexList indexes; indexes.append(index); addItemsToPlayList(indexes, QString(), MPDConnection::Append); } } void StoredPlaylistsPage::addItemsToPlayList(const QModelIndexList &indexes, const QString &name, int action, quint8 priorty, bool decreasePriority) { if (indexes.isEmpty()) { return; } // If we only have 1 item selected, see if it is a playlist. If so, we might be able to // just ask MPD to load it... if (name.isEmpty() && 1==indexes.count() && 0==priorty && !proxy.enabled() && MPDConnection::Append==action) { QModelIndex idx=proxy.mapToSource(*(indexes.begin())); PlaylistsModel::Item *item=static_cast(idx.internalPointer()); if (item->isPlaylist()) { emit loadPlaylist(static_cast(item)->name, false); return; } } if (!name.isEmpty()) { foreach (const QModelIndex &idx, indexes) { QModelIndex m=proxy.mapToSource(idx); PlaylistsModel::Item *item=static_cast(m.internalPointer()); if ( (item->isPlaylist() && static_cast(item)->name==name) || (!item->isPlaylist() && static_cast(item)->parent->name==name) ) { MessageBox::error(this, tr("Cannot add songs from '%1' to '%2'").arg(name).arg(name)); return; } } } QStringList files=PlaylistsModel::self()->filenames(proxy.mapToSource(indexes)); if (!files.isEmpty()) { if (name.isEmpty()) { emit add(files, action, priorty, decreasePriority); } else { emit addSongsToPlaylist(name, files); } view->clearSelection(); } } #ifdef ENABLE_DEVICES_SUPPORT QList StoredPlaylistsPage::selectedSongs(bool allowPlaylists) const { Q_UNUSED(allowPlaylists) QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return PlaylistsModel::self()->songs(proxy.mapToSource(selected)); } void StoredPlaylistsPage::addSelectionToDevice(const QString &udi) { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit addToDevice(QString(), udi, songs); view->clearSelection(); } } #endif void StoredPlaylistsPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool enableActions=selected.count()>0; bool allSmartPlaylists=false; bool canRename=false; if (1==selected.count()) { QModelIndex index = proxy.mapToSource(selected.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (item) { if (item->isPlaylist()) { if (static_cast(item)->isSmartPlaylist) { allSmartPlaylists=true; } else { canRename=true; } } else if (static_cast(item)->parent->isSmartPlaylist) { allSmartPlaylists=true; } } } else if (selected.count()<=200) { allSmartPlaylists=true; foreach (const QModelIndex &index, selected) { PlaylistsModel::Item *item=static_cast(proxy.mapToSource(index).internalPointer()); if (item && !(item->isPlaylist() ? static_cast(item)->isSmartPlaylist : static_cast(item)->parent->isSmartPlaylist)) { allSmartPlaylists=false; break; } } } renamePlaylistAction->setEnabled(canRename); removeDuplicatesAction->setEnabled(enableActions && !allSmartPlaylists); StdActions::self()->removeAction->setEnabled(enableActions && !allSmartPlaylists); StdActions::self()->enableAddToPlayQueue(enableActions); StdActions::self()->addToStoredPlaylistAction->setEnabled(enableActions); #ifdef ENABLE_DEVICES_SUPPORT StdActions::self()->copyToDeviceAction->setEnabled(enableActions && !allSmartPlaylists); #endif } void StoredPlaylistsPage::doSearch() { QString text=view->searchText().trimmed(); bool updated=proxy.update(text); if (proxy.enabled() && !proxy.filterText().isEmpty()) { view->expandAll(); } if(updated) { view->updateRows(); } } void StoredPlaylistsPage::updated(const QModelIndex &index) { view->updateRows(proxy.mapFromSource(index)); } void StoredPlaylistsPage::headerClicked(int level) { if (0==level) { emit close(); } } void StoredPlaylistsPage::setStartClosed(bool sc) { view->setStartClosed(sc); } void StoredPlaylistsPage::updateToPlayQueue(const QModelIndex &idx, bool replace) { QStringList files=PlaylistsModel::self()->filenames(proxy.mapToSource(QModelIndexList() << idx, false)); if (!files.isEmpty()) { emit add(files, replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0, false); } } cantata-2.2.0/playlists/storedplaylistspage.h000066400000000000000000000054441316350454000214350ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STORED_PAGE_H #define STORED_PAGE_H #include "widgets/singlepagewidget.h" #include "widgets/multipagewidget.h" #include "models/playlistsproxymodel.h" class Action; class StoredPlaylistsPage : public SinglePageWidget { Q_OBJECT public: StoredPlaylistsPage(QWidget *p); virtual ~StoredPlaylistsPage(); void updateRows(); void clear(); //QStringList selectedFiles() const; void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); void setView(int mode); #ifdef ENABLE_DEVICES_SUPPORT QList selectedSongs(bool allowPlaylists=false) const; void addSelectionToDevice(const QString &udi); #endif Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void loadPlaylist(const QString &name, bool replace); void removePlaylist(const QString &name); void savePlaylist(const QString &name); void renamePlaylist(const QString &oldname, const QString &newname); void removeFromPlaylist(const QString &name, const QList &positions); void addToDevice(const QString &from, const QString &to, const QList &songs); private: void addItemsToPlayList(const QModelIndexList &indexes, const QString &name, int action, quint8 priorty=0, bool decreasePriority=false); public Q_SLOTS: void removeItems(); private Q_SLOTS: void savePlaylist(); void renamePlaylist(); void removeDuplicates(); void itemDoubleClicked(const QModelIndex &index); void updated(const QModelIndex &index); void headerClicked(int level); void setStartClosed(bool sc); void updateToPlayQueue(const QModelIndex &idx, bool replace); private: void doSearch(); void controlActions(); private: Action *renamePlaylistAction; Action *removeDuplicatesAction; Action *intitiallyCollapseAction; PlaylistsProxyModel proxy; }; #endif cantata-2.2.0/replaygain/000077500000000000000000000000001316350454000152625ustar00rootroot00000000000000cantata-2.2.0/replaygain/CMakeLists.txt000066400000000000000000000043651316350454000200320ustar00rootroot00000000000000if (FFMPEG_FOUND OR MPG123_FOUND) include_directories(${QTINCLUDES} ${ZLIB_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) if (FFMPEG_FOUND) set(CANTATA_RG_SRCS ${CANTATA_RG_SRCS} ffmpeginput.cpp) include_directories(${FFMPEG_INCLUDE_DIRS}) endif () if (MPG123_FOUND) set(CANTATA_RG_SRCS ${CANTATA_RG_SRCS} mpg123input.cpp) include_directories(${MPG123_INCLUDE_DIRS}) endif () set(CANTATA_RG_SRCS ${CANTATA_RG_SRCS} main.cpp replaygain.cpp trackscanner.cpp jobcontroller.cpp ../support/thread.cpp) set(CANTATA_RG_MOC_HDRS ${CANTATA_RG_MOC_HDRS} replaygain.h trackscanner.h jobcontroller.h ../support/thread.h) QT5_WRAP_CPP(CANTATA_RG_MOC_SRCS ${CANTATA_RG_MOC_HDRS}) if (WIN32) set(CMAKE_BUILD_TYPE "Release") ADD_EXECUTABLE(cantata-replaygain WIN32 ${CANTATA_RG_SRCS} ${CANTATA_RG_MOC_SRCS} ${CANTATA_PO}) install(TARGETS cantata-replaygain DESTINATION ${CMAKE_INSTALL_PREFIX}) elseif (APPLE) set(CMAKE_BUILD_TYPE "Release") ADD_EXECUTABLE(cantata-replaygain ${CANTATA_RG_SRCS} ${CANTATA_RG_MOC_SRCS} ${CANTATA_PO}) # install(TARGETS cantata-replaygain DESTINATION ${MACOSX_BUNDLE_APP_DIR}) else () ADD_EXECUTABLE(cantata-replaygain ${CANTATA_RG_SRCS} ${CANTATA_RG_MOC_SRCS}) install(TARGETS cantata-replaygain RUNTIME DESTINATION ${LINUX_LIB_DIR}/cantata) endif () find_package(Ebur128) if (EBUR128_FOUND) message("-- Using system libebur128") include_directories(${EBUR128_INCLUDE_DIR}) target_link_libraries(cantata-replaygain ${EBUR128_LIBRARY}) else () message("-- Using supplied libebur128") include_directories(${CMAKE_SOURCE_DIR}/3rdparty) add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/ebur128 ${CMAKE_BINARY_DIR}/3rdparty/ebur128) target_link_libraries(cantata-replaygain ebur128) endif () if (FFMPEG_FOUND) target_link_libraries(cantata-replaygain ${FFMPEG_LIBRARIES}) endif () if (MPG123_FOUND) target_link_libraries(cantata-replaygain ${MPG123_LIBRARIES}) endif () target_link_libraries(cantata-replaygain ${QTCORELIBS}) if (UNIX AND NOT APPLE) target_link_libraries(cantata-replaygain -lpthread) endif () endif () cantata-2.2.0/replaygain/albumscanner.cpp000066400000000000000000000070151316350454000204430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "albumscanner.h" #include "config.h" #include #include AlbumScanner::AlbumScanner(const QMap &files) : proc(0) { QMap::ConstIterator it=files.constBegin(); QMap::ConstIterator end=files.constEnd(); for (int i=0; it!=end; ++it, ++i) { fileNames.append(it.value()); trackIndexMap[i]=it.key(); } } AlbumScanner::~AlbumScanner() { stop(); } void AlbumScanner::start() { if (!proc) { proc=new QProcess(this); proc->setReadChannelMode(QProcess::MergedChannels); proc->setReadChannel(QProcess::StandardOutput); connect(proc, SIGNAL(finished(int)), this, SLOT(procFinished())); connect(proc, SIGNAL(readyReadStandardOutput()), this, SLOT(read())); proc->start(Utils::helper(QLatin1String("cantata-replaygain")), fileNames, QProcess::ReadOnly); } } void AlbumScanner::stop() { if (proc) { disconnect(proc, SIGNAL(finished(int)), this, SLOT(procFinished())); disconnect(proc, SIGNAL(readyReadStandardOutput()), this, SLOT(read())); proc->terminate(); proc->deleteLater(); proc=0; } } static const QString constProgLine=QLatin1String("PROGRESS: "); static const QString constTrackLine=QLatin1String("TRACK: "); static const QString constAlbumLine=QLatin1String("ALBUM: "); void AlbumScanner::read() { if (!proc) { return; } QString output = proc->readAllStandardOutput().data(); if (output.isEmpty()) { return; } QStringList lines=output.split("\n", QString::SkipEmptyParts); foreach (const QString &line, lines) { if (line.startsWith(constProgLine)) { emit progress(line.mid(constProgLine.length()).toUInt()); } else if (line.startsWith(constTrackLine)) { QStringList parts=line.mid(constTrackLine.length()).split(" ", QString::SkipEmptyParts); if (!parts.isEmpty()) { int num=parts[0].toUInt(); Values vals; if (parts.length()>=3) { vals.gain=parts[1].toDouble(); vals.peak=parts[2].toDouble(); vals.ok=true; } tracks[trackIndexMap[num]]=vals; } } else if (line.startsWith(constAlbumLine)) { QStringList parts=line.mid(constAlbumLine.length()).split(" ", QString::SkipEmptyParts); if (parts.length()>=2) { album.gain=parts[0].toDouble(); album.peak=parts[1].toDouble(); album.ok=true; } } } } void AlbumScanner::procFinished() { setFinished(true); emit done(); } cantata-2.2.0/replaygain/albumscanner.h000066400000000000000000000031531316350454000201070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _ALBUM_SCANNER_H_ #define _ALBUM_SCANNER_H_ #include "jobcontroller.h" #include #include class QProcess; class AlbumScanner : public Job { Q_OBJECT public: struct Values { Values() : gain(0.0), peak(0.0), ok(false) { } double gain; double peak; bool ok; }; AlbumScanner(const QMap &files); ~AlbumScanner(); virtual void start(); virtual void stop(); const Values & albumValues() const { return album; } const QMap trackValues() const { return tracks; } private Q_SLOTS: void read(); void procFinished(); private: QProcess *proc; Values album; QMap tracks; QMap trackIndexMap; QStringList fileNames; }; #endif cantata-2.2.0/replaygain/ffmpeginput.cpp000066400000000000000000000464601316350454000203240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This class is a C++/Qt version of input_ffmpeg.c from libebur128 */ /* See LICENSE file for copyright and license details. */ #ifdef __cplusplus #define __STDC_CONSTANT_MACROS #ifdef _STDINT_H #undef _STDINT_H #endif #include #endif #ifdef __cplusplus extern "C" { #endif #include #include #if LIBAVFORMAT_VERSION_MAJOR >= 54 #include #endif #ifdef __cplusplus } #endif #include #include #include #include #include #include "ebur128/ebur128.h" #include "ffmpeginput.h" static QMutex mutex; #ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio #endif #define BUFFER_SIZE ((((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2) * sizeof(int16_t)) + FF_INPUT_BUFFER_PADDING_SIZE) #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 0) #define AV_FREE(PKT) av_packet_unref(PKT) #else #define AV_FREE(PKT) av_free_packet(PKT) #endif struct FfmpegInput::Handle { Handle() : formatContext(0) , codecContext(0) , codec(0) #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) , frame(0) , gotFrame(0) , packetLeft(false) , flushing(false) #endif , audioStream(0) , currentBytes(0) { av_init_packet(&packet); audioBuffer = (int16_t*)av_malloc(BUFFER_SIZE); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 39, 101) frame = avcodec_alloc_frame(); #else frame = av_frame_alloc(); packet.data = NULL; origPacket.size = 0; origPacket.data=NULL; #endif #endif } ~Handle() { if (audioBuffer) { av_free(audioBuffer); } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) if (frame) { #if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(54, 23, 100) av_free(&frame); #elif LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 39, 101) avcodec_free_frame(&frame); #else av_frame_free(&frame); #endif } #endif } AVFormatContext *formatContext; AVCodecContext *codecContext; AVCodec *codec; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) AVFrame *frame; int gotFrame; bool packetLeft; bool flushing; AVPacket origPacket; #endif AVPacket packet; int audioStream; int16_t *audioBuffer; float buffer[BUFFER_SIZE / 2 + 1]; QList bufferList; size_t currentBytes; }; void FfmpegInput::init() { static int i=false; if (!i) { av_register_all(); av_log_set_level(AV_LOG_ERROR); i=true; } } FfmpegInput::FfmpegInput(const QString &fileName) { mutex.lock(); handle=new Handle; bool ok=true; #if LIBAVFORMAT_VERSION_MAJOR >= 54 || \ (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 2, 0)) || \ (LIBAVFORMAT_VERSION_MAJOR == 52 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 110, 0)) if (avformat_open_input(&handle->formatContext, QFile::encodeName(fileName).constData(), NULL, NULL) != 0) #else if (av_open_input_file(&handle->formatContext, QFile::encodeName(fileName).constData(), NULL, 0, NULL) != 0) #endif { mutex.unlock(); delete handle; handle=0; return; } #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 21, 0) if (ok && avformat_find_stream_info(handle->formatContext, 0) < 0) { ok=false; } #else if (ok && av_find_stream_info(handle->formatContext) < 0) { ok=false; } #endif // Find the first audio stream if (ok) { handle->audioStream = -1; for (size_t j = 0; j < handle->formatContext->nb_streams; ++j) { if (handle->formatContext->streams[j]->codec->codec_type == #if LIBAVCODEC_VERSION_MAJOR >= 53 || \ (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0)) AVMEDIA_TYPE_AUDIO #else CODEC_TYPE_AUDIO #endif ) { handle->audioStream = (int) j; break; } } if (-1==handle->audioStream) { ok=false; } } if (ok) { // Get a pointer to the codec context for the audio stream handle->codecContext = handle->formatContext->streams[handle->audioStream]->codec; // request float output if supported #if LIBAVCODEC_VERSION_MAJOR >= 54 handle->codecContext->request_sample_fmt = AV_SAMPLE_FMT_FLT; #elif(LIBAVCODEC_VERSION_MAJOR == 53 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 4, 0)) handle->codecContext->request_sample_fmt = SAMPLE_FMT_FLT; #endif // Find the decoder for the video stream, and open codec... handle->codec = avcodec_find_decoder(handle->codecContext->codec_id); QString floatCodec=QLatin1String(handle->codec->name)+QLatin1String("float"); AVCodec *possibleFloatCodec = avcodec_find_decoder_by_name(floatCodec.toLatin1().constData()); if (possibleFloatCodec) { handle->codec = possibleFloatCodec; } if (!handle->codec || #if LIBAVCODEC_VERSION_MAJOR >= 53 avcodec_open2(handle->codecContext, handle->codec, NULL) < 0) #else avcodec_open(handle->codecContext, handle->codec) < 0) #endif { ok=false; } } if (ok) { mutex.unlock(); } else { #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 21, 0) avformat_close_input(&handle->formatContext); #else av_close_input_file(handle->formatContext); #endif mutex.unlock(); delete handle; handle=0; } } FfmpegInput::~FfmpegInput() { if (handle) { mutex.lock(); avcodec_close(handle->codecContext); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 21, 0) avformat_close_input(&handle->formatContext); #else av_close_input_file(handle->formatContext); #endif mutex.unlock(); delete handle; handle=0; } } size_t FfmpegInput::totalFrames() const { if (!handle) { return 0; } double tmp = (double) handle->formatContext->streams[handle->audioStream]->duration * (double) handle->formatContext->streams[handle->audioStream]->time_base.num / (double) handle->formatContext->streams[handle->audioStream]->time_base.den * (double) handle->codecContext->sample_rate; return tmp<=0.0 ? 0 : (size_t) (tmp + 0.5); } unsigned int FfmpegInput::channels() const { return handle ? handle->codecContext->channels : 0; } unsigned long FfmpegInput::sampleRate() const { return handle ? handle->codecContext->sample_rate : 0; } float * FfmpegInput::buffer() const { return handle ? handle->buffer : 0; } bool FfmpegInput::setChannelMap(int *st) const { if (handle && handle->codecContext->channel_layout) { unsigned int mapIndex = 0; int bitCounter = 0; while (mapIndex < (unsigned) handle->codecContext->channels) { if (handle->codecContext->channel_layout & (1 << bitCounter)) { switch (1 << bitCounter) { #if LIBAVFORMAT_VERSION_MAJOR >= 54 case AV_CH_FRONT_LEFT: #else case CH_FRONT_LEFT: #endif st[mapIndex] = EBUR128_LEFT; break; #if LIBAVFORMAT_VERSION_MAJOR >= 54 case AV_CH_FRONT_RIGHT: #else case CH_FRONT_RIGHT: #endif st[mapIndex] = EBUR128_RIGHT; break; #if LIBAVFORMAT_VERSION_MAJOR >= 54 case AV_CH_FRONT_CENTER: #else case CH_FRONT_CENTER: #endif st[mapIndex] = EBUR128_CENTER; break; #if LIBAVFORMAT_VERSION_MAJOR >= 54 case AV_CH_BACK_LEFT: #else case CH_BACK_LEFT: #endif st[mapIndex] = EBUR128_LEFT_SURROUND; break; #if LIBAVFORMAT_VERSION_MAJOR >= 54 case AV_CH_BACK_RIGHT: #else case CH_BACK_RIGHT: #endif st[mapIndex] = EBUR128_RIGHT_SURROUND; break; default: st[mapIndex] = EBUR128_UNUSED; break; } ++mapIndex; } ++bitCounter; } return true; } return false; } size_t FfmpegInput::readFrames() { if (!handle || !channels()) { return 0; } size_t bufferPosition=0, numberRead=0; while (handle->currentBytes < BUFFER_SIZE) { numberRead = readOnePacket(); if (!numberRead) { break; } size_t bufferSize=numberRead * channels() * sizeof(float); handle->bufferList.append(QByteArray((const char *)(handle->buffer), bufferSize)); handle->currentBytes += bufferSize; } while (handle->bufferList.count() && handle->bufferList.first().size() + bufferPosition <= BUFFER_SIZE) { QByteArray b=handle->bufferList.takeAt(0); memcpy((char *) handle->buffer + bufferPosition, b.constData(), b.size()); bufferPosition += b.size(); handle->currentBytes -= b.size(); } return bufferPosition / sizeof(float) / channels(); } #if LIBAVCODEC_VERSION_MAJOR >= 54 static int decodePacket(FfmpegInput::Handle *handle) { int ret = avcodec_decode_audio4(handle->codecContext, handle->frame, &handle->gotFrame, &handle->packet); if (ret < 0) { return ret; } return FFMIN(ret, handle->packet.size); } size_t FfmpegInput::readOnePacket() { if (!handle) { return 0; } start: if (handle->flushing) { handle->packet.data = NULL; handle->packet.size = 0; decodePacket(handle); if (!handle->gotFrame) { return 0; } else { goto write_to_buffer; } } if (handle->packetLeft) { goto packetLeft; } //next_frame: for (;;) { if (av_read_frame(handle->formatContext, &handle->packet) < 0) { handle->flushing = true; goto start; } if (handle->packet.stream_index != handle->audioStream) { AV_FREE(&handle->packet); continue; } else { break; } } int ret; handle->origPacket = handle->packet; packetLeft: ret = decodePacket(handle); if (ret < 0) { goto free_packet; } handle->packet.data += ret; handle->packet.size -= ret; if (handle->packet.size > 0) { handle->packetLeft = true; } else { free_packet: AV_FREE(&handle->origPacket); handle->packetLeft = false; } if (!handle->gotFrame) { goto start; } write_to_buffer: ; size_t numberRead=handle->frame->nb_samples; /* TODO: fix this */ int numChannels = handle->codecContext->channels; // channels = handle->frame->channels; if (handle->frame->nb_samples * numChannels > (int)sizeof handle->buffer) { return 0; } switch (handle->codecContext->sample_fmt) { case AV_SAMPLE_FMT_S16: { int16_t *dataShort = (int16_t*)handle->frame->extended_data[0]; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { handle->buffer[i] = ((float) dataShort[i])/qMax(-(float) SHRT_MIN, (float) SHRT_MAX); } break; } case AV_SAMPLE_FMT_S32: { int32_t *dataInt = (int32_t*)handle->frame->extended_data[0]; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { handle->buffer[i] = ((float) dataInt[i])/qMax(-(float) INT_MIN, (float) INT_MAX); } break; } case AV_SAMPLE_FMT_FLT: { float *dataFloat = (float*)handle->frame->extended_data[0]; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { handle->buffer[i] = dataFloat[i]; } break; } case AV_SAMPLE_FMT_DBL: { double *dataDouble = (double*)handle->frame->extended_data[0]; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { handle->buffer[i] = (float)dataDouble[i]; } break; } case AV_SAMPLE_FMT_S16P: { uint8_t **ed = handle->frame->extended_data; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { int currentChannel = i / handle->frame->nb_samples; int currentSample = i % handle->frame->nb_samples; handle->buffer[currentSample * numChannels + currentChannel] = ((float)((int16_t*) ed[currentChannel])[currentSample])/qMax(-(float)SHRT_MIN, (float)SHRT_MAX); } break; } case AV_SAMPLE_FMT_S32P: { uint8_t **ed = handle->frame->extended_data; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { int currentChannel = i / handle->frame->nb_samples; int currentSample = i % handle->frame->nb_samples; handle->buffer[currentSample * numChannels + currentChannel] = ((float) ((int32_t*) ed[currentChannel])[currentSample])/qMax(-(float)INT_MIN, (float)INT_MAX); } break; } case AV_SAMPLE_FMT_FLTP: { uint8_t **ed = handle->frame->extended_data; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { int currentChannel = i / handle->frame->nb_samples; int currentSample = i % handle->frame->nb_samples; handle->buffer[currentSample * numChannels + currentChannel] = ((float*) ed[currentChannel])[currentSample]; } break; } case AV_SAMPLE_FMT_DBLP:{ uint8_t **ed = handle->frame->extended_data; for (int i = 0; i < handle->frame->nb_samples * numChannels; ++i) { int currentChannel = i / handle->frame->nb_samples; int currentSample = i % handle->frame->nb_samples; handle->buffer[currentSample * numChannels + currentChannel] = ((double*) ed[currentChannel])[currentSample]; } break; } case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_NONE: case AV_SAMPLE_FMT_NB: default: numberRead=0; goto out; } out: return numberRead; } #else #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) static int decodeAudio(FfmpegInput::Handle *handle, int *frame_size_ptr) { int ret, got_frame = 0; if (!handle->frame) { return AVERROR(ENOMEM); } #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55, 0, 0) if (handle->codecContext->get_buffer != avcodec_default_get_buffer) { handle->codecContext->get_buffer = avcodec_default_get_buffer; handle->codecContext->release_buffer = avcodec_default_release_buffer; } #endif ret = avcodec_decode_audio4(handle->codecContext, handle->frame, &got_frame, &handle->packet); if (ret >= 0 && got_frame) { int ch, plane_size; int planar = av_sample_fmt_is_planar(handle->codecContext->sample_fmt); int data_size = av_samples_get_buffer_size(&plane_size, handle->codecContext->channels, handle->frame->nb_samples, handle->codecContext->sample_fmt, 1); if (*frame_size_ptr < data_size) { return AVERROR(EINVAL); } memcpy(handle->audioBuffer, handle->frame->extended_data[0], plane_size); if (planar && handle->codecContext->channels > 1) { uint8_t *out = ((uint8_t *)(handle->audioBuffer)) + plane_size; for (ch = 1; ch < handle->codecContext->channels; ch++) { memcpy(out, handle->frame->extended_data[ch], plane_size); out += plane_size; } } *frame_size_ptr = data_size; } else { *frame_size_ptr = 0; } return ret; } #endif size_t FfmpegInput::readOnePacket() { if (!handle) { return 0; } next_frame: for (;;) { if (av_read_frame(handle->formatContext, &handle->packet) < 0) { return 0; } if (handle->packet.stream_index == handle->audioStream) { break; } AV_FREE(&handle->packet); } size_t numberRead=0; int dataSize=BUFFER_SIZE; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 35, 0) int len = decodeAudio(handle, &dataSize); #elif LIBAVCODEC_VERSION_MAJOR >= 53 || \ (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 23, 0)) int len = avcodec_decode_audio3(handle->codecContext, handle->audioBuffer, &dataSize, &handle->packet); #else int len = avcodec_decode_audio2(handle->codecContext, handle->audioBuffer, &dataSize, handle->packet.data, handle->packet.size); #endif if (len < 0) { goto out; } /* No data used, (happens with metadata frames for example) */ if (len <= 0) { AV_FREE(&handle->packet); goto next_frame; } switch (handle->codecContext->sample_fmt) { case SAMPLE_FMT_S16: { int16_t *dataShort = (int16_t*) handle->audioBuffer; numberRead = (size_t) dataSize / sizeof(int16_t) / (size_t) handle->codecContext->channels; for (unsigned int i = 0; i < (size_t) dataSize / sizeof(int16_t); ++i) { handle->buffer[i] = ((float) dataShort[i]) / qMax(-(float) SHRT_MIN, (float) SHRT_MAX); } break; } case SAMPLE_FMT_S32: { int32_t *dataInt = (int32_t*) handle->audioBuffer; numberRead = (size_t) dataSize / sizeof(int32_t) / (size_t) handle->codecContext->channels; for (unsigned int i = 0; i < (size_t) dataSize / sizeof(int32_t); ++i) { handle->buffer[i] = ((float) dataInt[i]) / qMax(-(float) INT_MIN, (float) INT_MAX); } break; } case SAMPLE_FMT_FLT: { float *dataFloat = (float*) handle->audioBuffer; numberRead = (size_t) dataSize / sizeof(float) / (size_t) handle->codecContext->channels; for (unsigned int i = 0; i < (size_t) dataSize / sizeof(float); ++i) { handle->buffer[i] = dataFloat[i]; } break; } case SAMPLE_FMT_DBL: { double *dataDouble = (double*) handle->audioBuffer; numberRead = (size_t) dataSize / sizeof(double) / (size_t) handle->codecContext->channels; for (unsigned int i = 0; i < (size_t) dataSize / sizeof(double); ++i) { handle->buffer[i] = (float) dataDouble[i]; } break; } case SAMPLE_FMT_U8: case SAMPLE_FMT_NONE: case SAMPLE_FMT_NB: default: numberRead=0; goto out; } out: AV_FREE(&handle->packet); return numberRead; } #endif bool FfmpegInput::isFloatCodec() const { return handle && handle->codec && QString(QLatin1String(handle->codec->name)).endsWith(QLatin1String("float")); } cantata-2.2.0/replaygain/ffmpeginput.h000066400000000000000000000013521316350454000177600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This class is a C++/Qt version of input_ffmpeg.c from libebur128 */ #ifndef _FFMPEG_INPUT_H_ #define _FFMPEG_INPUT_H_ #include "input.h" class FfmpegInput : public Input { public: struct Handle; static void init(); FfmpegInput(const QString &fileName); ~FfmpegInput(); operator bool() const { return 0!=handle; } size_t totalFrames() const; unsigned int channels() const; unsigned long sampleRate() const; float * buffer() const; bool setChannelMap(int *st) const; size_t readFrames(); bool isFloatCodec() const; private: size_t readOnePacket(); private: Handle *handle; }; #endif cantata-2.2.0/replaygain/input.h000066400000000000000000000012261316350454000165730ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This class is a C++/Qt version of input_mpg123.c from libebur128 */ #ifndef _INPUT_H_ #define _INPUT_H_ class QString; class Input { public: Input() { } virtual ~Input() { } virtual operator bool() const=0; virtual size_t totalFrames() const=0; virtual unsigned int channels() const=0; virtual unsigned long sampleRate() const=0; virtual bool allocateBuffer() { return true; } virtual float * buffer() const=0; virtual bool setChannelMap(int *st) const=0; virtual size_t readFrames()=0; }; #endif cantata-2.2.0/replaygain/jobcontroller.cpp000066400000000000000000000050051316350454000206440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "jobcontroller.h" #include "support/thread.h" #include "support/globalstatic.h" Job::Job() : abortRequested(false) , finished(false) { } void Job::setFinished(bool f) { finished=f; emit done(); } StandardJob::StandardJob() : thread(0) { } void StandardJob::start() { if (!thread) { thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); connect(this, SIGNAL(exec()), this, SLOT(run()), Qt::QueuedConnection); emit exec(); } } void StandardJob::stop() { requestAbort(); if (thread) { thread->stop(); thread=0; } deleteLater(); } GLOBAL_STATIC(JobController, instance) JobController::JobController() : maxActive(1) { } void JobController::setMaxActive(int m) { maxActive=m; } void JobController::add(Job *job) { jobs.append(job); startJobs(); } void JobController::finishedWith(Job *job) { active.removeAll(job); jobs.removeAll(job); job->stop(); } void JobController::startJobs() { while (active.count()start(); } } void JobController::cancel() { foreach (Job *j, active) { disconnect(j, SIGNAL(done()), this, SLOT(jobDone())); j->stop(); } active.clear(); foreach (Job *j, jobs) { j->deleteLater(); } jobs.clear(); } void JobController::jobDone() { QObject *s=sender(); if (s && dynamic_cast(s)) { active.removeAll(static_cast(s)); } startJobs(); } cantata-2.2.0/replaygain/jobcontroller.h000066400000000000000000000036501316350454000203150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef JOB_CONTROLLER #define JOB_CONTROLLER #include class Thread; class QProcess; class Job : public QObject { Q_OBJECT public: Job(); virtual ~Job() { } virtual void requestAbort() { abortRequested=true; } virtual void start()=0; virtual void stop()=0; void setFinished(bool f); bool success() { return finished; } Q_SIGNALS: void exec(); void progress(int); void done(); protected: bool abortRequested; bool finished; }; class StandardJob : public Job { Q_OBJECT public: StandardJob(); virtual ~StandardJob() { stop(); } virtual void start(); virtual void stop(); private Q_SLOTS: virtual void run() =0; private: Thread *thread; }; class JobController : public QObject { Q_OBJECT public: static JobController * self(); JobController(); void setMaxActive(int m); void add(Job *job); void finishedWith(Job *job); void startJobs(); void cancel(); private Q_SLOTS: void jobDone(); private: int maxActive; QList active; QList jobs; }; #endif cantata-2.2.0/replaygain/main.cpp000066400000000000000000000025301316350454000167120ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "replaygain.h" int main(int argc, char *argv[]) { if (argc<2) { printf("Usage: %s \n", argv[0]); return -1; } QStringList fileNames; for (int i=0; i * */ /* This class is a C++/Qt version of input_ffmpeg.c from libebur128 */ /* See LICENSE file for copyright and license details. */ #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif #include #include #include #include "ebur128/ebur128.h" #include "mpg123input.h" struct Mpg123Input::Handle { Handle() : mpg123(0) , rate(0) , channels(0) , encoding(0) , buffer(0) { } ~Handle() { if (buffer) { free(buffer); } } mpg123_handle *mpg123; long rate; int channels, encoding; float *buffer; }; void Mpg123Input::init() { static int i=false; if (!i) { mpg123_init(); } } Mpg123Input::Mpg123Input(const QString &fileName) { handle=new Handle; QByteArray fName=QFile::encodeName(fileName); bool ok=false; int result; handle->mpg123 = mpg123_new(NULL, &result); if (handle->mpg123 && MPG123_OK==mpg123_open(handle->mpg123, fName.constData()) && MPG123_OK==mpg123_getformat(handle->mpg123, &handle->rate, &handle->channels, &handle->encoding) && MPG123_OK==mpg123_format_none(handle->mpg123) && MPG123_OK==mpg123_format(handle->mpg123, handle->rate, handle->channels, MPG123_ENC_FLOAT_32)) { mpg123_close(handle->mpg123); if (MPG123_OK==mpg123_open(handle->mpg123, fName.constData()) && MPG123_OK==mpg123_getformat(handle->mpg123, &handle->rate, &handle->channels, &handle->encoding)) { ok=true; } } if (!ok) { if (handle->mpg123) { mpg123_close(handle->mpg123); mpg123_delete(handle->mpg123); handle->mpg123 = NULL; } delete handle; handle = 0; } } Mpg123Input::~Mpg123Input() { if (handle) { if (handle->mpg123) { mpg123_close(handle->mpg123); mpg123_delete(handle->mpg123); handle->mpg123 = NULL; } delete handle; handle = 0; } } size_t Mpg123Input::totalFrames() const { if (!handle) { return 0; } off_t length = mpg123_length(handle->mpg123); return MPG123_ERR==length ? 0 : (size_t) length; } unsigned int Mpg123Input::channels() const { return handle ? (unsigned int)handle->channels : 0; } unsigned long Mpg123Input::sampleRate() const { return handle ? (unsigned long)handle->rate : 0; } bool Mpg123Input::allocateBuffer() { if (!handle) { return false; } handle->buffer = (float*) malloc((size_t) handle->rate * (size_t) handle->channels * sizeof(float)); return handle->buffer; } float * Mpg123Input::buffer() const { return handle ? handle->buffer : 0; } bool Mpg123Input::setChannelMap(int *st) const { Q_UNUSED(st) return false; } size_t Mpg123Input::readFrames() { if (!handle) { return 0; } size_t numberRead; int result = mpg123_read(handle->mpg123, (unsigned char*) handle->buffer, (size_t) handle->rate * (size_t) handle->channels * sizeof(float), &numberRead); if (MPG123_OK!=result && MPG123_DONE!=result) { return 0; } numberRead /= (size_t) handle->channels * sizeof(float); return numberRead; } cantata-2.2.0/replaygain/mpg123input.h000066400000000000000000000013001316350454000175160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /* This class is a C++/Qt version of input_mpg123.c from libebur128 */ #ifndef _MPG123_INPUT_H_ #define _MPG123_INPUT_H_ #include "input.h" class Mpg123Input : public Input { struct Handle; public: static void init(); Mpg123Input(const QString &fileName); ~Mpg123Input(); operator bool() const { return 0!=handle; } size_t totalFrames() const; unsigned int channels() const; unsigned long sampleRate() const; bool allocateBuffer(); float * buffer() const; bool setChannelMap(int *st) const; size_t readFrames(); private: Handle *handle; }; #endif cantata-2.2.0/replaygain/replaygain.cpp000066400000000000000000000106641316350454000201300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "replaygain.h" #include "jobcontroller.h" #include #include // Work-around possible locale issues by forcing usage of '.' as separator static QString formatDouble(double d) { return QString::number(d, 'f', 10).replace(",", "."); } ReplayGain::ReplayGain(const QStringList &fileNames) : QObject(0) , files(fileNames) , lastProgress(-1) , totalScanned(0) { TrackScanner::init(); JobController::self()->setMaxActive(8); } ReplayGain::~ReplayGain() { clearScanners(); } void ReplayGain::scan() { for (int i=0; isetFile(files.at(index)); connect(s, SIGNAL(progress(int)), this, SLOT(scannerProgress(int))); connect(s, SIGNAL(done()), this, SLOT(scannerDone())); scanners.insert(index, s); JobController::self()->add(s); } void ReplayGain::clearScanners() { JobController::self()->cancel(); QMap::ConstIterator it(scanners.constBegin()); QMap::ConstIterator end(scanners.constEnd()); for (; it!=end; ++it) { it.value()->stop(); } scanners.clear(); toScan.clear(); tracks.clear(); } void ReplayGain::showProgress() { int finished=0; quint64 totalProgress=0; QMap::iterator it=tracks.begin(); QMap::iterator end=tracks.end(); for (; it!=end; ++it) { if ((*it).finished) { finished++; } totalProgress+=(*it).finished ? 100 : (*it).progress; } int progress=(totalProgress/files.count())+0.5; progress=(progress/5)*5; if (progress!=lastProgress) { lastProgress=progress; printf("PROGRESS: %02d\n", lastProgress); fflush(stdout); } } void ReplayGain::showResults() { QList okScanners; for (int i=0; iok()) { printf("TRACK: %d %s %s\n", i, formatDouble(TrackScanner::reference(s->results().loudness)).toLatin1().constData(), formatDouble(s->results().peakValue()).toLatin1().constData()); okScanners.append(s); } else { printf("TRACK: %d FAILED\n", i); } } if (okScanners.isEmpty()) { printf("ALBUM: FAILED\n"); } else { TrackScanner::Data album=TrackScanner::global(okScanners); printf("ALBUM: %s %s\n", formatDouble(TrackScanner::reference(album.loudness)).toLatin1().constData(), formatDouble(album.peak).toLatin1().constData()); } fflush(stdout); QCoreApplication::exit(0); } void ReplayGain::scannerProgress(int p) { TrackScanner *s=qobject_cast(sender()); if (!s) { return; } tracks[s->index()].progress=p; showProgress(); } void ReplayGain::scannerDone() { TrackScanner *s=qobject_cast(sender()); if (!s) { return; } Track &track=tracks[s->index()]; if (!track.finished) { track.finished=true; track.success=s->success(); track.progress=100; showProgress(); totalScanned++; } if (toScan.isEmpty()) { if (totalScanned==files.count()) { showResults(); } } else { int index=toScan.takeAt(0); createScanner(index); } } cantata-2.2.0/replaygain/replaygain.h000066400000000000000000000033131316350454000175660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _REPLAYGAIN_H_ #define _REPLAYGAIN_H_ #include #include #include #include #include "trackscanner.h" #include "config.h" class ReplayGain : public QObject { Q_OBJECT public: ReplayGain(const QStringList &fileNames); virtual ~ReplayGain(); public Q_SLOTS: void scan(); private: void createScanner(int index); void clearScanners(); void showProgress(); void showResults(); private Q_SLOTS: void scannerProgress(int p); void scannerDone(); private: struct Track { Track() : progress(0), finished(false), success(false) { } unsigned char progress; bool finished : 1; bool success : 1; }; QStringList files; QMap scanners; QList toScan; QMap tracks; int lastProgress; int totalScanned; }; #endif cantata-2.2.0/replaygain/rgdialog.cpp000066400000000000000000000510221316350454000175560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "rgdialog.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/device.h" #include "models/devicesmodel.h" #endif #include "gui/settings.h" #include "tags/tags.h" #include "tagreader.h" #include "support/utils.h" #include "support/messagebox.h" #include "support/monoicon.h" #include "jobcontroller.h" #include "widgets/basicitemdelegate.h" #include "support/action.h" #include "mpd-interface/cuefile.h" #include #include #include #include #include #include #include #include #include enum Columns { COL_ARTIST, COL_ALBUM, COL_TITLE, COL_ALBUMGAIN, COL_TRACKGAIN, COL_ALBUMPEAK, COL_TRACKPEAK }; static int iCount=0; int RgDialog::instanceCount() { return iCount; } static inline void setResizeMode(QHeaderView *hdr, int idx, QHeaderView::ResizeMode mode) { hdr->setSectionResizeMode(idx, mode); } RgDialog::RgDialog(QWidget *parent) : SongDialog(parent, "RgDialog", QSize(800, 400)) , state(State_Idle) , totalToScan(0) , tagReader(0) , autoScanTags(false) { iCount++; setButtons(User1|Ok|Cancel); setCaption(tr("ReplayGain")); setAttribute(Qt::WA_DeleteOnClose); QWidget *mainWidget = new QWidget(this); QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, mainWidget); combo = new QComboBox(this); view = new QTreeWidget(this); statusLabel = new QLabel(this); statusLabel->setVisible(false); progress = new QProgressBar(this); progress->setVisible(false); combo->addItem(tr("Show All Tracks"), true); combo->addItem(tr("Show Untagged Tracks"), false); view->setRootIsDecorated(false); view->setAllColumnsShowFocus(true); view->setItemDelegate(new BasicItemDelegate(view)); view->setAlternatingRowColors(false); view->setContextMenuPolicy(Qt::ActionsContextMenu); view->setSelectionMode(QAbstractItemView::ExtendedSelection); removeAct=new Action(tr("Remove From List"), view); removeAct->setEnabled(false); view->addAction(removeAct); QTreeWidgetItem *hdr = view->headerItem(); hdr->setText(COL_ARTIST, tr("Artist")); hdr->setText(COL_ALBUM, tr("Album")); hdr->setText(COL_TITLE, tr("Title")); hdr->setText(COL_ALBUMGAIN, tr("Album Gain")); hdr->setText(COL_TRACKGAIN, tr("Track Gain")); hdr->setText(COL_ALBUMPEAK, tr("Album Peak")); hdr->setText(COL_TRACKPEAK, tr("Track Peak")); QHeaderView *hv=view->header(); setResizeMode(hv, COL_ARTIST, QHeaderView::ResizeToContents); setResizeMode(hv, COL_ALBUM, QHeaderView::ResizeToContents); setResizeMode(hv, COL_TITLE, QHeaderView::Stretch); setResizeMode(hv, COL_ALBUMGAIN, QHeaderView::Fixed); setResizeMode(hv, COL_TRACKGAIN, QHeaderView::Fixed); setResizeMode(hv, COL_ALBUMPEAK, QHeaderView::Fixed); setResizeMode(hv, COL_TRACKPEAK, QHeaderView::Fixed); hv->setStretchLastSection(false); layout->setMargin(0); layout->addWidget(combo); layout->addWidget(view); layout->addWidget(statusLabel); layout->addWidget(progress); setMainWidget(mainWidget); setButtonGuiItem(Ok, StdGuiItem::save()); setButtonGuiItem(Cancel, StdGuiItem::close()); setButtonGuiItem(User1, GuiItem(tr("Scan"), FontAwesome::search)); enableButton(Ok, false); enableButton(User1, false); qRegisterMetaType("Tags::ReplayGain"); connect(combo, SIGNAL(currentIndexChanged(int)), SLOT(toggleDisplay())); connect(view, SIGNAL(itemSelectionChanged()), SLOT(controlRemoveAct())); connect(removeAct, SIGNAL(triggered()), SLOT(removeItems())); italic=font(); italic.setItalic(true); JobController::self()->setMaxActive(1); } RgDialog::~RgDialog() { clearScanners(); iCount--; } void RgDialog::show(const QList &songs, const QString &udi, bool autoScan) { if (songs.isEmpty()) { deleteLater(); return; } foreach (const Song &s, songs) { if (!CueFile::isCue(s.file)) { origSongs.append(s); } } if (origSongs.isEmpty()) { deleteLater(); return; } autoScanTags=autoScan; qSort(origSongs); #ifdef ENABLE_DEVICES_SUPPORT if (udi.isEmpty()) { base=MPDConnection::self()->getDetails().dir; } else { Device *dev=getDevice(udi, parentWidget()); if (!dev) { deleteLater(); return; } base=dev->path(); } #else base=MPDConnection::self()->getDetails().dir; #endif if (!songsOk(origSongs, base, udi.isEmpty())) { return; } state=State_Idle; enableButton(User1, origSongs.count()); view->clear(); foreach (const Song &s, origSongs) { new QTreeWidgetItem(view, QStringList() << s.albumArtist() << s.album << s.title); } Dialog::show(); startReadingTags(); } void RgDialog::slotButtonClicked(int button) { if (State_Saving==state) { return; } switch (button) { case Ok: if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Update ReplayGain tags in tracks?"), tr("Update Tags"), GuiItem(tr("Update Tags")), StdGuiItem::cancel())) { if (saveTags()) { stopScanning(); accept(); } } break; case User1: startScanning(); break; case Cancel: switch (state) { case State_ScanningFiles: if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Abort scanning of tracks?"), tr("Abort"), GuiItem(tr("Abort")), StdGuiItem::no())) { stopScanning(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); state=State_Idle; } break; case State_ScanningTags: if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Abort reading of existing tags?"), tr("Abort"), GuiItem(tr("Abort")), StdGuiItem::no())) { stopReadingTags(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); state=State_Idle; } break; default: case State_Idle: stopScanning(); reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; } break; default: break; } } void RgDialog::startScanning() { bool all=origTags.isEmpty() || (origTags.count()==origSongs.count() ? MessageBox::Yes==MessageBox::questionYesNo(this, tr("Scan all tracks?

    All tracks have existing ReplayGain tags."), QString(), GuiItem(tr("Scan")), StdGuiItem::cancel()) : MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to scan all tracks, or only tracks without existing tags?"), QString(), GuiItem(tr("Untagged Tracks")), GuiItem(tr("All Tracks")))); if (!all && origTags.count()==origSongs.count()) { return; } setButtonGuiItem(Cancel, StdGuiItem::cancel()); state=State_ScanningFiles; enableButton(Ok, false); enableButton(User1, false); progress->setValue(0); progress->setVisible(true); statusLabel->setText(tr("Scanning tracks...")); statusLabel->setVisible(true); clearScanners(); totalToScan=0; QMap > groupedTracks; for (int i=0; i >::ConstIterator it(groupedTracks.constBegin()); QMap >::ConstIterator end(groupedTracks.constEnd()); for (; it!=end; ++it) { createScanner(*it); totalToScan++; } progress->setRange(0, 100*totalToScan); } void RgDialog::stopScanning() { state=State_Idle; enableButton(User1, true); progress->setVisible(false); statusLabel->setVisible(false); JobController::self()->cancel(); clearScanners(); setButtonGuiItem(Cancel, StdGuiItem::close()); } void RgDialog::createScanner(const QList &indexes) { QMap fileMap; foreach (int i, indexes) { fileMap[i]=base+origSongs.at(i).filePath(); } AlbumScanner *s=new AlbumScanner(fileMap); connect(s, SIGNAL(progress(int)), this, SLOT(scannerProgress(int))); connect(s, SIGNAL(done()), this, SLOT(scannerDone())); scanners[s]=0; JobController::self()->add(s); } void RgDialog::clearScanners() { QList scannerList=scanners.keys(); foreach (AlbumScanner *sc, scannerList) { sc->stop(); } scanners.clear(); } void RgDialog::startReadingTags() { if (tagReader) { return; } setButtonGuiItem(Cancel, StdGuiItem::cancel()); state=State_ScanningTags; enableButton(Ok, false); enableButton(User1, false); progress->setRange(0, origSongs.count()); progress->setVisible(true); statusLabel->setText(tr("Reading existing tags...")); statusLabel->setVisible(true); tagReader=new TagReader(); tagReader->setDetails(origSongs, base); connect(tagReader, SIGNAL(progress(int, Tags::ReplayGain)), this, SLOT(songTags(int, Tags::ReplayGain))); connect(tagReader, SIGNAL(done()), this, SLOT(tagReaderDone())); JobController::self()->add(tagReader); } void RgDialog::stopReadingTags() { if (!tagReader) { return; } state=State_Idle; enableButton(User1, true); progress->setVisible(false); statusLabel->setVisible(false); disconnect(tagReader, SIGNAL(progress(int, Tags::ReplayGain)), this, SLOT(songTags(int, Tags::ReplayGain))); disconnect(tagReader, SIGNAL(done()), this, SLOT(tagReaderDone())); tagReader->requestAbort(); tagReader=0; autoScanTags=false; } bool RgDialog::saveTags() { state=State_Saving; enableButton(Ok, false); enableButton(Close, false); enableButton(Cancel, false); enableButton(User1, false); QStringList failed; progress->setVisible(true); progress->setRange(0, tagsToSave.count()); int count=0; bool someTimedout=false; QMap::ConstIterator it=tagsToSave.constBegin(); QMap::ConstIterator end=tagsToSave.constEnd(); for (; it!=end; ++it) { QString filePath=origSongs.at(it.key()).filePath(); switch (Tags::updateReplaygain(base+filePath, it.value())) { case Tags::Update_Failed: failed.append(filePath); break; case Tags::Update_BadFile: failed.append(tr("%1 (Corrupt tags?)", "filename (Corrupt tags?)").arg(filePath)); break; default: break; } progress->setValue(progress->value()+1); if (0==count++%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } } if (failed.count()) { MessageBox::errorListEx(this, tr("Failed to update the tags of the following tracks:"), failed); } return !someTimedout; } void RgDialog::updateView() { int finished=0; quint64 totalProgress=0; QMap::ConstIterator it=scanners.constBegin(); QMap::ConstIterator end=scanners.constEnd(); for (; it!=end; ++it) { if (100==it.value()) { finished++; } totalProgress+=(*it); } if (finished==totalToScan) { progress->setVisible(false); statusLabel->setVisible(false); state=State_Idle; setButtonGuiItem(Cancel, StdGuiItem::close()); enableButton(Ok, !tagsToSave.isEmpty()); } else { progress->setValue(totalProgress); } } #ifdef ENABLE_DEVICES_SUPPORT Device * RgDialog::getDevice(const QString &udi, QWidget *p) { Device *dev=DevicesModel::self()->device(udi); if (!dev) { MessageBox::error(p ? p : this, tr("Device has been removed!")); reject(); return 0; } if (!dev->isConnected()) { MessageBox::error(p ? p : this, tr("Device is not connected.")); reject(); return 0; } if (!dev->isIdle()) { MessageBox::error(p ? p : this, tr("Device is busy?")); reject(); return 0; } return dev; } #endif void RgDialog::scannerProgress(int p) { AlbumScanner *s=qobject_cast(sender()); if (!s) { return; } scanners[s]=p; updateView(); } void RgDialog::scannerDone() { AlbumScanner *s=qobject_cast(sender()); if (!s) { return; } const QMap &trackValues=s->trackValues(); QMap::ConstIterator it(trackValues.constBegin()); QMap::ConstIterator end(trackValues.constEnd()); if (s->success()) { for(; it!=end; ++it) { Tags::ReplayGain updatedTags(it.value().gain, s->albumValues().gain, it.value().peak, s->albumValues().peak); QTreeWidgetItem *item=view->topLevelItem(it.key()); if (it.value().ok) { item->setText(COL_TRACKGAIN, tr("%1 dB").arg(Utils::formatNumber(updatedTags.trackGain, 2))); item->setText(COL_TRACKPEAK, Utils::formatNumber(updatedTags.trackPeak, 6)); } else { item->setText(COL_TRACKGAIN, tr("Failed")); item->setText(COL_TRACKPEAK, tr("Failed")); } if (s->albumValues().ok) { item->setText(COL_ALBUMGAIN, tr("%1 dB").arg(Utils::formatNumber(updatedTags.albumGain, 2))); item->setText(COL_ALBUMPEAK, Utils::formatNumber(updatedTags.albumPeak, 6)); } else { item->setText(COL_ALBUMGAIN, tr("Failed")); item->setText(COL_ALBUMPEAK, tr("Failed")); } if (it.value().ok && origTags.contains(it.key())) { Tags::ReplayGain t=origTags[it.key()]; bool diff=false; bool diffAlbum=false; if (!Utils::equal(t.trackGain, updatedTags.trackGain, 0.01)) { item->setFont(COL_TRACKGAIN, italic); item->setToolTip(COL_TRACKGAIN, tr("Original: %1 dB").arg(Utils::formatNumber(t.trackGain, 2))); diff=true; } if (!Utils::equal(t.trackPeak, updatedTags.trackPeak, 0.000001)) { item->setFont(COL_TRACKPEAK, italic); item->setToolTip(COL_TRACKPEAK, tr("Original: %1").arg(Utils::formatNumber(t.trackPeak, 6))); diff=true; } if (!Utils::equal(t.albumGain, updatedTags.albumGain, 0.01)) { item->setFont(COL_ALBUMGAIN, italic); item->setToolTip(COL_ALBUMGAIN, tr("Original: %1 dB").arg(Utils::formatNumber(t.albumGain, 2))); diffAlbum=true; } if (!Utils::equal(t.albumPeak, updatedTags.albumPeak, 0.000001)) { item->setFont(COL_ALBUMPEAK, italic); item->setToolTip(COL_ALBUMPEAK, tr("Original: %1").arg(Utils::formatNumber(t.albumPeak, 6))); diffAlbum=true; } if (diff || diffAlbum) { if (diff) { item->setFont(COL_ARTIST, italic); item->setFont(COL_TITLE, italic); } if (diffAlbum) { item->setFont(COL_ALBUM, italic); } tagsToSave.insert(it.key(), updatedTags); } else { tagsToSave.remove(it.key()); } } else if (it.value().ok) { item->setFont(COL_ARTIST, italic); item->setFont(COL_ALBUM, italic); item->setFont(COL_TITLE, italic); item->setFont(COL_TRACKGAIN, italic); item->setFont(COL_TRACKPEAK, italic); item->setFont(COL_ALBUMGAIN, italic); item->setFont(COL_ALBUMPEAK, italic); tagsToSave.insert(it.key(), updatedTags); } else { tagsToSave.remove(it.key()); } } } else { for(; it!=end; ++it) { QTreeWidgetItem *item=view->topLevelItem(it.key()); item->setText(COL_TRACKGAIN, tr("Failed")); item->setText(COL_TRACKPEAK, tr("Failed")); item->setText(COL_ALBUMGAIN, tr("Failed")); item->setText(COL_ALBUMPEAK, tr("Failed")); tagsToSave.remove(it.key()); } } scanners[s]=100; updateView(); JobController::self()->finishedWith(s); } void RgDialog::songTags(int index, Tags::ReplayGain tags) { if (index>=0 && indexsetValue(progress->value()+1); if (!tags.isEmpty()) { origTags[index]=tags; QTreeWidgetItem *item=view->topLevelItem(index); if (!item) { return; } item->setText(COL_TRACKGAIN, tr("%1 dB").arg(Utils::formatNumber(tags.trackGain, 2))); item->setText(COL_TRACKPEAK, Utils::formatNumber(tags.trackPeak, 6)); item->setText(COL_ALBUMGAIN, tr("%1 dB").arg(Utils::formatNumber(tags.albumGain, 2))); item->setText(COL_ALBUMPEAK, Utils::formatNumber(tags.albumPeak, 6)); } } } void RgDialog::tagReaderDone() { TagReader *t=qobject_cast(sender()); if (!t) { return; } JobController::self()->finishedWith(t); tagReader=0; state=State_Idle; enableButton(User1, true); progress->setVisible(false); statusLabel->setVisible(false); if (autoScanTags) { autoScanTags=false; startScanning(); } } void RgDialog::toggleDisplay() { bool showAll=combo->itemData(combo->currentIndex()).toBool(); for (int i=0; itopLevelItemCount(); ++i) { QTreeWidgetItem *item=view->topLevelItem(i); if (!removedItems.contains(view->indexOfTopLevelItem(item))) { view->setItemHidden(item, showAll ? false : origTags.contains(i)); } } } void RgDialog::controlRemoveAct() { removeAct->setEnabled(view->topLevelItemCount()>1 && !view->selectedItems().isEmpty()); } void RgDialog::removeItems() { if (view->topLevelItemCount()<1) { return; } if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Remove the selected tracks from the list?"), tr("Remove Tracks"), StdGuiItem::remove(), StdGuiItem::cancel())) { QList selection=view->selectedItems(); foreach (QTreeWidgetItem *item, selection) { int index=view->indexOfTopLevelItem(item); view->setItemHidden(item, true); removedItems.insert(index); if (tagsToSave.contains(index)) { tagsToSave.remove(index); } } } } void RgDialog::closeEvent(QCloseEvent *event) { if (State_Idle!=state) { slotButtonClicked(Cancel); if (State_Idle!=state) { event->ignore(); } } else { Dialog::closeEvent(event); } } cantata-2.2.0/replaygain/rgdialog.h000066400000000000000000000053221316350454000172250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _RGDIALOG_H_ #define _RGDIALOG_H_ #include #include "widgets/songdialog.h" #include "albumscanner.h" #include "tags/tags.h" #include "config.h" class QComboBox; class QTreeWidget; class QLabel; class QProgressBar; #ifdef ENABLE_DEVICES_SUPPORT class Device; #endif class TagReader; class Action; class RgDialog : public SongDialog { Q_OBJECT public: static int instanceCount(); RgDialog(QWidget *parent); virtual ~RgDialog(); void show(const QList &songs, const QString &udi, bool autoScan=false); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void update(); private: void slotButtonClicked(int button); void startScanning(); void stopScanning(); void createScanner(const QList &indexes); void clearScanners(); void startReadingTags(); void stopReadingTags(); bool saveTags(); void updateView(); #ifdef ENABLE_DEVICES_SUPPORT Device * getDevice(const QString &udi, QWidget *p); #endif void closeEvent(QCloseEvent *event); private Q_SLOTS: void scannerProgress(int p); void scannerDone(); void songTags(int index, Tags::ReplayGain tags); void tagReaderDone(); void toggleDisplay(); void controlRemoveAct(); void removeItems(); private: enum State { State_Idle, State_ScanningTags, State_ScanningFiles, State_Saving }; QComboBox *combo; QTreeWidget *view; QLabel *statusLabel; QProgressBar *progress; Action *removeAct; State state; QString base; QList origSongs; QMap scanners; int totalToScan; QMap origTags; QMap tagsToSave; TagReader *tagReader; bool autoScanTags; QSet removedItems; QFont italic; }; #endif cantata-2.2.0/replaygain/tagreader.cpp000066400000000000000000000023321316350454000177240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tagreader.h" void TagReader::setDetails(const QList &s, const QString &dir) { songs=s; baseDir=dir; } void TagReader::run() { for(int i=0; i * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _TAGREADER_H_ #define _TAGREADER_H_ #include "jobcontroller.h" #include "mpd-interface/song.h" #include "tags/tags.h" class TagReader : public StandardJob { Q_OBJECT public: TagReader() { } virtual ~TagReader() { } void setDetails(const QList &s, const QString &dir); private: void run(); Q_SIGNALS: void progress(int index, Tags::ReplayGain); private: QList songs; QString baseDir; }; #endif cantata-2.2.0/replaygain/trackscanner.cpp000066400000000000000000000124461316350454000204530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "trackscanner.h" #include "config.h" #ifdef MPG123_FOUND #include "mpg123input.h" #endif #ifdef FFMPEG_FOUND #include "ffmpeginput.h" #endif #define RG_REFERENCE_LEVEL -18.0 double TrackScanner::clamp(double v) { return v < -51.0 ? -51.0 : v > 51.0 ? 51.0 : v; } double TrackScanner::reference(double v) { return clamp(RG_REFERENCE_LEVEL-v); } TrackScanner::Data TrackScanner::global(const QList &scanners) { Data d; if (scanners.count()<1) { return d; } else if(scanners.count()==1) { return scanners.first()->results(); } else { ebur128_state **states = new ebur128_state * [scanners.count()]; for (int i=0; istate; if (s->results().peak>d.peak) { d.peak=s->results().peak; } if (s->results().truePeak>d.truePeak) { d.truePeak=s->results().truePeak; } } double l=0.0; int rv=ebur128_loudness_global_multiple(states, scanners.count(), &l); delete [] states; d.loudness=0==rv ? l : 0.0; return d; } } void TrackScanner::init() { static bool doneInit=false; if (doneInit) { return; } doneInit=true; #ifdef MPG123_FOUND Mpg123Input::init(); #endif #ifdef FFMPEG_FOUND FfmpegInput::init(); #endif } TrackScanner::TrackScanner(int i) : idx(i) , state(0) , input(0) { } TrackScanner::~TrackScanner() { delete input; if (state) { ebur128_destroy(&state); state=0; } } void TrackScanner::setFile(const QString &fileName) { file=fileName; } void TrackScanner::run() { bool ffmpegIsFloat=false; #ifdef FFMPEG_FOUND FfmpegInput *ffmpeg=new FfmpegInput(file); if (*ffmpeg) { input=ffmpeg; ffmpegIsFloat=ffmpeg->isFloatCodec(); } else { delete ffmpeg; ffmpeg=0; } #endif #if MPG123_FOUND if (file.endsWith(".mp3", Qt::CaseInsensitive) && (!input || !ffmpegIsFloat)) { Mpg123Input *mpg123=new Mpg123Input(file); if (*mpg123) { input=mpg123; #ifdef FFMPEG_FOUND if (ffmpeg) { delete ffmpeg; } #endif } else { delete mpg123; } } #endif if (!input) { setFinishedStatus(false); return; } state=ebur128_init(input->channels(), input->sampleRate(), EBUR128_MODE_M|EBUR128_MODE_I|EBUR128_MODE_SAMPLE_PEAK); int *channelMap=new int [state->channels]; if (input->setChannelMap(channelMap)) { for (unsigned int i = 0; i < state->channels; ++i) { ebur128_set_channel(state, i, channelMap[i]); } } delete [] channelMap; //if (1==state->channels && opts->force_dual_mono) { // ebur128_set_channel(state, 0, EBUR128_DUAL_MONO); //} size_t numFramesRead=0; size_t totalRead=0; input->allocateBuffer(); while ((numFramesRead = input->readFrames())) { if (abortRequested) { setFinishedStatus(false); return; } totalRead+=numFramesRead; emit progress((int)((totalRead*100.0/input->totalFrames())+0.5)); if (ebur128_add_frames_float(state, input->buffer(), numFramesRead)) { setFinishedStatus(false); return; } } if (abortRequested) { setFinishedStatus(false); return; } ebur128_loudness_global(state, &data.loudness); // if (opts->lra) { // result = ebur128_loudness_range(ebur, &lra); // if (result) abort(); // } if (EBUR128_MODE_SAMPLE_PEAK==(state->mode & EBUR128_MODE_SAMPLE_PEAK)) { for (unsigned i = 0; i < state->channels; ++i) { double sp; ebur128_sample_peak(state, i, &sp); if (sp > data.peak) { data.peak = sp; } } } if (EBUR128_MODE_TRUE_PEAK==(state->mode & EBUR128_MODE_TRUE_PEAK)) { for (unsigned i = 0; i < state->channels; ++i) { double tp; ebur128_true_peak(state, i, &tp); if (tp > data.truePeak) { data.truePeak = tp; } } } setFinishedStatus(true); } void TrackScanner::setFinishedStatus(bool f) { delete input; input=0; setFinished(f); } cantata-2.2.0/replaygain/trackscanner.h000066400000000000000000000035331316350454000201150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _SCANNER_H_ #define _SCANNER_H_ #include "jobcontroller.h" #include "ebur128/ebur128.h" class Input; class TrackScanner : public StandardJob { Q_OBJECT public: struct Data { Data() : loudness(0.0) , peak(0.0) , truePeak(0.0) { } double peakValue() const { return truePeak>peak ? truePeak : peak; } double loudness; double peak; double truePeak; }; static Data global(const QList &scanners); static double clamp(double v); static double reference(double v); static void init(); TrackScanner(int i); ~TrackScanner(); void setFile(const QString &fileName); const Data & results() const { return data; } int index() const { return idx; } bool ok() const { return data.peakValue()>0.00001; } private: void run(); void setFinishedStatus(bool f); private: int idx; ebur128_state *state; Data data; QString file; Input *input; }; #endif cantata-2.2.0/scrobbling/000077500000000000000000000000001316350454000152535ustar00rootroot00000000000000cantata-2.2.0/scrobbling/pausabletimer.cpp000066400000000000000000000023121316350454000206120ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "pausabletimer.h" PausableTimer::PausableTimer() : QTimer() , timePaused(0) { setInterval(0); } void PausableTimer::start() { elapsedTimer.start(); QTimer::start(interval()); } void PausableTimer::pause() { if (isActive()) { stop(); timePaused+=elapsedTimer.elapsed(); setInterval(interval() - timePaused); } } cantata-2.2.0/scrobbling/pausabletimer.h000066400000000000000000000021431316350454000202610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PAUSABLETIMER_H #define PAUSABLETIMER_H #include #include class PausableTimer : public QTimer { public: PausableTimer(); void start(); void pause(); private: QElapsedTimer elapsedTimer; qint64 timePaused; }; #endif cantata-2.2.0/scrobbling/scrobbler.cpp000066400000000000000000000711361316350454000177440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * This file contains code from QMPDClient: * Copyright (C) 2005-2008 Håvard Tautra Knutsen * Copyright (C) 2009 Voker57 * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "scrobbler.h" #include "pausabletimer.h" #include "config.h" #include "gui/covers.h" #include "network/networkaccessmanager.h" #include "mpd-interface/mpdconnection.h" #include "support/globalstatic.h" #include "support/utils.h" #include "support/configuration.h" #include "qtiocompressor/qtiocompressor.h" #include #include #include #include #include #include #include #include #include static bool debugIsEnabled=false; static bool fakeScrobbling=false; #define DBUG if (debugIsEnabled) qWarning() << metaObject()->className() << __FUNCTION__ #define DBUG_CLASS(C) if (debugIsEnabled) qWarning() << C << __FUNCTION__ void Scrobbler::enableDebug() { debugIsEnabled=true; } const QLatin1String Scrobbler::constCacheDir("scrobbling"); const QLatin1String Scrobbler::constCacheFile("tracks.xml.gz"); static const QLatin1String constSettingsGroup("Scrobbling"); static const QString constSecretKey=QLatin1String("0753a75ccded9b17b872744d4bb60b35"); static const int constMaxBatchSize=50; static const int constNowPlayingInterval=5000; GLOBAL_STATIC(Scrobbler, instance) enum LastFmErrors { NoError = 0, // ?? InvalidService = 2, InvalidMethod, AuthenticationFailed, InvalidFormat, InvalidParameters, InvalidResourceSpecified, OperationFailed, InvalidSessionKey, InvalidApiKey, ServiceOffline, TokenNotAuthorised = 14, TryAgainLater = 16, RateLimitExceeded = 29 }; static QString errorString(int code, const QString &msg) { switch (code) { case InvalidService: return QObject::tr("Invalid service"); case InvalidMethod: return QObject::tr("Invalid method"); case AuthenticationFailed: return QObject::tr("Authentication failed"); case InvalidFormat: return QObject::tr("Invalid format"); case InvalidParameters: return QObject::tr("Invalid parameters"); case InvalidResourceSpecified: return QObject::tr("Invalid resource specified"); case OperationFailed: return QObject::tr("Operation failed"); case InvalidSessionKey: return QObject::tr("Invalid session key"); case InvalidApiKey: return QObject::tr("Invalid API key"); case ServiceOffline: return QObject::tr("Service offline"); case TryAgainLater: return QObject::tr("Last.fm is currently busy, please try again in a few minutes"); case RateLimitExceeded: return QObject::tr("Rate-limit exceeded"); default: return msg.isEmpty() ? QObject::tr("Unknown error") : msg.trimmed(); } } static QString md5(const QString &s) { return QString::fromLatin1(QCryptographicHash::hash(s.toUtf8(), QCryptographicHash::Md5).toHex()); } static void sign(QMap ¶ms) { QString s; params[QLatin1String("api_key")] = Covers::constLastFmApiKey; QStringList keys=params.keys(); keys.sort(); foreach (const QString &k, keys) { s += k+params[k]; } s += constSecretKey; params[QLatin1String("api_sig")] = md5(s); } static QByteArray format(const QMap ¶ms) { QByteArray data; QMapIterator i(params); while (i.hasNext()) { i.next(); data+=i.key().toLatin1()+'='+QUrl::toPercentEncoding(i.value()); if (i.hasNext()) { data+='&'; } } DBUG_CLASS("Scrobbler") << data; return data; } static QString cacheName(bool createDir) { QString dir=Utils::cacheDir(Scrobbler::constCacheDir, createDir); return dir.isEmpty() ? QString() : (dir+Scrobbler::constCacheFile); } Scrobbler::Track::Track(const Song &s) { if (!(s.blank&Song::BlankTitle)) { title=s.title; } if (!(s.blank&Song::BlankArtist)) { artist=s.artist; } if (!(s.blank&Song::BlankAlbum)) { album=s.album; } albumartist=s.albumartist; track=s.track; length=s.time; timestamp=0; } Scrobbler::Scrobbler() : QObject(0) , scrobblingEnabled(false) , loveIsEnabled(false) , scrobbler("Last.fm") , lastNowPlaying(0) , nowPlayingIsPending(false) , lovePending(false) , lastScrobbleFailed(false) , nowPlayingSent(false) , loveSent(false) , scrobbledCurrent(false) , scrobbleViaMpd(false) , failedCount(0) , lastState(MPDState_Inactive) , authJob(0) , scrobbleJob(0) { hardFailTimer = new QTimer(this); hardFailTimer->setInterval(60*1000); hardFailTimer->setSingleShot(true); scrobbleTimer = new PausableTimer(); scrobbleTimer->setInterval(9000000); // fakeScrobblinghuge number to avoid scrobbling just started song start (rare case) scrobbleTimer->setSingleShot(true); nowPlayingTimer = new PausableTimer(); nowPlayingTimer->setSingleShot(true); nowPlayingTimer->setInterval(constNowPlayingInterval); retryTimer = new QTimer(this); retryTimer->setSingleShot(true); retryTimer->setInterval(10000); connect(scrobbleTimer, SIGNAL(timeout()), this, SLOT(scrobbleCurrent())); connect(retryTimer, SIGNAL(timeout()), this, SLOT(scrobbleQueued())); connect(nowPlayingTimer, SIGNAL(timeout()), this, SLOT(scrobbleNowPlaying())); connect(hardFailTimer, SIGNAL(timeout()), this, SLOT(authenticate())); loadSettings(); connect(this, SIGNAL(clientMessage(QString,QString,QString)), MPDConnection::self(), SLOT(sendClientMessage(QString,QString,QString))); connect(MPDConnection::self(), SIGNAL(clientMessageFailed(QString,QString)), SLOT(clientMessageFailed(QString,QString))); connect(MPDConnection::self(), SIGNAL(statusUpdated(MPDStatusValues)), this, SLOT(mpdStatusUpdated(MPDStatusValues))); connect(MPDConnection::self(), SIGNAL(currentSongUpdated(Song)), this, SLOT(setSong(Song))); } Scrobbler::~Scrobbler() { DBUG; stop(); } void Scrobbler::stop() { cancelJobs(); saveCache(); } void Scrobbler::setActive() { if (isEnabled()) { loadCache(); } else { reset(); } if (isEnabled() || scrobbleViaMpd) { connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(mpdStateUpdated())); } else { disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(mpdStateUpdated())); } if (isEnabled() && !inactiveSong.isEmpty()) { Song s; s.title=inactiveSong.title; s.artist=inactiveSong.artist; s.albumartist=inactiveSong.albumartist; s.album=inactiveSong.album; s.track=inactiveSong.track; s.time=inactiveSong.length; setSong(s); inactiveSong.clear(); } if (!isAuthenticated()) { authenticate(); } else if (!songQueue.isEmpty()) { scrobbleQueued(); } } static const QLatin1String constFakeScrobbling("fake"); void Scrobbler::loadSettings() { Configuration cfg(constSettingsGroup); userName=cfg.get("userName", userName); // password=cfg.get("password", password); sessionKey=cfg.get("sessionKey", sessionKey); scrobblingEnabled=cfg.get("enabled", scrobblingEnabled); loveIsEnabled=cfg.get("loveEnabled", loveIsEnabled); scrobbler=cfg.get("scrobbler", scrobbler); scrobbleViaMpd=viaMpd(scrobblerUrl()); fakeScrobbling=constFakeScrobbling==scrobbler; if (fakeScrobbling) { sessionKey=constFakeScrobbling; } DBUG << scrobbler << userName << sessionKey.isEmpty() << scrobblingEnabled; emit authenticated(isAuthenticated()); emit enabled(isEnabled()); emit loveEnabled(loveIsEnabled); setActive(); } void Scrobbler::setDetails(const QString &s, const QString &u, const QString &p) { if (fakeScrobbling) { return; } if (u!=scrobbler || u!=userName || p!=password) { DBUG << "details changed"; Configuration cfg(constSettingsGroup); scrobbler=s; userName=u; password=p; sessionKey=QString(); reset(); cfg.set("scrobbler", scrobbler); cfg.set("userName", userName); cfg.set("sessionKey", sessionKey); scrobbleViaMpd=viaMpd(scrobblerUrl()); // cfg.set("password", password); setActive(); if (!isEnabled()) { emit authenticated(false); } emit scrobblerChanged(); } else if (!isAuthenticated() && haveLoginDetails()) { authenticate(); } else if (!scrobbleViaMpd) { DBUG << "details NOT changed"; emit authenticated(isAuthenticated()); } } void Scrobbler::love() { lovePending=false; if (!loveIsEnabled) { return; } const Track &song=isEnabled() ? currentSong : inactiveSong; if (song.title.isEmpty() || song.artist.isEmpty() || loveSent) { return; } if (scrobbleViaMpd) { emit clientMessage(scrobblerUrl(), QLatin1String("love"), scrobbler); loveSent=true; return; } if (!ensureAuthenticated()) { lovePending=true; return; } QMap params; params["method"] = "track.love"; params["track"] = song.title; params["artist"] = song.artist; params["sk"] = sessionKey; sign(params); DBUG << song.title << song.artist; loveSent=true; if (fakeScrobbling) { DBUG << "MSG" << params; } else { QNetworkReply *job=NetworkAccessManager::self()->postFormData(QUrl(scrobblerUrl()), format(params)); connect(job, SIGNAL(finished()), this, SLOT(handleResp())); } } void Scrobbler::setEnabled(bool e) { if (e!=scrobblingEnabled) { scrobblingEnabled=e; Configuration(constSettingsGroup).set("enabled", scrobblingEnabled); setActive(); emit enabled(e); } } void Scrobbler::setLoveEnabled(bool e) { if (e!=loveIsEnabled) { loveIsEnabled=e; Configuration(constSettingsGroup).set("loveEnabled", loveIsEnabled); setActive(); emit loveEnabled(e); } } void Scrobbler::calcScrobbleIntervals() { int elapsed=MPDStatus::self()->timeElapsed()*1000; if (elapsed<0) { elapsed=0; } int nowPlayingTimemout=constNowPlayingInterval; if (elapsed>4000) { nowPlayingTimemout=10; } else { nowPlayingTimemout-=elapsed; } nowPlayingTimer->setInterval(nowPlayingTimemout); int timeout=qMin(currentSong.length/2, (quint32)240)*1000; // Scrobble at 1/2 way point or 4 mins - whichever comes first! DBUG << "timeout" << timeout << elapsed << nowPlayingTimemout; if (timeout>elapsed) { timeout-=elapsed; } else { timeout=100; } scrobbleTimer->setInterval(timeout); } void Scrobbler::setSong(const Song &s) { DBUG << isEnabled() << s.isStandardStream() << s.time << s.file << s.title << s.artist << s.album << s.albumartist << s.blank; if (!scrobbleViaMpd && !isEnabled()) { if (inactiveSong.artist != s.artist || inactiveSong.title!=s.title || inactiveSong.album!=s.album) { emit songChanged(!s.isStandardStream() && !s.isEmpty()); } inactiveSong=Track(s); return; } inactiveSong.clear(); if (currentSong.artist != s.artist || currentSong.title!=s.title || currentSong.album!=s.album) { nowPlayingSent=scrobbledCurrent=loveSent=lovePending=nowPlayingIsPending=false; currentSong=Track(s); lastNowPlaying=0; emit songChanged(!s.isStandardStream() && !s.isEmpty()); if (scrobbleViaMpd || !isEnabled() || s.isStandardStream() || s.time<30) { return; } calcScrobbleIntervals(); if (MPDState_Playing==MPDStatus::self()->state()) { mpdStateUpdated(true); } } } void Scrobbler::scrobbleNowPlaying() { nowPlayingIsPending=false; if (!ensureAuthenticated()) { nowPlayingIsPending=true; return; } if (currentSong.title.isEmpty() || currentSong.artist.isEmpty() || nowPlayingSent || scrobbleViaMpd) { return; } QMap params; params["method"] = "track.updateNowPlaying"; params["track"] = currentSong.title; if (!currentSong.album.isEmpty()) { params["album"] = currentSong.album; } params["artist"] = currentSong.artist; if (!currentSong.albumartist.isEmpty() && currentSong.albumartist!=currentSong.artist) { params["albumArtist"] = currentSong.albumartist; } if (currentSong.track) { params["trackNumber"] = QString::number(currentSong.track); } if (currentSong.length) { params["duration"] = QString::number(currentSong.length); } params["sk"] = sessionKey; sign(params); DBUG << currentSong.title << currentSong.artist << currentSong.albumartist << currentSong.album << currentSong.track << currentSong.length; nowPlayingSent=true; lastNowPlaying=time(NULL); if (fakeScrobbling) { DBUG << "MSG" << params; } else { QNetworkReply *job=NetworkAccessManager::self()->postFormData(QUrl(scrobblerUrl()), format(params)); connect(job, SIGNAL(finished()), this, SLOT(handleResp())); } } void Scrobbler::handleResp() { QNetworkReply *job=qobject_cast(sender()); if (!job) { return; } job->deleteLater(); DBUG << job->readAll(); } void Scrobbler::scrobbleCurrent() { if (!scrobbledCurrent) { if (songQueue.isEmpty() || songQueue.last()!=currentSong) { songQueue.enqueue(currentSong); } scrobbledCurrent=true; } scrobbleQueued(); } void Scrobbler::scrobbleQueued() { if (!scrobblingEnabled || scrobbleViaMpd) { return; } if (!ensureAuthenticated() || scrobbleJob) { if (!retryTimer->isActive()) { retryTimer->start(); } return; } if (!songQueue.isEmpty()) { QMap params; params["method"] = "track.scrobble"; int batchSize=qMin(constMaxBatchSize, songQueue.size()); DBUG << "queued:" << songQueue.size() << "batchSize:" << batchSize; for (int i=0; ipostFormData(scrobblerUrl(), format(params)); connect(scrobbleJob, SIGNAL(finished()), this, SLOT(scrobbleFinished())); } } } void Scrobbler::scrobbleFinished() { QNetworkReply *job=qobject_cast(sender()); if (!job) { return; } job->deleteLater(); if (job==scrobbleJob) { QByteArray data=job->readAll(); DBUG << job->errorString() << data << songQueue.size() << lastScrobbledSongs.size(); scrobbleJob=0; int errorCode=NoError; QXmlStreamReader reader(data); while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { if (QLatin1String("lfm")==reader.name().toString()) { QString status=reader.attributes().value("status").toString().toLower(); DBUG << status; if (QLatin1String("failed")==status) { while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { if (QLatin1String("error")==reader.name().toString()) { errorCode=reader.attributes().value(QLatin1String("code")).toString().toInt(); QString errorStr=errorString(errorCode, reader.readElementText()); emit error(tr("%1 error: %2").arg(scrobbler).arg(errorStr)); DBUG << errorStr; break; } } } } break; } } } switch (errorCode) { case NoError: failedCount=0; DBUG << "Scrobble succeeded"; lastScrobbledSongs.clear(); return; case AuthenticationFailed: case InvalidSessionKey: case TokenNotAuthorised: sessionKey.clear(); authenticate(); failedCount=0; break; default: if (++failedCount > 2 && !hardFailTimer->isActive()) { sessionKey.clear(); hardFailTimer->setInterval((failedCount > 120 ? 120 : failedCount)*60*1000); hardFailTimer->start(); } break; } DBUG << "Move last scrobbled into queued"; songQueue << lastScrobbledSongs; lastScrobbledSongs.clear(); } } bool Scrobbler::ensureAuthenticated() { if (fakeScrobbling || !sessionKey.isEmpty()) { return true; } authenticate(); return false; } void Scrobbler::authenticate() { if (fakeScrobbling) { return; } if (hardFailTimer->isActive() || authJob || scrobbleViaMpd) { DBUG << "authentication delayed"; return; } if (!haveLoginDetails()) { DBUG << "no login details"; return; } QUrl url(scrobblerUrl()); QMap params; params["method"] = "auth.getMobileSession"; params["username"] = userName; bool supportsSsl = false; #ifndef QT_NO_SSL supportsSsl = QSslSocket::supportsSsl(); #endif if (supportsSsl) { params["password"] = password; url.setScheme("https"); // Use HTTPS to authenticate } else { params["authToken"]=md5(userName+md5(password)); } sign(params); authJob=NetworkAccessManager::self()->postFormData(url, format(params)); connect(authJob, SIGNAL(finished()), this, SLOT(authResp())); DBUG << url.toString(); } void Scrobbler::authResp() { QNetworkReply *job=qobject_cast(sender()); if (!job) { return; } job->deleteLater(); if (job!=authJob) { return; } authJob=0; sessionKey.clear(); QByteArray data=job->readAll(); DBUG << data; QXmlStreamReader reader(data); while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { QString element = reader.name().toString(); if (QLatin1String("session")==element) { while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); if (reader.isStartElement()) { element = reader.name().toString(); if (QLatin1String("key")==element) { sessionKey = reader.readElementText(); break; } } } break; } else if (QLatin1String("error")==element) { int code=reader.attributes().value(QLatin1String("code")).toString().toInt(); emit error(tr("%1 error: %2").arg(scrobbler).arg(errorString(code, reader.readElementText()))); break; } } } DBUG << "authenticated:" << !sessionKey.isEmpty(); emit authenticated(isAuthenticated()); Configuration cfg(constSettingsGroup); cfg.set("sessionKey", sessionKey); if (isAuthenticated()) { if (nowPlayingIsPending) { scrobbleNowPlaying(); } if (lovePending) { love(); } } } void Scrobbler::loadCache() { QString fileName=cacheName(false); if (fileName.isEmpty()) { return; } QFile file(fileName); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::ReadOnly)) { QXmlStreamReader reader(&compressor); while (!reader.atEnd()) { reader.readNext(); if (reader.isStartElement() && QLatin1String("track")==reader.name()) { Track t; t.artist = reader.attributes().value(QLatin1String("artist")).toString(); t.album = reader.attributes().value(QLatin1String("album")).toString(); t.albumartist = reader.attributes().value(QLatin1String("albumartist")).toString(); t.title = reader.attributes().value(QLatin1String("title")).toString(); t.track = reader.attributes().value(QLatin1String("track")).toString().toUInt(); t.length = reader.attributes().value(QLatin1String("length")).toString().toUInt(); t.timestamp = reader.attributes().value(QLatin1String("timestamp")).toString().toUInt(); songQueue.append(t); } } } DBUG << fileName << songQueue.size(); } void Scrobbler::saveCache() { QString fileName=cacheName(true); DBUG << fileName << lastScrobbledSongs.count() << songQueue.count(); if (fileName.isEmpty()) { return; } if (lastScrobbledSongs.isEmpty() && songQueue.isEmpty()) { if (QFile::exists(fileName)) { QFile::remove(fileName); } return; } QFile file(fileName); QtIOCompressor compressor(&file); compressor.setStreamFormat(QtIOCompressor::GzipFormat); if (compressor.open(QIODevice::WriteOnly)) { QXmlStreamWriter writer(&compressor); writer.setAutoFormatting(false); writer.writeStartDocument(); writer.writeStartElement("tracks"); writer.writeAttribute("version", "1"); const QQueue &queue=lastScrobbledSongs.isEmpty() ? songQueue : lastScrobbledSongs; foreach (const Track &t, queue) { writer.writeEmptyElement("track"); writer.writeAttribute(QLatin1String("artist"), t.artist); writer.writeAttribute(QLatin1String("album"), t.album); writer.writeAttribute(QLatin1String("albumartist"), t.albumartist); writer.writeAttribute(QLatin1String("title"), t.title); writer.writeAttribute(QLatin1String("track"), QString::number(t.track)); writer.writeAttribute(QLatin1String("length"), QString::number(t.length)); writer.writeAttribute(QLatin1String("timestamp"), QString::number(t.timestamp)); } writer.writeEndElement(); writer.writeEndDocument(); } } void Scrobbler::mpdStateUpdated(bool songChanged) { if (isEnabled() && !scrobbleViaMpd) { DBUG << songChanged << lastState << MPDStatus::self()->state(); bool stateChange=songChanged || lastState!=MPDStatus::self()->state(); bool isRepeat=!stateChange && MPDState_Playing==MPDStatus::self()->state() && MPDStatus::self()->timeElapsed()>=0 && MPDStatus::self()->timeElapsed()<2; if (!stateChange && !isRepeat) { return; } lastState=MPDStatus::self()->state(); switch (lastState) { case MPDState_Paused: scrobbleTimer->pause(); nowPlayingTimer->pause(); break; case MPDState_Playing: { time_t now=time(NULL); currentSong.timestamp = now-MPDStatus::self()->timeElapsed(); DBUG << "Timestamp:" << currentSong.timestamp << "scrobbledCurrent:" << scrobbledCurrent << "nowPlayingSent:" << nowPlayingSent << "now:" << now << "lastNowPlaying:" << lastNowPlaying << "isRepeat:" << isRepeat; if (isRepeat) { calcScrobbleIntervals(); nowPlayingSent=scrobbledCurrent=nowPlayingIsPending=false; lastNowPlaying=0; scrobbleTimer->start(); nowPlayingTimer->start(); return; } if (!scrobbledCurrent) { scrobbleTimer->start(); } // Send now playing if it has not already been sent, or if not scrobbled current track and its been // over X seconds since now paying was sent. if (!nowPlayingSent) { nowPlayingTimer->start(); } else if (!scrobbledCurrent && ((now-lastNowPlaying)*1000)>constNowPlayingInterval) { int remaining=(MPDStatus::self()->timeTotal()-MPDStatus::self()->timeElapsed())*1000; DBUG << "remaining:" << remaining; if (remaining>constNowPlayingInterval) { nowPlayingTimer->setInterval(constNowPlayingInterval); nowPlayingSent=false; nowPlayingTimer->start(); } } break; } default: scrobbleTimer->stop(); nowPlayingTimer->stop(); nowPlayingTimer->setInterval(constNowPlayingInterval); } } } void Scrobbler::mpdStatusUpdated(const MPDStatusValues &vals) { if (!vals.playlistLength) { currentSong.clear(); emit songChanged(false); } } void Scrobbler::clientMessageFailed(const QString &client, const QString &msg) { if (loveSent && client==scrobblerUrl() && msg==QLatin1String("love")) { // 'love' failed, so re-enable... loveSent=lovePending=false; emit songChanged(true); } } void Scrobbler::cancelJobs() { if (authJob) { disconnect(authJob, SIGNAL(finished()), this, SLOT(authResp())); authJob->close(); authJob->abort(); authJob->deleteLater(); authJob=0; } if (scrobbleJob) { disconnect(scrobbleJob, SIGNAL(finished()), this, SLOT(scrobbleFinished())); authJob->close(); authJob->abort(); authJob->deleteLater(); scrobbleJob=0; } } void Scrobbler::reset() { songQueue.clear(); lastScrobbledSongs.clear(); saveCache(); cancelJobs(); } void Scrobbler::loadScrobblers() { if (scrobblers.isEmpty()) { QStringList dirs=QStringList() << Utils::dataDir() << CANTATA_SYS_CONFIG_DIR; foreach (const QString &dir, dirs) { if (dir.isEmpty()) { continue; } QFile f(dir+"scrobblers.xml"); if (f.open(QIODevice::ReadOnly)) { QXmlStreamReader doc(&f); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement() && QLatin1String("scrobbler")==doc.name()) { QString name=doc.attributes().value("name").toString(); QString url=doc.attributes().value("url").toString(); if (!name.isEmpty() && !url.isEmpty() && !scrobblers.contains(name)) { scrobblers.insert(name, url); } } } } } } } QString Scrobbler::scrobblerUrl() { loadScrobblers(); if (scrobblers.isEmpty()) { return QString(); } if (!scrobblers.contains(scrobbler)) { return scrobblers.constBegin().value(); } return scrobblers[scrobbler]; } cantata-2.2.0/scrobbling/scrobbler.h000066400000000000000000000116531316350454000174070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * This file contains code from QMPDClient: * Copyright (C) 2005-2008 Håvard Tautra Knutsen * Copyright (C) 2009 Voker57 * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SCROBBLER_H #define SCROBBLER_H #include #include #include #include #include #include #include #include "mpd-interface/mpdstatus.h" class QTimer; class QNetworkReply; class PausableTimer; struct Song; struct MPDStatusValues; class Scrobbler : public QObject { Q_OBJECT public: struct Track { Track() : track(0), length(0), timestamp(0) { } Track(const Song &s); bool operator==(const Track &o) const { return track==o.track && title==o.title && artist==o.artist && albumartist==o.albumartist && album==o.album; } bool operator!=(const Track &o) const { return !(*this==o); } void clear() { title=artist=albumartist=album=QString(); track=length=0; timestamp=0; } bool isEmpty() { return 0==track && 0==length && 0==timestamp && title.isEmpty() && artist.isEmpty() && albumartist.isEmpty() && album.isEmpty(); } QString title; QString artist; QString albumartist; QString album; quint32 track; quint32 length; time_t timestamp; }; static Scrobbler * self(); static void enableDebug(); static const QLatin1String constCacheDir; static const QLatin1String constCacheFile; static bool viaMpd(const QString &sc) { return !sc.startsWith("http"); } Scrobbler(); ~Scrobbler(); QMap availableScrobblers() { loadScrobblers(); return scrobblers; } void stop(); bool isEnabled() const { return scrobblingEnabled; } bool isLoveEnabled() const { return loveIsEnabled; } bool lovedTrack() const { return lovePending || loveSent; } bool haveLoginDetails() const { return !userName.isEmpty() && !password.isEmpty(); } void setDetails(const QString &s, const QString &u, const QString &p); const QString & user() const { return userName; } const QString & pass() const { return password; } const QString & activeScrobbler() const { return scrobbler; } bool isAuthenticated() const { return !sessionKey.isEmpty(); } Q_SIGNALS: void error(const QString &msg); void authenticated(bool a); void enabled(bool e); void loveEnabled(bool e); void songChanged(bool valid); void scrobblerChanged(); // send love via client message... void clientMessage(const QString &client, const QString &msg, const QString &clientName); public Q_SLOTS: void love(); void setEnabled(bool e); void setLoveEnabled(bool e); private Q_SLOTS: void setSong(const Song &s); void scrobbleCurrent(); void scrobbleQueued(); void scrobbleNowPlaying(); void handleResp(); void authenticate(); void authResp(); void scrobbleFinished(); void mpdStateUpdated(bool songChanged=false); void mpdStatusUpdated(const MPDStatusValues &vals); void clientMessageFailed(const QString &client, const QString &msg); private: void setActive(); void loadSettings(); bool ensureAuthenticated(); void loadCache(); void saveCache(); void calcScrobbleIntervals(); void cancelJobs(); void reset(); void loadScrobblers(); QString scrobblerUrl(); private: bool scrobblingEnabled; bool loveIsEnabled; QMap scrobblers; QString scrobbler; QString userName; QString password; QString sessionKey; QQueue songQueue; QQueue lastScrobbledSongs; Track inactiveSong; // Song set whilst inactive Track currentSong; PausableTimer * scrobbleTimer; QTimer * retryTimer; PausableTimer * nowPlayingTimer; time_t lastNowPlaying; QTimer * hardFailTimer; bool nowPlayingIsPending; bool lovePending; bool lastScrobbleFailed; bool nowPlayingSent; bool loveSent; bool scrobbledCurrent; bool scrobbleViaMpd; int failedCount; MPDState lastState; QNetworkReply *authJob; QNetworkReply *scrobbleJob; }; #endif cantata-2.2.0/scrobbling/scrobblers.xml000066400000000000000000000004071316350454000201360ustar00rootroot00000000000000 cantata-2.2.0/scrobbling/scrobblinglove.cpp000066400000000000000000000036021316350454000207720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "scrobblinglove.h" #include "scrobbler.h" #include "widgets/icons.h" ScrobblingLove::ScrobblingLove(QWidget *p) : ToolButton(p) { setIcon(Icons::self()->addToFavouritesIcon); connect(Scrobbler::self(), SIGNAL(loveEnabled(bool)), SLOT(setVisible(bool))); connect(Scrobbler::self(), SIGNAL(songChanged(bool)), SLOT(songChanged(bool))); connect(Scrobbler::self(), SIGNAL(scrobblerChanged()), SLOT(scrobblerChanged())); setVisible(Scrobbler::self()->isLoveEnabled()); connect(this, SIGNAL(clicked()), this, SLOT(sendLove())); songChanged(false); } void ScrobblingLove::sendLove() { setEnabled(false); Scrobbler::self()->love(); scrobblerChanged(); } void ScrobblingLove::songChanged(bool valid) { setEnabled(valid); scrobblerChanged(); } void ScrobblingLove::scrobblerChanged() { setToolTip(Scrobbler::self()->lovedTrack() ? tr("%1: Loved Current Track").arg(Scrobbler::self()->activeScrobbler()) : tr("%1: Love Current Track").arg(Scrobbler::self()->activeScrobbler())); } cantata-2.2.0/scrobbling/scrobblinglove.h000066400000000000000000000022341316350454000204370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SCROBBLING_LOVE_H #define SCROBBLING_LOVE_H #include "widgets/toolbutton.h" class ScrobblingLove : public ToolButton { Q_OBJECT public: ScrobblingLove(QWidget *p); virtual ~ScrobblingLove() { } private Q_SLOTS: void sendLove(); void songChanged(bool valid); void scrobblerChanged(); }; #endif cantata-2.2.0/scrobbling/scrobblingsettings.cpp000066400000000000000000000133231316350454000216660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "scrobblingsettings.h" #include "scrobbler.h" #include "support/buddylabel.h" #include "support/lineedit.h" #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; ScrobblingSettings::ScrobblingSettings(QWidget *parent) : QWidget(parent) { setupUi(this); connect(loginButton, SIGNAL(clicked()), this, SLOT(save())); connect(Scrobbler::self(), SIGNAL(authenticated(bool)), SLOT(showStatus(bool))); connect(Scrobbler::self(), SIGNAL(error(QString)), SLOT(showError(QString))); connect(user, SIGNAL(textChanged(QString)), SLOT(controlLoginButton())); connect(pass, SIGNAL(textChanged(QString)), SLOT(controlLoginButton())); // connect(enableScrobbling, SIGNAL(toggled(bool)), Scrobbler::self(), SLOT(setEnabled(bool))); // connect(showLove, SIGNAL(toggled(bool)), Scrobbler::self(), SLOT(setLoveEnabled(bool))); connect(Scrobbler::self(), SIGNAL(enabled(bool)), enableScrobbling, SLOT(setChecked(bool))); connect(scrobbler, SIGNAL(currentIndexChanged(int)), this, SLOT(scrobblerChanged())); loginButton->setEnabled(false); QMap scrobblers=Scrobbler::self()->availableScrobblers(); QStringList keys=scrobblers.keys(); keys.sort(); QString firstMpdClient; foreach (const QString &k, keys) { bool viaMpd=Scrobbler::viaMpd(scrobblers[k]); if (viaMpd) { scrobbler->addItem(tr("%1 (via MPD)", "scrobbler name (via MPD)").arg(k), k); } else { scrobbler->addItem(k); } if (viaMpd && firstMpdClient.isEmpty()) { firstMpdClient=k; } } if (scrobbler->count()>1) { scrobblerName->setVisible(false); } else { scrobblerName->setText(scrobbler->itemText(0)); scrobbler->setVisible(false); scrobblerLabel->setBuddy(0); } scrobblerName->setVisible(scrobbler->count()<2); if (firstMpdClient.isEmpty()) { REMOVE(noteLabel) } else { noteLabel->setText(tr("If you use a scrobbler which is marked as '(via MPD)' (such as %1), " "then you will need to have this already started and running. " "Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling.").arg(firstMpdClient)); } #ifdef Q_OS_MAC expandingSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); #endif } void ScrobblingSettings::load() { showStatus(Scrobbler::self()->isAuthenticated()); user->setText(Scrobbler::self()->user().trimmed()); pass->setText(Scrobbler::self()->pass().trimmed()); QString s=Scrobbler::self()->activeScrobbler(); for (int i=0; icount(); ++i) { if (scrobbler->itemText(i)==s || scrobbler->itemData(i).toString()==s) { scrobbler->setCurrentIndex(i); break; } } enableScrobbling->setChecked(Scrobbler::self()->isEnabled()); showLove->setChecked(Scrobbler::self()->isLoveEnabled()); controlLoginButton(); scrobblerChanged(); } void ScrobblingSettings::save() { bool isLogin=sender()==loginButton; messageWidget->close(); QString u=user->text().trimmed(); QString p=pass->text().trimmed(); QString sc=scrobbler->itemData(scrobbler->currentIndex()).toString(); if (sc.isEmpty()) { loginStatusLabel->setText(tr("Authenticating...")); sc=scrobbler->currentText(); } Scrobbler::self()->setEnabled(enableScrobbling->isChecked()); Scrobbler::self()->setLoveEnabled(showLove->isChecked()); // We dont save password, so this /might/ be empty! Therefore, dont disconnect just // because user clicked OK/Apply... if (isLogin || sc!=Scrobbler::self()->activeScrobbler() || u!=Scrobbler::self()->user() || (!Scrobbler::self()->pass().isEmpty() && p!=Scrobbler::self()->pass())) { Scrobbler::self()->setDetails(sc, u, pass->text().trimmed()); } } void ScrobblingSettings::showStatus(bool status) { loginStatusLabel->setText(status ? tr("Authenticated") : tr("Not Authenticated")); if (status) { messageWidget->close(); } enableScrobbling->setEnabled(status); } void ScrobblingSettings::showError(const QString &msg) { messageWidget->setError(msg, true); } void ScrobblingSettings::controlLoginButton() { loginButton->setEnabled(user->isEnabled() && !user->text().trimmed().isEmpty() && !pass->text().trimmed().isEmpty()); } void ScrobblingSettings::scrobblerChanged() { bool viaMpd=!scrobbler->itemData(scrobbler->currentIndex()).toString().isEmpty(); user->setEnabled(!viaMpd); userLabel->setEnabled(!viaMpd); pass->setEnabled(!viaMpd); passLabel->setEnabled(!viaMpd); loginStatusLabel->setEnabled(!viaMpd); statusLabel->setEnabled(!viaMpd); enableScrobbling->setEnabled(!viaMpd); if (viaMpd) { enableScrobbling->setChecked(false); } controlLoginButton(); } cantata-2.2.0/scrobbling/scrobblingsettings.h000066400000000000000000000025071316350454000213350ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SCROBBLING_SETTINGS_H #define SCROBBLING_SETTINGS_H #include "ui_scrobblingsettings.h" class LineEdit; class QPushButton; class QLabel; class ScrobblingSettings : public QWidget, public Ui::ScrobblingSettings { Q_OBJECT public: ScrobblingSettings(QWidget *parent); void load(); public Q_SLOTS: void save(); private Q_SLOTS: void showStatus(bool status); void showError(const QString &msg); void controlLoginButton(); void scrobblerChanged(); }; #endif cantata-2.2.0/scrobbling/scrobblingsettings.ui000066400000000000000000000114301316350454000215160ustar00rootroot00000000000000 ScrobblingSettings 0 0 475 295 0 0 0 0 QFormLayout::ExpandingFieldsGrow Scrobble using: scrobbler 0 0 Username: user Password: pass QLineEdit::Password Status: 0 0 0 0 Login QFrame::StyledPanel QFrame::Raised Scrobble tracks Show 'Love' button Qt::Vertical 20 0 LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    MessageWidget QFrame
    support/messagewidget.h
    1
    cantata-2.2.0/scrobbling/scrobblingstatus.cpp000066400000000000000000000034531316350454000213540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "scrobblingstatus.h" #include "scrobbler.h" #include "widgets/icons.h" #include "widgets/spacerwidget.h" #include ScrobblingStatus::ScrobblingStatus(QWidget *p) : QWidget(p) { btn = new ToolButton(this); btn->setCheckable(true); btn->setIcon(Icons::self()->lastFmIcon); connect(Scrobbler::self(), SIGNAL(authenticated(bool)), SLOT(setVisible(bool))); connect(Scrobbler::self(), SIGNAL(enabled(bool)), btn, SLOT(setChecked(bool))); connect(btn, SIGNAL(toggled(bool)), Scrobbler::self(), SLOT(setEnabled(bool))); setVisible(Scrobbler::self()->isAuthenticated()); btn->setChecked(Scrobbler::self()->isEnabled()); scrobblerChanged(); QHBoxLayout *l=new QHBoxLayout(this); l->setMargin(0); l->setSpacing(0); l->addWidget(btn); l->addWidget(new SpacerWidget(this)); } void ScrobblingStatus::scrobblerChanged() { btn->setToolTip(tr("%1: Scrobble Tracks").arg(Scrobbler::self()->activeScrobbler())); } cantata-2.2.0/scrobbling/scrobblingstatus.h000066400000000000000000000024061316350454000210160ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SCROBBLING_STATUS_H #define SCROBBLING_STATUS_H #include "widgets/toolbutton.h" class ScrobblingStatus : public QWidget { Q_OBJECT public: ScrobblingStatus(QWidget *p); virtual ~ScrobblingStatus() { } bool isChecked() const { return btn->isChecked(); } public Q_SLOTS: void setChecked(bool c) { btn->setChecked(c); } private Q_SLOTS: void scrobblerChanged(); private: ToolButton *btn; }; #endif cantata-2.2.0/streams/000077500000000000000000000000001316350454000146055ustar00rootroot00000000000000cantata-2.2.0/streams/digitallyimportedsettings.cpp000066400000000000000000000112731316350454000226240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "digitallyimportedsettings.h" #include "models/digitallyimported.h" #include "support/buddylabel.h" #include "support/lineedit.h" #include #include DigitallyImportedSettings::DigitallyImportedSettings(QWidget *parent) : Dialog(parent) { setButtons(Ok|Cancel); setCaption(tr("Digitally Imported Settings")); QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); audio->addItem(tr("MP3 256k"), 0); audio->addItem(tr("AAC 64k"), 1); audio->addItem(tr("AAC 128k"), 2); connect(loginButton, SIGNAL(clicked()), this, SLOT(login())); connect(DigitallyImported::self(), SIGNAL(loginStatus(bool,QString)), SLOT(loginStatus(bool,QString))); int h=fontMetrics().height(); user->setMinimumWidth(h*20); adjustSize(); setMinimumSize(size()); } void DigitallyImportedSettings::show() { prevUser=DigitallyImported::self()->user(); prevPass=DigitallyImported::self()->pass(); wasLoggedIn=DigitallyImported::self()->loggedIn(); prevAudioType=DigitallyImported::self()->audioType(); setState(); user->setText(prevUser); pass->setText(prevPass); for (int i=0; icount(); ++i) { if (audio->itemData(i).toInt()==DigitallyImported::self()->audioType()) { audio->setCurrentIndex(i); break; } } loginStatusLabel->setText(DigitallyImported::self()->statusString()); if (QDialog::Accepted==Dialog::exec()) { QString u=user->text().trimmed(); QString p=pass->text().trimmed(); int at=audio->itemData(audio->currentIndex()).toInt(); bool changed=false; bool needToLogin=false; if (u!=DigitallyImported::self()->user()) { DigitallyImported::self()->setUser(u); needToLogin=changed=true; } if (p!=DigitallyImported::self()->pass()) { DigitallyImported::self()->setPass(p); needToLogin=changed=true; } if (DigitallyImported::self()->audioType()!=at) { DigitallyImported::self()->setAudioType(at); changed=true; } if (needToLogin) { DigitallyImported::self()->login(); } if (changed) { DigitallyImported::self()->save(); } } else { DigitallyImported::self()->setUser(prevUser); DigitallyImported::self()->setPass(prevPass); DigitallyImported::self()->setAudioType(prevAudioType); if (wasLoggedIn) { DigitallyImported::self()->login(); } } } void DigitallyImportedSettings::login() { if (DigitallyImported::self()->loggedIn()) { loginStatusLabel->setText(tr("Not Authenticated")); DigitallyImported::self()->logout(); } else { loginStatusLabel->setText(tr("Authenticating...")); messageWidget->close(); DigitallyImported::self()->setUser(user->text().trimmed()); DigitallyImported::self()->setPass(pass->text().trimmed()); DigitallyImported::self()->login(); } } void DigitallyImportedSettings::loginStatus(bool status, const QString &msg) { loginStatusLabel->setText(status ? tr("Authenticated") : tr("Not Authenticated")); if (status) { messageWidget->close(); } else { messageWidget->setError(msg, true); adjustSize(); } setState(); } void DigitallyImportedSettings::setState() { if (DigitallyImported::self()->sessionExpiry().isValid()) { expiry->setText(DigitallyImported::self()->sessionExpiry().toString(Qt::ISODate)); } else { loginButton->setText(tr("Login")); expiry->setText(QString()); } expiry->setVisible(DigitallyImported::self()->sessionExpiry().isValid()); expiryLabel->setVisible(expiry->isVisible()); loginButton->setText(DigitallyImported::self()->loggedIn() ? tr("Logout") : tr("Login")); } cantata-2.2.0/streams/digitallyimportedsettings.h000066400000000000000000000027001316350454000222640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DIGITALLYIMPORTED_SETTINGS_H #define DIGITALLYIMPORTED_SETTINGS_H #include "support/dialog.h" #include "ui_digitallyimportedsettings.h" class LineEdit; class QComboBox; class QPushButton; class QLabel; class DigitallyImportedSettings : public Dialog, public Ui::DigitallyImportedSettings { Q_OBJECT public: DigitallyImportedSettings(QWidget *parent); void show(); private Q_SLOTS: void login(); void loginStatus(bool status, const QString &msg); private: void setState(); private: bool wasLoggedIn; QString prevUser; QString prevPass; int prevAudioType; }; #endif cantata-2.2.0/streams/digitallyimportedsettings.ui000066400000000000000000000140211316350454000224510ustar00rootroot00000000000000 DigitallyImportedSettings 0 0 491 367 0 0 0 0 You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. true true Premium Account QFormLayout::ExpandingFieldsGrow Username: user Password: pass QLineEdit::Password Stream type: audio Status: 0 0 0 0 Login Session expiry: 0 0 QFrame::StyledPanel QFrame::Raised These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Qt::Vertical 20 6 LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    NoteLabel QLabel
    widgets/notelabel.h
    MessageWidget QFrame
    support/messagewidget.h
    1
    cantata-2.2.0/streams/icons/000077500000000000000000000000001316350454000157205ustar00rootroot00000000000000cantata-2.2.0/streams/icons/CMakeLists.txt000066400000000000000000000006141316350454000204610ustar00rootroot00000000000000set(CANTATA_INSTALL_STREAM_ICONS stream.png) if (WIN32) install(FILES ${CANTATA_INSTALL_STREAM_ICONS} DESTINATION ${CMAKE_INSTALL_PREFIX}/icons/) elseif (APPLE) install(FILES ${CANTATA_INSTALL_STREAM_ICONS} DESTINATION ${MACOSX_BUNDLE_RESOURCES}/icons/) else () install(FILES ${CANTATA_INSTALL_STREAM_ICONS} DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/icons/) endif () cantata-2.2.0/streams/icons/dirble.svg000066400000000000000000000021441316350454000177030ustar00rootroot00000000000000 cantata-2.2.0/streams/icons/favourites.svg000066400000000000000000000032101316350454000206240ustar00rootroot00000000000000 cantata-2.2.0/streams/icons/icecast.svg000066400000000000000000000033111316350454000200520ustar00rootroot00000000000000 Nuvola apps package cantata-2.2.0/streams/icons/shoutcast.svg000066400000000000000000000060641316350454000204640ustar00rootroot00000000000000 cantata-2.2.0/streams/icons/stream.png000066400000000000000000000437321316350454000177320ustar00rootroot00000000000000PNG  IHDR``w8bKGD pHYs  tIME%.<GgIDATxԽwdWu{T:PF%d؀13Y2vR\"*U(ERSJ+$(@ ~@ ,d&7@bXz#I,.N~f;<25K#ărpoMxaQ =ʕ*JJJR x$0OX'0Ρ#m8jG'st3KG[ĩt{S?>~Ux{9|ow?n4g:kIk.C*F'|MݣV1?>7Gi;T\{E"]z*TJD\ZRUV*T*%JQT,S%/W"_8{s`\n I ]hvhNjd81.M]=}?uBЏx_L@kMfa-9RWsN>066yz=&VeqIT%QDRZP\QT*SJa)P273`8'@/s8X:ڐ݌yy/ oKR o؁cn5HiXkp1Q*y]Tshi6t,߄"C_Q&"4G$܅?wvq\M Fi1xVi\.1u;tԗIQ[BxTej C*C jJL ȡR D7[ V~| pX 剺L-sc.4bK;ıAwV~tZ?x]}!|~ߺ_>XgV p 1̓.Z>R8q(Z>jDJT "@(kB8_ZzGt}3HXPD|w~6] r?ˇ9_I)BRcB2a!Q!s'$Mq(>T8遒8)sd$%Ihm{9 R"E$HH_" Ѓ@WIvl#7rvn>plÑ%e%JHCK3[5oy#_?~8־bQL}R ˯}'|񋝯E՛HU@D Y^Vx|n4b_Z|銾[lڒ y^ K:2*eEPMf''rP|}$9e: S&nv=#/zQ R *{yL\&o⹄@$D$c '2@&J#$Da*Ո) ,*Mal-=9$$P$ykp+2]^:Ʉ:X `B, ?#rY7DZB3dAx>R(!̯~EuBRF< OE8g|;38z[yqS?Dx>J "g=7AyEm0441G Cnǫgx &d ISH%$P[|RݜࣜE`rYD[0>pW«\_k1_)ʓk!uc(ҙa1bAbynYNXϋK2Ph(8Z(aa+r.'ް-C\{(;ϏF>Ĵ[I iҝL?O~@/)X5n2d Eyٜ1#m-eL~bFAʛ5xo2SEkcE .Z%]6X⡭؅y[Mc4OV7ĔXKij)q(Ifv+H5axݬ-H%3S Doko}}quk^YT_"bbN.$ciR)û$ C {9ԯFKxcKNK ?~0e1]'&N>˅[Mj^~sNTGd?|Rw4tġYx(áAzBȅ`BJee5ϡhc DHFRdzs .V{*JEC .n!ablt3ow'M;["aEpzp[AWWHK X;s'2/@H!s1Sr:ǷgFaяk@]]!;s<` BP.2dYFsΪk)=Ak6^ JIhڇ rǘ CUWM3->gpOBM|8$Χs 3v0qttJ? :{=n$=Ld]4#4Z[]ly <䈢5}F8xOL;/| .J׌ h21~@ʥeěfI{+< k 2 CchdQ =<' ǒ׭b:Gќd l÷;Pٮѷ`iLpG^EehWKO޻"$тT+޻wے8^v^М/ Z9m(͝+Pڐe)T% lmc : h-Y9n`HBXα4+:rd(LAęn&ItN9sٹZU#O9B[QP)1-,/|/PGdĉ-[GVËv}I1fgIӔ֢-:1IoI$?ʑqa "5ZQPA.=%1ǡ鐟^b\E18o]4K#R0*.<+w:㸷F. w`L/Q8&N ^b{]vj^]hTs"ϱ,J_d^A< |y^KݾKv)o7:t a#.a`;I-eL!U_|D>b%+ˎFf$<:wcB,Uջ)V)#-˞%o-4!M3lG[r qЭ"ŬB^ k# +䉁Z\Phuo\KŃb;|~xO펻ٳw/~[R)W|.R'&FkHDVE3hkX_3LLrI# [pam+9\4| S~=6q&%, 6r/$2m%21n2fT`hŢ1NrI( ݷok>/F¬YR aRV9u1 fzmX,ۍɐQp6K.'m }dg3Y9!9_Ey#MQ*b= %4u&!dzQJ1xAQ[Wr=)v0#p40(x}JR6dI(Nq'so Ebtٙ,5ͨWt 2aerjfм\-p!6o\ǯ5lXe+V8If߁<'_e'_rњ|X)~kg,=N> ]Q(20k:w4&sUn? DV&q*h4]@' $&++ s ?"EB("?1cwD3Zɟv\}| bR` UGpHzqttJg{RQs_B2swRp.K_<\ {jz]m5Af z/ 0dqBڋ;]I Ɓ FfU%4",y5ԅ,fTQdÌpnƝvdXs7qkٶT$tń8tpPz\y[m:qK$AX}J/\~u{g32Y{}us >GaНu=nG'Hg+n q 'B) #G 80L^VA՛-B(l?m+Q'Gm=i[N%.v{eXcO#[SnXSYv\V vűo}KY~ 6Gl&d"YH#DܳZ% i7&&݌Qn(yg<46G/gxY",gqVus+\.' I}u8?[Bܛ$(UM8J!(W#st:JILQeq!2V:K~:m3W]y%X,C-o+~Mz|'%kx:Mkc |?_YA爻$FgwŞszHP-<8L>7o85 M􆐅 &ѹ<1/X;)38kJܶ'\8^7 |G%*E~P(it2 GfϮ]λy|shИ՜nQT7?A$ؙ<ۮw!Оvѝά[7Lܜ#^vvs$[g|||j>u҄Gyt>n<㤽.9:6qʋ틘vc!m贡ӄv :-6ۆ q.tmozUxyQr)]<&^,f 9 քs!/ 9~.E,L2pI"yipN2qm9ht`pZ3%掻Rض՜#*W9m*:K8={=7EݤѨi7:SگT[_]wVn^ &Lukͺϳz6ҥ8׍>͆,(gMqFKCy.*QX|gqNH2D\/[ D[Evr|$sGֹct'0:b|q/ؽwZ W9f-n\.a\ଣyy[{?-+֍evj v,>/+Iab-K<_N}Nl Z A!$a > 6L::N)P8\քt-ّԇCPyx^j¥S21yI]S ^L~J0FբT)c[xݲ~(NlXO8mvmDGt؏wrJg/xի_Sߌ?ƒj{2~yLr(i sTBŻo>qj. I{_Ab=!`4]s ٤¾l55shIN'}jQOxџy3[LJiw(E%t~x۝DQt%mKqavvwIwʫ䪫$݌ ) R^ɞ;I;~|'s34M>p`>tgt0I"mtiӼag2X\:rW7aMLq=}ײ|Z!dE#E )=B)1?hhS1&ظɏ 0'~ [Ȃz{~9/M.]!'gytN4ZGh4$iA@طg76ldrjnCۡ׋_?rjJ*~o> 3S՘h2Zz6w-¤Nclb ct )0'a懌f10O`[Oħ9Dza>"e`cp(^Cs*GAg㾶أsgQ)zCVZI8,㒋Eg9ffgس abɉ,[^G>Kz=$xHf[ʕgwҮשШϰz*>̹=V4Fg F9'A =}ij 5p/F}.ʙEzZmF˼3;F\fL_K~|$o(|4 _G5_Tw:vNAIqqO >yUW p!'#S9z04T˒qųO>Ɋ.ethXjiZc+5LE^?=Vkz})]v)BI:lFd 7+֘*@c,w8w^b2K'$Iq,.k>E_ qac^p"4<4}^6z* FƖ "2_xLj2~$]WDHh457GsAQݨl159#=zgɹ/4IH :64˗/e-[3bu~a k20dKFa| L۝-vU7 Ϋ$B:=\G.%SiKa0悰3IH.x3?'@/7rp>< 9?@$I/=gB?8R(,Q.W1FSecvi۴-NvIKo}3' M)qw8ܩ-C7筃fg\7]O/@0JJλ"<\Vo؀v;hY9It`c h!Ǐ /f#k8umLN4T\rʺ;x8i5瘘8ʫ_uM^h)1}` Ij,Y,>͟CKť჌V-N7,6c0)\ |w7]/2"PAO_5G:zqg֩^C/}'z(cݺ5:|5^4E}{grze0+V)V$N83)Kdz^} ļM QM7R)* sӜ~vFG8|8䋤u!v:qBRV /+.'NbIBe=n+W,>hߚ,7-b H(WC4q0B( gKC|$oGyϭs/c7}7jxca>IJNn睃䀢[~tR ?[2l)Ng@I#IzĽJx1n-,Zgi7m?s٠)*B#pޟ<1?(!Ry$.[G^LFA~v8Ln+I"7ҫI̷]Ovoe\vՕl9m:cXs=JR%*93y݃1Js+DHAZcvfq/L *!I\R=hFfQ$ѨSTe42y]U?wok;N5BX :X蠖S:8h9֠2DK0:,h H&C6Y;!k~q{P*:U[>| Il UQf) hҫ x@wEyn>G~̬1^ɿU9M@H'U~<Sg*s]Zg0XpC %r:Hgu U沘.+Q|BĞ}/|Iei:@׉(Ƶ]lۡkVR_. XF^К—u|DMDg:^Q@oHb:%v;Qd٥_mb483{y @oԫ.C{DAޑ ~Xe+n`޼.hfI:`E% /(,M,i:u -ESUPUZh؀S;eΑ/qdKhg* WT>^hlZw%:?|W$Ƈa /qa6 I24cchƇn>7E(& =1:̡.Nu]dYBh0mBKyc<΢E@R$dI@D "WUP٣P:,&9dzʭ]j$ %j9 M]ƶ;N*-GKbJOyLqmytrjn6>##COLt ‘8BԊD#a{q1a]\~5̙5 sNizãf _jPxUK-gci*)U'J%bʏe K !uUREByvˬUwW"㧼W-3 h\e]{^@BX.i$IFFyKD(PDT_e_R+~7DT%I )v^5AJWjVvgo㉏=ӷ"PknDmH7|bȦ؈(I}}=- 14<0bϮI{ Omdu5,\#/Yfqw'hH8BcS#OlyFhR`2d_MatGqtAV^s[q-̞ݎb==!pCeC߯u>m069ɯyD}}|R@PQUpD8m~7rRUBRU$MC24$]ijW~ki+}rxhŋCM]cYuɥ"a~ӟp1^f8)475N )S Dm D۶+zD>H*l&˙'Z?gnk pd}=CضmԲ,M6DI$.,bKH KV 5Xņ_>D쒡Qt~?3t҂ApUx?SpRDeN/b̽R^+ |5gdmMhĨIıoȂE y[ b lJS,(h*+}r~?{ybۘ ͰrZS)&ljF"MkƢ y^P|98}f(KTeUBJ̗5(Ƕb~ga=org_|q߯SxM︆Y5wnZə39t_z)vۙ`b$j0 3$/EGG'i0x F8Ly=xUSi30b&ݟ2UrpufHeG P 92dE& l7$xdE,Z럋Pxg?9qYy`oLMuSض͚KyǛ.FuYw[壽$vJ4CEs,d$B ;y3&{r?y.D4PQf=enYp<阙2E$.ҩ]_Zo%OՔ?#NOlyў]uaӼH4(=$T&O4|jI|g޽,Zc##,d s;>is5$kk}0DLNgہ$&2qY ]2G\dLǞzx.W3GS?({6ywΎo'j0iR*ؾ{(zb>O:d| SSZ H5C7890s#UtQ3aq!K ~+>|Z^o❟Xϧ?w[d2B6o|"(dMQ '@6H }xȉ:&$BTŘ1ɴjMHy cantata-2.2.0/streams/streamdialog.cpp000066400000000000000000000070261316350454000177710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "streamdialog.h" #include "gui/settings.h" #include "models/streamsmodel.h" #include "widgets/icons.h" #include "mpd-interface/mpdconnection.h" #include "support/buddylabel.h" #include "support/utils.h" #include "config.h" StreamDialog::StreamDialog(QWidget *parent, bool addToPlayQueue) : Dialog(parent) , saveCheckbox(0) , urlHandlers(MPDConnection::self()->urlHandlers()) { QWidget *wid = new QWidget(this); QFormLayout *layout = new QFormLayout(wid); layout->setMargin(0); urlEntry = new LineEdit(wid); nameEntry = new LineEdit(wid); if (addToPlayQueue) { saveCheckbox=new QCheckBox(tr("Add stream to favourites"), wid); } statusText = new QLabel(this); urlEntry->setMinimumWidth(300); nameLabel=new BuddyLabel(tr("Name:"), wid, nameEntry); BuddyLabel *urlLabel=new BuddyLabel(tr("URL:"), wid, urlEntry); int row=0; layout->setWidget(row, QFormLayout::LabelRole, urlLabel); layout->setWidget(row++, QFormLayout::FieldRole, urlEntry); layout->setWidget(row, QFormLayout::LabelRole, nameLabel); layout->setWidget(row++, QFormLayout::FieldRole, nameEntry); if (addToPlayQueue) { saveCheckbox->setChecked(false); layout->setWidget(row++, QFormLayout::FieldRole, saveCheckbox); connect(saveCheckbox, SIGNAL(toggled(bool)), SLOT(changed())); } layout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); layout->setWidget(row++, QFormLayout::SpanningRole, statusText); setCaption(tr("Add Stream")); setMainWidget(wid); setButtons(Ok|Cancel); enableButton(Ok, false); connect(nameEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); connect(urlEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); urlEntry->setFocus(); resize(400, 100); } void StreamDialog::setEdit(const QString &editName, const QString &editUrl) { setCaption(tr("Edit Stream")); enableButton(Ok, false); prevName=editName; prevUrl=editUrl; nameEntry->setText(editName); urlEntry->setText(editUrl); } void StreamDialog::changed() { QString u=url(); bool urlOk=u.length()>5 && u.contains(QLatin1String("://")); bool validProtocol=u.isEmpty() || urlHandlers.contains(QUrl(u).scheme()) || urlHandlers.contains(u); bool enableOk=false; if (!save()) { enableOk=urlOk; } else { QString n=name(); enableOk=!n.isEmpty() && urlOk && (n!=prevName || u!=prevUrl); } statusText->setText(validProtocol || !urlOk ? QString() : tr("ERROR: Invalid protocol")); enableOk=enableOk && validProtocol; enableButton(Ok, enableOk); } cantata-2.2.0/streams/streamdialog.h000066400000000000000000000032231316350454000174310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMDIALOG_H #define STREAMDIALOG_H #include #include #include "support/dialog.h" #include "support/lineedit.h" class QLabel; class BuddyLabel; class StreamDialog : public Dialog { Q_OBJECT public: StreamDialog(QWidget *parent, bool addToPlayQueue=false); void setEdit(const QString &editName, const QString &editUrl); QString name() const { return nameEntry->text().trimmed(); } QString url() const { return urlEntry->text().trimmed(); } bool save() const { return !saveCheckbox || saveCheckbox->isChecked(); } private Q_SLOTS: void changed(); private: QString prevName; QString prevUrl; QCheckBox *saveCheckbox; LineEdit *nameEntry; LineEdit *urlEntry; BuddyLabel *nameLabel; QLabel *statusText; QSet urlHandlers; }; #endif cantata-2.2.0/streams/streamfetcher.cpp000066400000000000000000000244461316350454000201570ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamfetcher.h" #include "network/networkaccessmanager.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdparseutils.h" #include "models/streamsmodel.h" #include #include #include #ifdef _MSC_VER #define strncasecmp _strnicmp #define strcasecmp _stricmp #endif #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << "StreamFetcher" << __FUNCTION__ void StreamFetcher::enableDebug() { debugEnabled=true; } static const int constMaxRedirects = 3; static const int constMaxData = 1024; static const int constTimeout = 3*1000; static QString parsePlaylist(const QByteArray &data, const QString &key, const QSet &handlers) { QStringList lines=QString(data).split('\n', QString::SkipEmptyParts); foreach (QString line, lines) { if (line.startsWith(key, Qt::CaseInsensitive)) { foreach (const QString &handler, handlers) { QString protocol(handler+QLatin1String("://")); int index=line.indexOf(protocol, Qt::CaseInsensitive); if (index>-1 && index<7) { line.remove('\n'); line.remove('\r'); return line.mid(index); } } } } return QString(); } static QString parseExt3Mu(const QByteArray &data, const QSet &handlers) { QStringList lines=QString(data).split(QRegExp(QLatin1String("(\r\n|\n|\r)")), QString::SkipEmptyParts); foreach (QString line, lines) { foreach (const QString &handler, handlers) { QString protocol(handler+QLatin1String("://")); if (line.startsWith(protocol, Qt::CaseInsensitive)) { line.remove('\n'); line.remove('\r'); return line; } } } return QString(); } static QString parseAsx(const QByteArray &data, const QSet &handlers) { QStringList lines=QString(data).split(QRegExp(QLatin1String("(\r\n|\n|\r|/>)")), QString::SkipEmptyParts); foreach (QString line, lines) { int ref=line.indexOf(QLatin1String(" &handlers) { // XSPF / SPIFF QXmlStreamReader reader(data); while (!reader.atEnd()) { reader.readNext(); if (QXmlStreamReader::StartElement==reader.tokenType() && QLatin1String("location")==reader.name()) { QString loc=reader.readElementText().trimmed(); foreach (const QString &handler, handlers) { if (loc.startsWith(handler+QLatin1String("://"))) { return loc; } } } } return QString(); } static QString parse(const QByteArray &data) { QSet handlers=MPDConnection::self()->urlHandlers(); if (data.length()>10 && !strncasecmp(data.constData(), "[playlist]", 10)) { DBUG << "playlist"; return parsePlaylist(data, QLatin1String("File"), handlers); } else if (data.length()>7 && (!strncasecmp(data.constData(), "#EXTM3U", 7) || !strncasecmp(data.constData(), "http://", 7))) { DBUG << "ext3mu"; return parseExt3Mu(data, handlers); } else if (data.length()>5 && !strncasecmp(data.constData(), "11 && !strncasecmp(data.constData(), "[reference]", 11)) { DBUG << "playlist/ref"; return parsePlaylist(data, QLatin1String("Ref"), handlers); } else if (data.length()>5 && !strncasecmp(data.constData(), "get(u, constTimeout); DBUG << "Check" << u.toString(); connect(job, SIGNAL(readyRead()), this, SLOT(dataReady())); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); return; } else { DBUG << "use orig" << current; done.append(MPDParseUtils::addStreamName(current, currentName)); } } if (todo.isEmpty() && !done.isEmpty()) { job=0; emit result(done, row, playQueueAction, prio, decreasePriority); emit status(QString()); } } void StreamFetcher::cancel() { todo.clear(); done.clear(); row=0; data.clear(); current=QString(); cancelJob(); emit status(QString()); } void StreamFetcher::dataReady() { NetworkJob *reply=qobject_cast(sender()); if (reply!=job) { return; } data+=job->readAll(); if (data.count()>constMaxData) { NetworkJob *thisJob=job; jobFinished(thisJob); // If jobFinished did not redirect, then we need to ensure job is cancelled. if (thisJob==job) { cancelJob(); } } } void StreamFetcher::jobFinished() { NetworkJob *reply=qobject_cast(sender()); if (reply) { jobFinished(reply); } } void StreamFetcher::jobFinished(NetworkJob *reply) { // We only handle 1 job at a time! if (reply==job) { bool redirected=false; if (!reply->error()) { QString u=parse(data); if (u.isEmpty() || u==current) { DBUG << "use (empty/current)" << current; done.append(MPDParseUtils::addStreamName(current.startsWith(StreamsModel::constPrefix) ? current.mid(StreamsModel::constPrefix.length()) : current, currentName)); } else if (u.startsWith(QLatin1String("http://")) && ++redirectsget(u, constTimeout); connect(job, SIGNAL(readyRead()), this, SLOT(dataReady())); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); redirected=true; } else { DBUG << "use" << u; done.append(MPDParseUtils::addStreamName(u, currentName)); } } else { DBUG << "error " << reply->errorString() << " - use" << current; done.append(MPDParseUtils::addStreamName(current.startsWith(StreamsModel::constPrefix) ? current.mid(StreamsModel::constPrefix.length()) : current, currentName)); } if (!redirected) { doNext(); } } reply->deleteLater(); } void StreamFetcher::cancelJob() { if (job) { disconnect(job, SIGNAL(readyRead()), this, SLOT(dataReady())); disconnect(job, SIGNAL(finished()), this, SLOT(jobFinished())); job->cancelAndDelete(); job=0; } } cantata-2.2.0/streams/streamfetcher.h000066400000000000000000000035211316350454000176130ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMFETCHER_H #define STREAMFETCHER_H #include #include #include #include class NetworkJob; class StreamFetcher : public QObject { Q_OBJECT public: static void enableDebug(); StreamFetcher(QObject *p); virtual ~StreamFetcher(); void get(const QStringList &items, int insertRow, int action, quint8 priority, bool decPriority); private: void doNext(); public Q_SLOTS: void cancel(); Q_SIGNALS: void result(const QStringList &items, int insertRow, int action, quint8 priority, bool decreasePriority); void status(const QString &msg); private Q_SLOTS: void dataReady(); void jobFinished(); private: void jobFinished(NetworkJob *reply); void cancelJob(); private: NetworkJob *job; QString current; QString currentName; QStringList todo; QStringList done; int row; int playQueueAction; quint8 prio; bool decreasePriority; int redirects; QByteArray data; }; #endif cantata-2.2.0/streams/streamproviderlistdialog.cpp000066400000000000000000000356561316350454000224520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamproviderlistdialog.h" #include "streamssettings.h" #include "network/networkaccessmanager.h" #include "widgets/basicitemdelegate.h" #include "support/messagebox.h" #include "support/squeezedtextlabel.h" #include "support/spinner.h" #include "support/monoicon.h" #include "widgets/actionitemdelegate.h" #include "widgets/messageoverlay.h" #include "models/streamsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //#define TEST_PROVIDERS static const QLatin1String constProviderBaseUrl("https://raw.githubusercontent.com/CDrummond/cantata-extra/master/streams/2.1/"); static QString fileMd5(const QString &fileName) { QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { return QString::fromLatin1(QCryptographicHash::hash(f.readAll(), QCryptographicHash::Md5).toHex()); } return QString(); } static QString getMd5(const QString &p) { QString dir=Utils::dataDir(StreamsModel::constSubDir, false); if (dir.isEmpty()) { return QString(); } dir+=Utils::constDirSep+p+Utils::constDirSep; if (QFile::exists(dir+StreamsModel::constSettingsFile)) { return fileMd5(dir+StreamsModel::constSettingsFile); } if (QFile::exists(dir+StreamsModel::constCompressedXmlFile)) { return fileMd5(dir+StreamsModel::constCompressedXmlFile); } return QString(); } class IconLabel : public QToolButton { public: IconLabel(const QIcon &icon, const QString &text, QWidget *p) : QToolButton(p) { setIcon(icon); setText(text); setAutoRaise(true); setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QFont f(font()); f.setItalic(true); setFont(f); setStyleSheet("QToolButton {border: 0}"); setFocusPolicy(Qt::NoFocus); } void paintEvent(QPaintEvent *) { QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); opt.state&=~(QStyle::State_MouseOver|QStyle::State_Sunken|QStyle::State_On); opt.state|=QStyle::State_Raised; p.drawComplexControl(QStyle::CC_ToolButton, opt); } }; StreamProviderListDialog::StreamProviderListDialog(StreamsSettings *parent) : Dialog(parent, "StreamProviderListDialog") , installed(MonoIcon::icon(FontAwesome::check, QColor(0, 220, 0))) , updateable(MonoIcon::icon(FontAwesome::angledoubledown, QColor(0, 0, 220))) , p(parent) , job(0) , spinner(0) , msgOverlay(0) { QWidget *wid=new QWidget(this); QBoxLayout *l=new QBoxLayout(QBoxLayout::TopToBottom, wid); QWidget *legends=new QWidget(wid); QBoxLayout *legendsLayout=new QBoxLayout(QBoxLayout::LeftToRight, legends); legendsLayout->setMargin(0); tree=new QTreeWidget(wid); tree->setItemDelegate(new BasicItemDelegate(this)); tree->header()->setVisible(false); tree->header()->setStretchLastSection(true); tree->setSelectionMode(QAbstractItemView::ExtendedSelection); statusText=new SqueezedTextLabel(wid); progress=new QProgressBar(wid); legendsLayout->addWidget(new IconLabel(installed, tr("Installed"), legends)); legendsLayout->addItem(new QSpacerItem(l->spacing(), 0, QSizePolicy::Fixed, QSizePolicy::Fixed)); legendsLayout->addWidget(new IconLabel(updateable, tr("Update available"), legends)); legendsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); l->addWidget(new QLabel(tr("Check the providers you wish to install/update."), wid)); l->addWidget(tree); l->addWidget(legends); l->addItem(new QSpacerItem(0, l->spacing(), QSizePolicy::Fixed, QSizePolicy::Fixed)); l->addWidget(statusText); l->addWidget(progress); l->setMargin(0); connect(tree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(itemChanged(QTreeWidgetItem*,int))); setMainWidget(wid); setButtons(User1|User2|Close); enableButton(User1, false); enableButton(User2, false); setDefaultButton(Close); setCaption(tr("Install/Update Stream Providers")); } StreamProviderListDialog::~StreamProviderListDialog() { if (job) { disconnect(job, SIGNAL(finished()), this, SLOT(jobFinished())); job->cancelAndDelete(); job=0; } } void StreamProviderListDialog::show(const QSet &installed) { installedProviders.clear(); foreach (const QString &inst, installed) { installedProviders.insert(inst, getMd5(inst)); } processItems.clear(); checkedItems.clear(); setState(false); updateView(true); if (!tree->topLevelItemCount()) { QTimer::singleShot(0, this, SLOT(getProviderList())); } exec(); } void StreamProviderListDialog::getProviderList() { if (!spinner) { spinner=new Spinner(this); spinner->setWidget(tree); } if (!msgOverlay) { msgOverlay=new MessageOverlay(this); msgOverlay->setWidget(tree); } #ifdef TEST_PROVIDERS QFile f("list.xml"); if (f.open(QIODevice::ReadOnly)) { readProviders(&f); } #else job=NetworkAccessManager::self()->get(QUrl(constProviderBaseUrl+"list.xml")); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); spinner->start(); msgOverlay->setText(tr("Downloading list..."), -1, false); #endif } enum Categories { Cat_General = 0, Cat_DigitallyImported = 1, Cat_ListenLive = 2, Cat_Total }; enum Roles { Role_Url = Qt::UserRole, Role_Md5, Role_Updateable, Role_Category }; static QString catName(int cat) { switch (cat) { default: case Cat_General: return QObject::tr("General"); case Cat_DigitallyImported: return QObject::tr("Digitally Imported"); case Cat_ListenLive: return QObject::tr("Local and National Radio (ListenLive)"); } } void StreamProviderListDialog::jobFinished() { NetworkJob *j=qobject_cast(sender()); if (!j) { return; } j->deleteLater(); if (j!=job) { return; } job=0; if (0==tree->topLevelItemCount()) { if (j->ok()) { readProviders(j->actualJob()); } else { MessageBox::error(this, tr("Failed to download list of stream providers!")); slotButtonClicked(Close); } if (spinner) { spinner->stop(); } if (msgOverlay) { msgOverlay->setText(QString()); } } else { QTreeWidgetItem *item=*(processItems.begin()); if (j->ok()) { statusText->setText(tr("Installing/updating %1").arg(item->text(0))); QTemporaryFile temp(QDir::tempPath()+"/cantata_XXXXXX.streams"); temp.setAutoRemove(true); temp.open(); temp.write(j->readAll()); temp.close(); if (!p->install(temp.fileName(), item->text(0), false)) { MessageBox::error(this, tr("Failed to install '%1'").arg(item->text(0))); setState(false); } else { item->setCheckState(0, Qt::Unchecked); processItems.removeAll(item); progress->setValue(progress->value()+1); doNext(); } } else { MessageBox::error(this, tr("Failed to download '%1'").arg(item->text(0))); setState(false); } } } void StreamProviderListDialog::itemChanged(QTreeWidgetItem *itm, int col) { Q_UNUSED(col) if (Qt::Checked==itm->checkState(0)) { checkedItems.insert(itm); } else { checkedItems.remove(itm); } enableButton(User1, !checkedItems.isEmpty()); } void StreamProviderListDialog::readProviders(QIODevice *dev) { QMap categories; QXmlStreamReader doc(dev); int currentCat=-1; while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (QLatin1String("category")==doc.name()) { currentCat=doc.attributes().value("type").toString().toInt(); } else if (QLatin1String("provider")==doc.name()) { QString name=doc.attributes().value("name").toString(); if (!name.isEmpty() && currentCat>=0 && currentCatsetFlags(Qt::ItemIsEnabled); QFont f=tree->font(); f.setBold(true); cat->setFont(0, f); categories.insert(currentCat, cat); } else { cat=categories[currentCat]; } QTreeWidgetItem *prov=new QTreeWidgetItem(cat, QStringList() << name); prov->setData(0, Role_Url, url); prov->setData(0, Role_Md5, doc.attributes().value("md5").toString()); prov->setData(0, Role_Updateable, false); prov->setData(0, Role_Category, currentCat); prov->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsUserCheckable); prov->setCheckState(0, Qt::Unchecked); cat->setExpanded(true); } } } } updateView(); } void StreamProviderListDialog::slotButtonClicked(int button) { switch (button) { case User2: case User1: { bool update=false; bool install=false; processItems.clear(); if (User2==button) { processItems.clear(); for (int tl=0; tltopLevelItemCount(); ++tl) { QTreeWidgetItem *tli=tree->topLevelItem(tl); for (int c=0; cchildCount(); ++c) { QTreeWidgetItem *ci=tli->child(c); if (ci->data(0, Role_Updateable).toBool()) { update=true; processItems.append(ci); } } } } else { foreach (QTreeWidgetItem *i, checkedItems) { if (installedProviders.keys().contains(i->text(0))) { update=true; } else { install=true; } processItems.append(i); } } QString message; if (install && update) { message=tr("Install/update the selected stream providers?"); } else if (install) { message=tr("Install the selected stream providers?"); } else if (update) { message=tr("Update the selected stream providers?"); } if (!message.isEmpty() && MessageBox::Yes==MessageBox::questionYesNo(this, message, tr("Install/Update"))) { setState(true); doNext(); } break; } case Cancel: if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Abort installation/update?"), tr("Abort"))) { if (job) { disconnect(job, SIGNAL(finished()), this, SLOT(jobFinished())); job->cancelAndDelete(); job=0; } reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); } break; case Close: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } void StreamProviderListDialog::updateView(bool unCheck) { QHash::ConstIterator end=installedProviders.end(); int numUpdates=0; for (int tl=0; tltopLevelItemCount(); ++tl) { QTreeWidgetItem *tli=tree->topLevelItem(tl); for (int c=0; cchildCount(); ++c) { bool update=false; QTreeWidgetItem *ci=tli->child(c); QHash::ConstIterator inst=installedProviders.find(ci->text(0)); if (inst!=end) { update=inst.value()!=ci->data(0, Role_Md5).toString(); ci->setData(0, Qt::DecorationRole, update ? updateable : installed); if (update) { numUpdates++; } } else { ci->setData(0, Qt::DecorationRole, def); } if (unCheck) { ci->setCheckState(0, Qt::Unchecked); } ci->setData(0, Role_Updateable, update); } } updateText=0==numUpdates ? QString() : tr("%n Update(s) available", "", numUpdates); setState(false); } void StreamProviderListDialog::doNext() { if (processItems.isEmpty()) { accept(); } else { QTreeWidgetItem *item=*(processItems.begin()); statusText->setText(tr("Downloading %1").arg(item->text(0))); job=NetworkAccessManager::self()->get(QUrl(item->data(0, Role_Url).toString())); connect(job, SIGNAL(finished()), this, SLOT(jobFinished())); } } void StreamProviderListDialog::setState(bool downloading) { tree->setEnabled(!downloading); progress->setVisible(downloading); statusText->setVisible(downloading || !updateText.isEmpty()); if (downloading) { setButtons(Cancel); progress->setRange(0, processItems.count()); progress->setValue(0); } else { setButtons(User1|User2|Close); setButtonText(User1, tr("Install/Update")); enableButton(User1, !checkedItems.isEmpty()); setButtonText(User2, tr("Update all updateable providers")); enableButton(User2, !updateText.isEmpty()); setDefaultButton(Close); statusText->setText(updateText); } } cantata-2.2.0/streams/streamproviderlistdialog.h000066400000000000000000000041401316350454000220770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAM_PROVIDER_LIST_DIALOG_H #define STREAM_PROVIDER_LIST_DIALOG_H #include "support/dialog.h" #include "support/icon.h" #include #include class NetworkJob; class QTreeWidget; class QTreeWidgetItem; class QProgressBar; class SqueezedTextLabel; class StreamsSettings; class Spinner; class MessageOverlay; class QIODevice; class StreamProviderListDialog : public Dialog { Q_OBJECT public: StreamProviderListDialog(StreamsSettings *parent); virtual ~StreamProviderListDialog(); void show(const QSet &installed); private Q_SLOTS: void getProviderList(); void jobFinished(); void itemChanged(QTreeWidgetItem *itm, int col); private: void readProviders(QIODevice *dev); void slotButtonClicked(int button); void updateView(bool unCheck=false); void doNext(); void setState(bool downloading); private: QIcon def; QIcon installed; QIcon updateable; StreamsSettings *p; NetworkJob *job; Spinner *spinner; MessageOverlay *msgOverlay; QTreeWidget *tree; QProgressBar *progress; QString updateText; SqueezedTextLabel *statusText; QHash installedProviders; QList processItems; QSet checkedItems; }; #endif cantata-2.2.0/streams/streamspage.cpp000066400000000000000000000574551316350454000176440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamspage.h" #include "streamdialog.h" #include "streamssettings.h" #include "mpd-interface/mpdconnection.h" #include "support/messagebox.h" #include "widgets/icons.h" #include "gui/stdactions.h" #include "support/actioncollection.h" #include "network/networkaccessmanager.h" #include "support/configuration.h" #include "support/monoicon.h" #include "gui/settings.h" #include "widgets/menubutton.h" #include "widgets/itemview.h" #include "widgets/servicestatuslabel.h" #include "models/digitallyimported.h" #include "models/playqueuemodel.h" #include "digitallyimportedsettings.h" #include #include #include #include #include #include static const int constMsgDisplayTime=1500; static const char *constNameProperty="name"; StreamsPage::StreamsPage(QWidget *p) : StackedPageWidget(p) { qRegisterMetaType("StreamItem"); qRegisterMetaType >("QList"); browse=new StreamsBrowsePage(this); addWidget(browse); connect(browse, SIGNAL(close()), this, SIGNAL(close())); connect(browse, SIGNAL(searchForStreams()), this, SLOT(searchForStreams())); search=new StreamSearchPage(this); addWidget(search); connect(search, SIGNAL(close()), this, SLOT(closeSearch())); disconnect(browse, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool))); disconnect(search, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool))); connect(browse, SIGNAL(add(const QStringList &, int, quint8, bool)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, int, quint8, bool))); connect(search, SIGNAL(add(const QStringList &, int, quint8, bool)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, int, quint8, bool))); connect(StreamsModel::self()->addToFavouritesAct(), SIGNAL(triggered()), this, SLOT(addToFavourites())); connect(search, SIGNAL(addToFavourites(QList)), browse, SLOT(addToFavourites(QList))); } StreamsPage:: ~StreamsPage() { } void StreamsPage::searchForStreams() { setCurrentWidget(search); } void StreamsPage::closeSearch() { setCurrentWidget(browse); } void StreamsPage::addToFavourites() { QWidget *w=currentWidget(); if (browse==w) { browse->addToFavourites(); } else if (search==w) { search->addToFavourites(); } } StreamsBrowsePage::StreamsBrowsePage(QWidget *p) : SinglePageWidget(p) , settings(0) { QColor iconCol=Utils::monoIconColor(); importAction = new Action(MonoIcon::icon(FontAwesome::arrowright, iconCol), tr("Import Streams Into Favorites"), this); exportAction = new Action(MonoIcon::icon(FontAwesome::arrowleft, iconCol), tr("Export Favorite Streams"), this); addAction = ActionCollection::get()->createAction("addstream", tr("Add New Stream To Favorites")); editAction = new Action(Icons::self()->editIcon, tr("Edit"), this); searchAction = new Action(Icons::self()->searchIcon, tr("Seatch For Streams"), this); connect(searchAction, SIGNAL(triggered()), this, SIGNAL(searchForStreams())); // connect(view, SIGNAL(itemsSelected(bool)), addToPlaylist, SLOT(setEnabled(bool))); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions())); connect(addAction, SIGNAL(triggered()), this, SLOT(addStream())); connect(StreamsModel::self()->addBookmarkAct(), SIGNAL(triggered()), this, SLOT(addBookmark())); connect(StreamsModel::self()->configureDiAct(), SIGNAL(triggered()), this, SLOT(configureDi())); connect(StreamsModel::self()->reloadAct(), SIGNAL(triggered()), this, SLOT(reload())); connect(editAction, SIGNAL(triggered()), this, SLOT(edit())); connect(importAction, SIGNAL(triggered()), this, SLOT(importXml())); connect(exportAction, SIGNAL(triggered()), this, SLOT(exportXml())); connect(StreamsModel::self(), SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &))); connect(StreamsModel::self(), SIGNAL(loading()), view, SLOT(showSpinner())); connect(StreamsModel::self(), SIGNAL(loaded()), view, SLOT(hideSpinner())); connect(StreamsModel::self(), SIGNAL(categoriesChanged()), view, SLOT(closeSearch())); connect(StreamsModel::self(), SIGNAL(favouritesLoaded()), SLOT(expandFavourites())); connect(StreamsModel::self(), SIGNAL(addedToFavourites(QString)), SLOT(addedToFavourites(QString))); connect(DigitallyImported::self(), SIGNAL(loginStatus(bool,QString)), SLOT(updateDiStatus())); connect(DigitallyImported::self(), SIGNAL(updated()), SLOT(updateDiStatus())); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); StreamsModel::self()->configureDiAct()->setEnabled(false); proxy.setSourceModel(StreamsModel::self()); view->setModel(&proxy); view->setDeleteAction(StdActions::self()->removeAction); view->setSearchResetLevel(1); view->alwaysShowHeader(); Configuration config(metaObject()->className()); view->setMode(ItemView::Mode_DetailedTree); view->load(config); MenuButton *menuButton=new MenuButton(this); Action *configureAction=new Action(Icons::self()->configureIcon, tr("Configure"), this); connect(configureAction, SIGNAL(triggered()), SLOT(configure())); menuButton->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); menuButton->addAction(configureAction); menuButton->addAction(StreamsModel::self()->configureDiAct()); menuButton->addSeparator(); menuButton->addAction(addAction); menuButton->addAction(StdActions::self()->removeAction); menuButton->addAction(editAction); menuButton->addAction(StreamsModel::self()->reloadAct()); menuButton->addSeparator(); menuButton->addAction(importAction); menuButton->addAction(exportAction); diStatusLabel=new ServiceStatusLabel(this); diStatusLabel->setText("DI", tr("Digitally Imported", "Service name")); connect(diStatusLabel, SIGNAL(clicked()), SLOT(diSettings())); updateDiStatus(); ToolButton *searchButton=new ToolButton(this); searchButton->setDefaultAction(searchAction); init(ReplacePlayQueue, QList() << menuButton << diStatusLabel, QList() << searchButton); view->addAction(editAction); view->addAction(StdActions::self()->removeAction); view->addAction(StreamsModel::self()->addToFavouritesAct()); view->addAction(StreamsModel::self()->addBookmarkAct()); view->addAction(StreamsModel::self()->reloadAct()); } StreamsBrowsePage::~StreamsBrowsePage() { foreach (NetworkJob *job, resolveJobs) { disconnect(job, SIGNAL(finished()), this, SLOT(tuneInResolved())); job->deleteLater(); } resolveJobs.clear(); Configuration config(metaObject()->className()); view->save(config); } void StreamsBrowsePage::showEvent(QShowEvent *e) { view->focusView(); QWidget::showEvent(e); } void StreamsBrowsePage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { Q_UNUSED(name) addItemsToPlayQueue(view->selectedIndexes(), action, priorty, decreasePriority); } void StreamsBrowsePage::addItemsToPlayQueue(const QModelIndexList &indexes, int action, quint8 priorty, bool decreasePriority) { if (indexes.isEmpty()) { return; } QModelIndexList mapped; foreach (const QModelIndex &idx, indexes) { mapped.append(proxy.mapToSource(idx)); } QStringList files=StreamsModel::self()->filenames(mapped, true); if (!files.isEmpty()) { emit add(files, action, priorty, decreasePriority); view->clearSelection(); } } void StreamsBrowsePage::itemDoubleClicked(const QModelIndex &index) { if (!static_cast(proxy.mapToSource(index).internalPointer())->isCategory()) { QModelIndexList indexes; indexes.append(index); addItemsToPlayQueue(indexes, false); } } void StreamsBrowsePage::configure() { if (!settings) { settings=new StreamsSettings(this); } if (!settings->isVisible()) { settings->load(); } settings->show(); settings->raiseWindow(); } void StreamsBrowsePage::configureDi() { QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } const StreamsModel::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (item->isCategory() && static_cast(item)->isDi()) { diSettings(); } } void StreamsBrowsePage::diSettings() { DigitallyImportedSettings(this).show(); } void StreamsBrowsePage::importXml() { QString fileName=QFileDialog::getOpenFileName(this, tr("Import Streams"), QDir::homePath(), tr("XML Streams (*.xml *.xml.gz *.cantata)")); if (fileName.isEmpty()) { return; } StreamsModel::self()->importIntoFavourites(fileName); } void StreamsBrowsePage::exportXml() { QLatin1String ext(".xml.gz"); QString fileName=QFileDialog::getSaveFileName(this, tr("Export Streams"), QDir::homePath()+QLatin1String("/streams")+ext, tr("XML Streams (*.xml.gz)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(ext)) { fileName+=ext; } if (!StreamsModel::self()->exportFavourites(fileName)) { MessageBox::error(this, tr("Failed to create '%1'!").arg(fileName)); } } void StreamsBrowsePage::addStream() { StreamDialog dlg(this); if (QDialog::Accepted==dlg.exec()) { QString name=dlg.name(); QString url=dlg.url(); QString existingNameForUrl=StreamsModel::self()->favouritesNameForUrl(url); if (!existingNameForUrl.isEmpty()) { MessageBox::error(this, tr("Stream '%1' already exists!").arg(existingNameForUrl)); } else if (StreamsModel::self()->nameExistsInFavourites(name)) { MessageBox::error(this, tr("A stream named '%1' already exists!").arg(name)); } else { StreamsModel::self()->addToFavourites(url, name); } } } void StreamsBrowsePage::addBookmark() { QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } const StreamsModel::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); // TODO: In future, if other categories support bookmarking, then we will need to calculate parent here!!! if (StreamsModel::self()->addBookmark(item->url, item->name, 0)) { view->showMessage(tr("Bookmark added"), constMsgDisplayTime); } else { view->showMessage(tr("Already bookmarked"), constMsgDisplayTime); } } void StreamsBrowsePage::addToFavourites() { QModelIndexList selected = view->selectedIndexes(); QList items; foreach (const QModelIndex &i, selected) { QModelIndex mapped=proxy.mapToSource(i); const StreamsModel::Item *item=static_cast(mapped.internalPointer()); if (!item->isCategory() && item->parent && !item->parent->isFavourites()) { items.append(item); } } QList itemsToAdd; foreach (const StreamsModel::Item *item, items) { itemsToAdd.append(StreamItem(item->url, item->modifiedName())); } addToFavourites(itemsToAdd); } void StreamsBrowsePage::addToFavourites(const QList &items) { int added=0; foreach (const StreamItem item, items) { QUrl url(item.url); QUrlQuery query(url); query.removeQueryItem(QLatin1String("locale")); if (!query.isEmpty()) { url.setQuery(query); } QString urlStr=url.toString(); if (urlStr.endsWith('&')) { urlStr=urlStr.left(urlStr.length()-1); } if (urlStr.startsWith(QLatin1String("http://opml.radiotime.com/Tune.ashx"))) { NetworkJob *job=NetworkAccessManager::self()->get(urlStr, 5000); job->setProperty(constNameProperty, item.modifiedName); connect(job, SIGNAL(finished()), this, SLOT(tuneInResolved())); resolveJobs.insert(job); added++; } else if (StreamsModel::self()->addToFavourites(urlStr, item.modifiedName)) { added++; } } if (!added) { view->showMessage(tr("Already in favorites"), constMsgDisplayTime); } } void StreamsBrowsePage::tuneInResolved() { NetworkJob *job=qobject_cast(sender()); if (!job) { return; } job->deleteLater(); if (!resolveJobs.contains(job)) { return; } resolveJobs.remove(job); QString url=job->readAll().split('\n').first(); QString name=job->property(constNameProperty).toString(); if (!url.isEmpty() && !name.isEmpty() && !StreamsModel::self()->addToFavourites(url, name)) { view->showMessage(tr("Already in favorites"), constMsgDisplayTime); } } void StreamsBrowsePage::headerClicked(int level) { if (0==level) { emit close(); } } void StreamsBrowsePage::reload() { QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.count()) { return; } QModelIndex mapped=proxy.mapToSource(selected.first()); const StreamsModel::Item *item=static_cast(mapped.internalPointer()); if (!item->isCategory()) { return; } const StreamsModel::CategoryItem *cat=static_cast(item); if (!cat->canReload()) { return; } if (cat->children.isEmpty() || cat->cacheName.isEmpty() || MessageBox::Yes==MessageBox::questionYesNo(this, tr("Reload '%1' streams?").arg(cat->name))) { StreamsModel::self()->reload(mapped); } } void StreamsBrowsePage::removeItems() { QModelIndexList selected = view->selectedIndexes(); if (1==selected.count()) { QModelIndex mapped=proxy.mapToSource(selected.first()); const StreamsModel::Item *item=static_cast(mapped.internalPointer()); if (item->isCategory() && item->parent) { if (item->parent->isBookmarks) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove bookmark to '%1'?").arg(item->name))) { return; } StreamsModel::self()->removeBookmark(mapped); return; } else if (static_cast(item)->isBookmarks) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove all '%1' bookmarks?").arg(item->parent->name))) { return; } StreamsModel::self()->removeAllBookmarks(mapped); return; } } } QModelIndexList useable; foreach (const QModelIndex &i, selected) { QModelIndex mapped=proxy.mapToSource(i); const StreamsModel::Item *item=static_cast(mapped.internalPointer()); if (!item->isCategory() && item->parent && item->parent->isFavourites()) { useable.append(mapped); } } if (useable.isEmpty()) { return; } if (useable.size()>1) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the %1 selected streams?").arg(useable.size()))) { return; } } else { if (MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove '%1'?").arg( StreamsModel::self()->data(useable.first(), Qt::DisplayRole).toString()))) { return; } } StreamsModel::self()->removeFromFavourites(useable); } void StreamsBrowsePage::edit() { QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } QModelIndex index=proxy.mapToSource(selected.first()); StreamsModel::Item *item=static_cast(index.internalPointer()); if (item->isCategory() || !item->parent || !item->parent->isFavourites()) { return; } QString name=item->name; QString url=item->url; StreamDialog dlg(this); dlg.setEdit(name, url); if (QDialog::Accepted==dlg.exec()) { QString newName=dlg.name(); QString newUrl=dlg.url(); QString existingNameForUrl=newUrl!=url ? StreamsModel::self()->favouritesNameForUrl(newUrl) : QString(); if (!existingNameForUrl.isEmpty()) { MessageBox::error(this, tr("Stream '%1' already exists!").arg(existingNameForUrl)); } else if (newName!=name && StreamsModel::self()->nameExistsInFavourites(newName)) { MessageBox::error(this, tr("A stream named '%1' already exists!").arg(newName)); } else { StreamsModel::self()->updateFavouriteStream(newUrl, newName, index); } } } void StreamsBrowsePage::doSearch() { QString text=view->searchText().trimmed(); if (!view->isSearchActive()) { proxy.setFilterItem(0); } proxy.update(view->isSearchActive() ? text : QString()); if (proxy.enabled() && !text.isEmpty()) { view->expandAll(proxy.filterItem() ? proxy.mapFromSource(StreamsModel::self()->categoryIndex(static_cast(proxy.filterItem()))) : QModelIndex()); } } void StreamsBrowsePage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool haveSelection=!selected.isEmpty(); bool enableAddToFav=true; bool onlyStreamsSelected=true; StreamsModel::self()->addBookmarkAct()->setEnabled(false); editAction->setEnabled(false); StreamsModel::self()->reloadAct()->setEnabled(false); bool enableRemove=true; foreach (const QModelIndex &idx, selected) { const StreamsModel::Item *item=static_cast(proxy.mapToSource(idx).internalPointer()); if (item->isCategory() || (item->parent && !item->parent->isFavourites())) { enableRemove=false; } if (item->isCategory() || (item->parent && item->parent->isFavourites())) { enableAddToFav=false; } if (item->isCategory()) { onlyStreamsSelected=false; } if (!enableRemove && !enableAddToFav && !onlyStreamsSelected) { break; } } StdActions::self()->removeAction->setEnabled(haveSelection && enableRemove); StreamsModel::self()->addToFavouritesAct()->setEnabled(haveSelection && enableAddToFav); if (1==selected.size()) { const StreamsModel::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (!item->isCategory() && item->parent && item->parent->isFavourites()) { editAction->setEnabled(true); } StreamsModel::self()->reloadAct()->setEnabled(item->isCategory() && static_cast(item)->canReload()); StreamsModel::self()->addBookmarkAct()->setEnabled(item->isCategory() && static_cast(item)->canBookmark); if (!StdActions::self()->removeAction->isEnabled()) { StdActions::self()->removeAction->setEnabled(item->isCategory() && item->parent && (item->parent->isBookmarks || (static_cast(item)->isBookmarks))); } StreamsModel::self()->configureDiAct()->setEnabled(item->isCategory() && static_cast(item)->isDi()); } else { StreamsModel::self()->configureDiAct()->setEnabled(false); } StdActions::self()->replacePlayQueueAction->setEnabled(haveSelection && onlyStreamsSelected); } void StreamsBrowsePage::updateDiStatus() { if (DigitallyImported::self()->user().isEmpty() || DigitallyImported::self()->pass().isEmpty()) { diStatusLabel->setVisible(false); } else { diStatusLabel->setStatus(DigitallyImported::self()->loggedIn()); } } void StreamsBrowsePage::expandFavourites() { view->expand(proxy.mapFromSource(StreamsModel::self()->favouritesIndex()), true); } void StreamsBrowsePage::addedToFavourites(const QString &name) { view->showMessage(tr("Added '%1'' to favorites").arg(name), constMsgDisplayTime); } StreamSearchPage::StreamSearchPage(QWidget *p) : SinglePageWidget(p) { proxy.setSourceModel(&model); view->setModel(&proxy); view->alwaysShowHeader(); view->setPermanentSearch(); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); view->setMode(ItemView::Mode_DetailedTree); init(ReplacePlayQueue); connect(StreamsModel::self(), SIGNAL(addedToFavourites(QString)), SLOT(addedToFavourites(QString))); } StreamSearchPage::~StreamSearchPage() { } void StreamSearchPage::showEvent(QShowEvent *e) { SinglePageWidget::showEvent(e); view->focusSearch(); } void StreamSearchPage::headerClicked(int level) { if (0==level) { emit close(); } } void StreamSearchPage::doSearch() { model.search(view->searchText().trimmed(), false); } void StreamSearchPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { Q_UNUSED(name) QModelIndexList indexes=view->selectedIndexes(); if (indexes.isEmpty()) { return; } QModelIndexList mapped; foreach (const QModelIndex &idx, indexes) { mapped.append(proxy.mapToSource(idx)); } QStringList files=StreamsModel::self()->filenames(mapped, true); if (!files.isEmpty()) { emit add(files, action, priorty, decreasePriority); view->clearSelection(); } } void StreamSearchPage::addToFavourites() { QModelIndexList selected = view->selectedIndexes(); QList items; foreach (const QModelIndex &i, selected) { QModelIndex mapped=proxy.mapToSource(i); const StreamsModel::Item *item=static_cast(mapped.internalPointer()); if (!item->isCategory() && item->parent && !item->parent->isFavourites()) { items.append(item); } } QList itemsToAdd; foreach (const StreamsModel::Item *item, items) { itemsToAdd.append(StreamItem(item->url, item->modifiedName())); } emit addToFavourites(itemsToAdd); } void StreamSearchPage::addedToFavourites(const QString &name) { view->showMessage(tr("Added '%1'' to favorites").arg(name), constMsgDisplayTime); } cantata-2.2.0/streams/streamspage.h000066400000000000000000000074051316350454000172770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMSPAGE_H #define STREAMSPAGE_H #include "widgets/singlepagewidget.h" #include "widgets/stackedpagewidget.h" #include "models/streamsproxymodel.h" #include "models/streamsearchmodel.h" #include "models/streamsmodel.h" #include "gui/page.h" #include class Action; class QAction; class NetworkReply; class ServiceStatusLabel; class StreamsSettings; struct StreamItem { StreamItem(const QString &u=QString(), const QString &mn=QString()) : url(u), modifiedName(mn) { } QString url; QString modifiedName; }; class StreamsBrowsePage : public SinglePageWidget { Q_OBJECT public: StreamsBrowsePage(QWidget *p); virtual ~StreamsBrowsePage(); void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); void showEvent(QShowEvent *e); Q_SIGNALS: void error(const QString &str); void showPreferencesPage(const QString &page); void searchForStreams(); public Q_SLOTS: void removeItems(); void controlActions(); void addToFavourites(const QList &items); private Q_SLOTS: void configure(); void configureDi(); void diSettings(); void importXml(); void exportXml(); void addStream(); void addBookmark(); void reload(); void edit(); void itemDoubleClicked(const QModelIndex &index); void updateDiStatus(); void expandFavourites(); void addedToFavourites(const QString &name); void tuneInResolved(); void headerClicked(int level); private: void doSearch(); void addItemsToPlayQueue(const QModelIndexList &indexes, int action, quint8 priorty=0, bool decreasePriority=false); void addToFavourites(); private: ServiceStatusLabel *diStatusLabel; Action *importAction; Action *exportAction; Action *addAction; Action *editAction; Action *searchAction; StreamsProxyModel proxy; QSet resolveJobs; StreamsSettings *settings; friend class StreamsPage; }; class StreamSearchPage : public SinglePageWidget { Q_OBJECT public: StreamSearchPage(QWidget *p); virtual ~StreamSearchPage(); void showEvent(QShowEvent *e); Q_SIGNALS: void addToFavourites(const QList &items); private Q_SLOTS: void headerClicked(int level); void addedToFavourites(const QString &name); private: void doSearch(); void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); void addToFavourites(); private: StreamsProxyModel proxy; StreamSearchModel model; friend class StreamsPage; }; class StreamsPage : public StackedPageWidget { Q_OBJECT public: StreamsPage(QWidget *p); virtual ~StreamsPage(); private Q_SLOTS: void searchForStreams(); void closeSearch(); void addToFavourites(); private: StreamsBrowsePage *browse; StreamSearchPage *search; }; #endif cantata-2.2.0/streams/streamssettings.cpp000066400000000000000000000255361316350454000205630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "streamssettings.h" #include "models/streamsmodel.h" #include "streamproviderlistdialog.h" #include "widgets/basicitemdelegate.h" #include "widgets/icons.h" #include "support/icon.h" #include "tar.h" #include "support/messagebox.h" #include "support/utils.h" #include "digitallyimportedsettings.h" #include #include #include #include #include enum Roles { KeyRole = Qt::UserRole, BuiltInRole, ConfigurableRole }; static bool removeDir(const QString &d, const QStringList &types) { QDir dir(d); if (dir.exists()) { QFileInfoList files=dir.entryInfoList(types, QDir::Files|QDir::NoDotAndDotDot); foreach (const QFileInfo &file, files) { if (!QFile::remove(file.absoluteFilePath())) { return false; } } return dir.rmdir(d); } return true; // Does not exist... } StreamsSettings::StreamsSettings(QWidget *p) : Dialog(p, "StreamsDialog", QSize(400, 500)) , providerDialog(0) { setCaption(tr("Configure Streams")); QWidget *mw=new QWidget(this); setupUi(mw); setMainWidget(mw); categories->setItemDelegate(new BasicItemDelegate(categories)); categories->setSortingEnabled(true); int iSize=Icon::stdSize(QApplication::fontMetrics().height()*1.25); QMenu *installMenu=new QMenu(this); QAction *installFromFileAct=installMenu->addAction(tr("From File...")); QAction *installFromWebAct=installMenu->addAction(tr("Download...")); categories->setIconSize(QSize(iSize, iSize)); connect(categories, SIGNAL(currentRowChanged(int)), SLOT(currentCategoryChanged(int))); connect(installFromFileAct, SIGNAL(triggered()), this, SLOT(installFromFile())); connect(installFromWebAct, SIGNAL(triggered()), this, SLOT(installFromWeb())); setButtons(Close|User1|User2|User3); setButtonGuiItem(User1, GuiItem(tr("Configure Provider"))); setButtonGuiItem(User2, GuiItem(tr("Install"))); setButtonGuiItem(User3, GuiItem(tr("Remove"))); setButtonMenu(User2, installMenu, InstantPopup); enableButton(User3, false); enableButton(User1, false); } void StreamsSettings::load() { QList cats=StreamsModel::self()->getCategories(); QFont f(font()); f.setItalic(true); categories->clear(); foreach (const StreamsModel::Category &cat, cats) { QListWidgetItem *item=new QListWidgetItem(cat.name, categories); item->setCheckState(cat.hidden ? Qt::Unchecked : Qt::Checked); item->setData(KeyRole, cat.key); item->setData(BuiltInRole, cat.builtin); item->setData(ConfigurableRole, cat.configurable); item->setIcon(cat.icon); if (cat.builtin) { item->setFont(f); } } } void StreamsSettings::save() { QSet disabled; for (int i=0; icount(); ++i) { QListWidgetItem *item=categories->item(i); if (Qt::Unchecked==item->checkState()) { disabled.insert(item->data(Qt::UserRole).toString()); } } StreamsModel::self()->setHiddenCategories(disabled); } void StreamsSettings::currentCategoryChanged(int row) { bool enableRemove=false; bool enableConfigure=false; if (row>=0) { QListWidgetItem *item=categories->item(row); enableRemove=!item->data(BuiltInRole).toBool(); enableConfigure=item->data(ConfigurableRole).toBool(); } enableButton(User3, enableRemove); enableButton(User1, enableConfigure); } void StreamsSettings::installFromFile() { QString fileName=QFileDialog::getOpenFileName(this, tr("Install Streams"), QDir::homePath(), tr("Cantata Streams (*.streams)")); if (fileName.isEmpty()) { return; } QString name=QFileInfo(fileName).baseName(); if (name.isEmpty()) { return; } name=name.replace(Utils::constDirSep, "_"); #ifdef Q_OS_WIN name=name.replace("\\", "_"); #endif if (get(name) && MessageBox::No==MessageBox::warningYesNo(this, tr("A category named '%1' already exists!\n\nOverwrite?").arg(name))) { return; } install(fileName, name); } void StreamsSettings::installFromWeb() { if (!providerDialog) { providerDialog=new StreamProviderListDialog(this); } QSet installed; for (int i=0; icount(); ++i) { QListWidgetItem *item=categories->item(i); installed.insert(item->text()); } providerDialog->show(installed); #ifdef Q_OS_MAC // Under OSX when stream providers are installed/updated, and the dialog closed, it // puts the pref dialog below the main window! This hack fixes this... QTimer::singleShot(0, this, SLOT(raiseWindow())); #endif } bool StreamsSettings::install(const QString &fileName, const QString &name, bool showErrors) { Tar tar(fileName); if (!tar.open()) { if (showErrors) { MessageBox::error(this, tr("Failed top open package file.")); } return false; } QMap files=tar.extract(QStringList() << StreamsModel::constXmlFile << StreamsModel::constCompressedXmlFile << StreamsModel::constSettingsFile << StreamsModel::constPngIcon << StreamsModel::constSvgIcon << ".png" << ".svg"); QString streamsName=files.contains(StreamsModel::constCompressedXmlFile) ? StreamsModel::constCompressedXmlFile : files.contains(StreamsModel::constXmlFile) ? StreamsModel::constXmlFile : StreamsModel::constSettingsFile; QString iconName=files.contains(StreamsModel::constSvgIcon) ? StreamsModel::constSvgIcon : StreamsModel::constPngIcon; QByteArray streamFile=files[streamsName]; QByteArray icon=files[iconName]; if (streamFile.isEmpty()) { if (showErrors) { MessageBox::error(this, tr("Invalid file format!")); } return false; } QString streamsDir=Utils::dataDir(StreamsModel::constSubDir, true); QString dir=streamsDir+name; if (!QDir(dir).exists() && !QDir(dir).mkpath(dir)) { if (showErrors) { MessageBox::error(this, tr("Failed to create stream category folder!")); } return false; } QFile streamsFile(dir+Utils::constDirSep+streamsName); if (!streamsFile.open(QIODevice::WriteOnly)) { if (showErrors) { MessageBox::error(this, tr("Failed to save stream list!")); } return false; } streamsFile.write(streamFile); streamsFile.close(); Icon icn; if (!icon.isEmpty()) { QFile iconFile(dir+Utils::constDirSep+iconName); if (iconFile.open(QIODevice::WriteOnly)) { iconFile.write(icon); iconFile.close(); icn.addFile(dir+Utils::constDirSep+iconName); } } // Write all other png and svg files... QMap::ConstIterator it=files.constBegin(); QMap::ConstIterator end=files.constEnd(); for (; it!=end; ++it) { if (it.key()!=iconName && (it.key().endsWith(".png") || it.key().endsWith(".svg"))) { QFile f(dir+Utils::constDirSep+it.key()); if (f.open(QIODevice::WriteOnly)) { f.write(it.value()); } } } StreamsModel::CategoryItem *cat=StreamsModel::self()->addInstalledProvider(name, icn, dir+Utils::constDirSep+streamsName, true); if (!cat) { if (showErrors) { MessageBox::error(this, tr("Invalid file format!")); } return false; } QListWidgetItem *existing=get(name); if (existing) { delete existing; } QListWidgetItem *item=new QListWidgetItem(name, categories); item->setCheckState(Qt::Checked); item->setData(KeyRole, cat->configName); item->setData(BuiltInRole, false); item->setData(ConfigurableRole, cat->isDi()); item->setIcon(icn); return true; } void StreamsSettings::remove() { int row=categories->currentRow(); if (row<0) { return; } QListWidgetItem *item=categories->item(row); if (!item->data(BuiltInRole).toBool() && MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove '%1'?").arg(item->text()))) { return; } QString dir=Utils::dataDir(StreamsModel::constSubDir); if (!dir.isEmpty() && !removeDir(dir+item->text(), QStringList() << StreamsModel::constXmlFile << StreamsModel::constCompressedXmlFile << StreamsModel::constSettingsFile << "*.png" << "*.svg")) { MessageBox::error(this, tr("Failed to remove streams folder!")); return; } StreamsModel::self()->removeInstalledProvider(item->data(KeyRole).toString()); delete item; } void StreamsSettings::configure() { int row=categories->currentRow(); if (row<0) { return; } QListWidgetItem *item=categories->item(row); if (!item->data(ConfigurableRole).toBool()) { return; } // TODO: Currently only digitally imported can be configured... DigitallyImportedSettings(this).show(); } void StreamsSettings::slotButtonClicked(int button) { switch (button) { case User1: configure(); break; case User3: remove(); break; case Close: reject(); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } void StreamsSettings::raiseWindow() { Utils::raiseWindow(topLevelWidget()); } QListWidgetItem * StreamsSettings::get(const QString &name) { for (int i=0; icount(); ++i) { QListWidgetItem *item=categories->item(i); if (!item->data(BuiltInRole).toBool() && item->text()==name) { return item; } } return 0; } cantata-2.2.0/streams/streamssettings.h000066400000000000000000000032531316350454000202200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STREAMS_SETTINGS_H #define STREAMS_SETTINGS_H #include "ui_streamssettings.h" #include "support/dialog.h" class QListWidgetItem; class StreamProviderListDialog; class StreamsSettings : public Dialog, private Ui::StreamsSettings { Q_OBJECT public: StreamsSettings(QWidget *p); virtual ~StreamsSettings() { } void load(); void save(); public Q_SLOTS: void raiseWindow(); private Q_SLOTS: void currentCategoryChanged(int row); void installFromFile(); void installFromWeb(); private: void slotButtonClicked(int button); void remove(); void configure(); bool install(const QString &fileName, const QString &name, bool showErrors=true); QListWidgetItem * get(const QString &name); private: friend class StreamProviderListDialog; StreamProviderListDialog *providerDialog; }; #endif cantata-2.2.0/streams/streamssettings.ui000066400000000000000000000020311316350454000203770ustar00rootroot00000000000000 StreamsSettings 0 Use the checkboxes below to configure the list of active providers. true Built-in categories are shown in italic, and these cannot be removed. PlainNoteLabel QLabel
    widgets/notelabel.h
    cantata-2.2.0/streams/tar.cpp000066400000000000000000000074401316350454000161040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tar.h" #include "qtiocompressor/qtiocompressor.h" Tar::Tar(const QString &fileName) : file(fileName) , compressor(0) , dev(0) { } Tar::~Tar() { delete compressor; } bool Tar::open() { if (!file.open(QIODevice::ReadOnly)) { return false; } // Check for gzip header... QByteArray header=file.read(2); bool isCompressed=((unsigned char)header[0])==0x1f && ((unsigned char)header[1])==0x8b; file.seek(0); if (isCompressed) { compressor=new QtIOCompressor(&file); compressor->setStreamFormat(QtIOCompressor::GzipFormat); if (!compressor->open(QIODevice::ReadOnly)) { return false; } dev=compressor; } else { dev=&file; } return true; } static const qint64 constHeaderLen=512; static qint64 roundUp(qint64 sz) { return ((sz/constHeaderLen)*constHeaderLen)+((sz%constHeaderLen) ? constHeaderLen : 0); } struct TarHeader { TarHeader() : fileSize(0) { } bool ok() const { return fileSize>0 && !fileName.isEmpty(); } QString fileName; qint64 fileSize; }; static unsigned int octStrToInt(char *ch, unsigned int size) { unsigned int val = 0; while (size > 0){ val = (val * 8) + (*ch - '0'); ch++; size--; } return val; } static TarHeader readHeader(QIODevice *dev) { TarHeader header; char buffer[constHeaderLen]; qint64 bytesRead=dev->read(buffer, constHeaderLen); if (constHeaderLen==bytesRead && ('0'==buffer[156] || '\0'==buffer[156])) { buffer[100]='\0'; header.fileName=QFile::decodeName(buffer); header.fileSize=octStrToInt(&buffer[124], 11); } return header; } static QString getExt(const QString &fileName) { int pos=fileName.lastIndexOf("."); return -1!=pos ? fileName.mid(pos) : fileName; } QMap Tar::extract(const QStringList &files) { QMap data; if (!dev) { return data; } qint64 offset=0; qint64 pos=0; for (;;) { TarHeader header=readHeader(dev); if (header.ok()) { pos+=constHeaderLen; if (!data.contains(header.fileName) && (files.contains(header.fileName) || files.contains(getExt(header.fileName)))) { data[header.fileName]=dev->read(header.fileSize); pos+=header.fileSize; } offset+=constHeaderLen+header.fileSize; offset=roundUp(offset); if (dev->isSequential()) { static const qint64 constSkipBlock=1024; // Can't seek with QtIOCompressor - so fake this by reading and discarding while (posread(toRead); pos+=toRead; } } else { dev->seek(offset); } } else { break; } } return data; } cantata-2.2.0/streams/tar.h000066400000000000000000000023541316350454000155500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAR_H #define TAR_H #include #include #include #include #include #include class QtIOCompressor; class Tar { public: Tar(const QString &fileName); ~Tar(); bool open(); QMap extract(const QStringList &files); private: QFile file; QtIOCompressor *compressor; QIODevice *dev; }; #endif cantata-2.2.0/support/000077500000000000000000000000001316350454000146435ustar00rootroot00000000000000cantata-2.2.0/support/CMakeLists.txt000066400000000000000000000041321316350454000174030ustar00rootroot00000000000000set(SUPPORT_CORE_SRCS utils.cpp thread.cpp) set(SUPPORT_CORE_MOC_HDRS thread.h) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${QTINCLUDES}) qt5_wrap_cpp(SUPPORT_CORE_MOC_SRCS ${SUPPORT_CORE_MOC_HDRS}) add_library(support-core STATIC ${SUPPORT_CORE_MOC_SRCS} ${SUPPORT_CORE_SRCS}) set (SUPPORT_SRCS icon.cpp fancytabwidget.cpp messagewidget.cpp buddylabel.cpp action.cpp actioncollection.cpp lineedit.cpp configuration.cpp gtkstyle.cpp spinner.cpp messagebox.cpp inputdialog.cpp thread.cpp squeezedtextlabel.cpp proxystyle.cpp pagewidget.cpp combobox.cpp configdialog.cpp monoicon.cpp) set(SUPPORT_MOC_HDRS fancytabwidget.h messagewidget.h inputdialog.h pagewidget.h action.h actioncollection.h configdialog.h) if (APPLE) install(FILES fontawesome-webfont.ttf DESTINATION ${MACOSX_BUNDLE_RESOURCES}/fonts) elseif (WIN32) install(FILES fontawesome-webfont.ttf DESTINATION ${CMAKE_INSTALL_PREFIX}/fonts) else () install(FILES fontawesome-webfont.ttf DESTINATION ${SHARE_INSTALL_PREFIX}/${CMAKE_PROJECT_NAME}/fonts) endif () set(SUPPORT_MOC_HDRS ${SUPPORT_MOC_HDRS} combobox.h) if (APPLE) set(SUPPORT_SRCS ${SUPPORT_SRCS} osxstyle.cpp flattoolbutton.cpp windowmanager.cpp) set(SUPPORT_MOC_HDRS ${SUPPORT_MOC_HDRS} osxstyle.h windowmanager.h) endif () set(SUPPORT_SRCS ${SUPPORT_SRCS} pathrequester.cpp kmessagewidget.cpp dialog.cpp shortcutsmodel.cpp shortcutssettingswidget.cpp keysequencewidget.cpp acceleratormanager.cpp urllabel.cpp) set(SUPPORT_MOC_HDRS ${SUPPORT_MOC_HDRS} pathrequester.h kmessagewidget.h urllabel.h dialog.h shortcutsmodel.h shortcutssettingswidget.h keysequencewidget.h spinner.h acceleratormanager_private.h) set(SUPPORT_UIS shortcutssettingswidget.ui) if (Qt5Gui_VERSION_MAJOR LESS 5 OR Qt5Gui_VERSION_MINOR LESS 2) set(SUPPORT_MOC_HDRS ${SUPPORT_MOC_HDRS} lineedit.h) endif () qt5_wrap_ui(SUPPORT_UI_HDRS ${SUPPORT_UIS}) qt5_wrap_cpp(SUPPORT_MOC_SRCS ${SUPPORT_MOC_HDRS}) qt5_add_resources(SUPPORT_RC_SRCS ${SUPPORT_RCS}) add_library(support STATIC ${SUPPORT_MOC_SRCS} ${SUPPORT_SRCS} ${SUPPORT_UI_HDRS} ${SUPPORT_RC_SRCS}) cantata-2.2.0/support/acceleratormanager.cpp000066400000000000000000000611761316350454000212010ustar00rootroot00000000000000/* This file is part of the KDE project Copyright (C) 2002 Matthias Hölzer-Klüpfel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "acceleratormanager.h" #include "dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pathrequester.h" //#include //#include //#include //#include #include "acceleratormanager_private.h" //#include /********************************************************************* class Item - helper class containing widget information This class stores information about the widgets the need accelerators, as well as about their relationship. *********************************************************************/ /********************************************************************* class AcceleratorManagerPrivate - internal helper class This class does all the work to find accelerators for a hierarchy of widgets. *********************************************************************/ static inline QString esc(const QString &orig) { return orig.toHtmlEscaped(); } class AcceleratorManagerPrivate { public: static void manage(QWidget *widget); static bool programmers_mode; static bool standardName(const QString &str); static bool checkChange(const AccelString &as) { QString t2 = as.accelerated(); QString t1 = as.originalText(); if (t1 != t2) { if (as.accel() == -1) { removed_string += "" + esc(t1) + ""; } else if (as.originalAccel() == -1) { added_string += "" + esc(t2) + ""; } else { changed_string += "" + esc(t1) + ""; changed_string += "" + esc(t2) + ""; } return true; } return false; } static QString changed_string; static QString added_string; static QString removed_string; static QMap ignored_widgets; private: class Item; public: typedef QList ItemList; private: static void traverseChildren(QWidget *widget, Item *item); static void manageWidget(QWidget *widget, Item *item); static void manageMenuBar(QMenuBar *mbar, Item *item); static void manageTabBar(QTabBar *bar, Item *item); static void manageDockWidget(QDockWidget *dock, Item *item); static void calculateAccelerators(Item *item, QString &used); class Item { public: Item() : m_widget(0), m_children(0), m_index(-1) {} ~Item(); void addChild(Item *item); QWidget *m_widget; AccelString m_content; ItemList *m_children; int m_index; }; }; bool AcceleratorManagerPrivate::programmers_mode = false; QString AcceleratorManagerPrivate::changed_string; QString AcceleratorManagerPrivate::added_string; QString AcceleratorManagerPrivate::removed_string; //K_GLOBAL_STATIC_WITH_ARGS(QStringList, kaccmp_sns, (KStandardAction::internal_stdNames())) QMap AcceleratorManagerPrivate::ignored_widgets; bool AcceleratorManagerPrivate::standardName(const QString &str) { return StdGuiItem::standardNames().contains(str); // return kaccmp_sns->contains(str); } AcceleratorManagerPrivate::Item::~Item() { if (m_children) while (!m_children->isEmpty()) delete m_children->takeFirst(); delete m_children; } void AcceleratorManagerPrivate::Item::addChild(Item *item) { if (!m_children) { m_children = new ItemList; } m_children->append(item); } void AcceleratorManagerPrivate::manage(QWidget *widget) { if (!widget) { // kDebug(240) << "null pointer given to manage"; return; } if (AcceleratorManagerPrivate::ignored_widgets.contains(widget)) { return; } if (qobject_cast(widget)) { // create a popup accel manager that can deal with dynamic menus PopupAccelManager::manage(static_cast(widget)); return; } Item *root = new Item; manageWidget(widget, root); QString used; calculateAccelerators(root, used); delete root; } void AcceleratorManagerPrivate::calculateAccelerators(Item *item, QString &used) { if (!item->m_children) return; // collect the contents AccelStringList contents; foreach(Item *it, *item->m_children) { contents << it->m_content; } // find the right accelerators AccelManagerAlgorithm::findAccelerators(contents, used); // write them back into the widgets int cnt = -1; foreach(Item *it, *item->m_children) { cnt++; QDockWidget *dock = qobject_cast(it->m_widget); if (dock) { if (checkChange(contents[cnt])) dock->setWindowTitle(contents[cnt].accelerated()); continue; } QTabBar *tabBar = qobject_cast(it->m_widget); if (tabBar) { if (checkChange(contents[cnt])) tabBar->setTabText(it->m_index, contents[cnt].accelerated()); continue; } QMenuBar *menuBar = qobject_cast(it->m_widget); if (menuBar) { if (it->m_index >= 0) { QAction *maction = menuBar->actions()[it->m_index]; if (maction) { checkChange(contents[cnt]); maction->setText(contents[cnt].accelerated()); } continue; } } // we possibly reserved an accel, but we won't set it as it looks silly QGroupBox *groupBox = qobject_cast(it->m_widget); if (groupBox && !groupBox->isCheckable()) continue; int tprop = it->m_widget->metaObject()->indexOfProperty("text"); if (tprop != -1) { if (checkChange(contents[cnt])) it->m_widget->setProperty("text", contents[cnt].accelerated()); } else { tprop = it->m_widget->metaObject()->indexOfProperty("title"); if (tprop != -1 && checkChange(contents[cnt])) it->m_widget->setProperty("title", contents[cnt].accelerated()); } } // calculate the accelerators for the children foreach(Item *it, *item->m_children) { if (it->m_widget && it->m_widget->isVisibleTo( item->m_widget ) ) calculateAccelerators(it, used); } } void AcceleratorManagerPrivate::traverseChildren(QWidget *widget, Item *item) { QList childList = widget->findChildren(); foreach ( QWidget *w , childList ) { // Ignore unless we have the direct parent if(qobject_cast(w->parent()) != widget) continue; if ( !w->isVisibleTo( widget ) || (w->isTopLevel() && qobject_cast(w) == NULL) ) continue; if ( AcceleratorManagerPrivate::ignored_widgets.contains( w ) ) continue; manageWidget(w, item); } } void AcceleratorManagerPrivate::manageWidget(QWidget *w, Item *item) { // first treat the special cases QTabBar *tabBar = qobject_cast(w); if (tabBar) { manageTabBar(tabBar, item); return; } QStackedWidget *wds = qobject_cast( w ); if ( wds ) { QWidgetStackAccelManager::manage( wds ); // return; } QDockWidget *dock = qobject_cast( w ); if ( dock ) { //QWidgetStackAccelManager::manage( wds ); manageDockWidget(dock, item); } QMenu *popupMenu = qobject_cast(w); if (popupMenu) { // create a popup accel manager that can deal with dynamic menus PopupAccelManager::manage(popupMenu); return; } QStackedWidget *wdst = qobject_cast( w ); if ( wdst ) { QWidgetStackAccelManager::manage( wdst ); // return; } QMenuBar *menuBar = qobject_cast(w); if (menuBar) { manageMenuBar(menuBar, item); return; } if (qobject_cast(w) || qobject_cast(w) || w->inherits("Q3TextEdit") || qobject_cast(w) || qobject_cast(w) || w->inherits( "KMultiTabBar" ) ) return; if ( w->inherits("KUrlRequester") ) { traverseChildren(w, item); return; } if ( qobject_cast(w) ) { traverseChildren(w, item); return; } // now treat 'ordinary' widgets QLabel *label = qobject_cast(w); if ( label ) { if ( !label->buddy() ) return; else { if ( label->textFormat() == Qt::RichText || ( label->textFormat() == Qt::AutoText && Qt::mightBeRichText( label->text() ) ) ) return; } } if (w->focusPolicy() != Qt::NoFocus || label || qobject_cast(w) || qobject_cast( w )) { QString content; QVariant variant; int tprop = w->metaObject()->indexOfProperty("text"); if (tprop != -1) { QMetaProperty p = w->metaObject()->property( tprop ); if ( p.isValid() && p.isWritable() ) variant = p.read (w); else tprop = -1; } if (tprop == -1) { tprop = w->metaObject()->indexOfProperty("title"); if (tprop != -1) { QMetaProperty p = w->metaObject()->property( tprop ); if ( p.isValid() && p.isWritable() ) variant = p.read (w); } } if (variant.isValid()) content = variant.toString(); if (!content.isEmpty()) { Item *i = new Item; i->m_widget = w; // put some more weight on the usual action elements int weight = AccelManagerAlgorithm::DEFAULT_WEIGHT; if (qobject_cast(w) || qobject_cast(w) || qobject_cast(w) || qobject_cast(w)) weight = AccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT; // don't put weight on non-checkable group boxes, // as usually the contents are more important QGroupBox *groupBox = qobject_cast(w); if (groupBox) { if (groupBox->isCheckable()) weight = AccelManagerAlgorithm::CHECKABLE_GROUP_BOX_WEIGHT; else weight = AccelManagerAlgorithm::GROUP_BOX_WEIGHT; } i->m_content = AccelString(content, weight); item->addChild(i); } } traverseChildren(w, item); } void AcceleratorManagerPrivate::manageTabBar(QTabBar *bar, Item *item) { // ignore QTabBar for QDockWidgets, because QDockWidget on its title change // also updates its tabbar entry, so on the next run of KChecAccelerators // this looks like a conflict and triggers a new reset of the shortcuts -> endless loop QWidget* parentWidget = bar->parentWidget(); if( parentWidget ) { QMainWindow* mainWindow = qobject_cast(parentWidget); // TODO: find better hints that this is a QTabBar for QDockWidgets if( mainWindow ) // && (mainWindow->layout()->indexOf(bar) != -1)) QMainWindowLayout lacks proper support return; } for (int i=0; icount(); i++) { QString content = bar->tabText(i); if (content.isEmpty()) continue; Item *it = new Item; item->addChild(it); it->m_widget = bar; it->m_index = i; it->m_content = AccelString(content); } } void AcceleratorManagerPrivate::manageDockWidget(QDockWidget *dock, Item *item) { // As of Qt 4.4.3 setting a shortcut to a QDockWidget has no effect, // because a QDockWidget does not grab it, even while displaying an underscore // in the title for the given shortcut letter. // Still it is useful to set the shortcut, because if QDockWidgets are tabbed, // the tab automatically gets the same text as the QDockWidget title, including the shortcut. // And for the QTabBar the shortcut does work, it gets grabbed as usual. // Having the QDockWidget without a shortcut and resetting the tab text with a title including // the shortcut does not work, the tab text is instantly reverted to the QDockWidget title // (see also manageTabBar()). // All in all QDockWidgets and shortcuts are a little broken for now. QString content = dock->windowTitle(); if (content.isEmpty()) return; Item *it = new Item; item->addChild(it); it->m_widget = dock; it->m_content = AccelString(content, AccelManagerAlgorithm::STANDARD_ACCEL); } void AcceleratorManagerPrivate::manageMenuBar(QMenuBar *mbar, Item *item) { QAction *maction; QString s; for (int i=0; iactions().count(); ++i) { maction = mbar->actions()[i]; if (!maction) continue; // nothing to do for separators if (maction->isSeparator()) continue; s = maction->text(); if (!s.isEmpty()) { Item *it = new Item; item->addChild(it); it->m_content = AccelString(s, // menu titles are important, so raise the weight AccelManagerAlgorithm::MENU_TITLE_WEIGHT); it->m_widget = mbar; it->m_index = i; } // have a look at the popup as well, if present if (maction->menu()) PopupAccelManager::manage(maction->menu()); } } /********************************************************************* class AcceleratorManager - main entry point This class is just here to provide a clean public API... *********************************************************************/ void AcceleratorManager::manage(QWidget *widget, bool programmers_mode) { AcceleratorManagerPrivate::changed_string.clear(); AcceleratorManagerPrivate::added_string.clear(); AcceleratorManagerPrivate::removed_string.clear(); AcceleratorManagerPrivate::programmers_mode = programmers_mode; AcceleratorManagerPrivate::manage(widget); } void AcceleratorManager::last_manage(QString &added, QString &changed, QString &removed) { added = AcceleratorManagerPrivate::added_string; changed = AcceleratorManagerPrivate::changed_string; removed = AcceleratorManagerPrivate::removed_string; } /********************************************************************* class AccelString - a string with weighted characters *********************************************************************/ AccelString::AccelString(const QString &input, int initialWeight) : m_pureText(input), m_weight() { m_orig_accel = m_pureText.indexOf("(!)&"); if (m_orig_accel != -1) m_pureText.remove(m_orig_accel, 4); m_orig_accel = m_pureText.indexOf("(&&)"); if (m_orig_accel != -1) m_pureText.replace(m_orig_accel, 4, "&"); m_origText = m_pureText; if (m_pureText.contains('\t')) m_pureText = m_pureText.left(m_pureText.indexOf('\t')); m_orig_accel = m_accel = stripAccelerator(m_pureText); if (initialWeight == -1) initialWeight = AccelManagerAlgorithm::DEFAULT_WEIGHT; calculateWeights(initialWeight); // dump(); } QString AccelString::accelerated() const { QString result = m_origText; if (result.isEmpty()) return result; if (AcceleratorManagerPrivate::programmers_mode) { if (m_accel != m_orig_accel) { int oa = m_orig_accel; if (m_accel >= 0) { result.insert(m_accel, "(!)&"); if (m_accel < m_orig_accel) oa += 4; } if (m_orig_accel >= 0) result.replace(oa, 1, "(&&)"); } } else { if (m_accel >= 0 && m_orig_accel != m_accel) { if (m_orig_accel != -1) result.remove(m_orig_accel, 1); result.insert(m_accel, "&"); } } return result; } QChar AccelString::accelerator() const { if ((m_accel < 0) || (m_accel > (int)m_pureText.length())) return QChar(); return m_pureText[m_accel].toLower(); } void AccelString::calculateWeights(int initialWeight) { m_weight.resize(m_pureText.length()); int pos = 0; bool start_character = true; while (pos= 0) { p = text.indexOf('&', p)+1; if (p <= 0 || p >= (int)text.length()) break; if (text[p] != '&') { QChar c = text[p]; if (c.isPrint()) { text.remove(p-1,1); return p-1; } } p++; } return -1; } int AccelString::maxWeight(int &index, const QString &used) const { int max = 0; index = -1; for (int pos=0; pos max) { max = m_weight[pos]; index = pos; } return max; } void AccelString::dump() { QString s; for (int i=0; imax) { max = m; index = i; accel = a; } } // stop if no more accelerators can be found if (index < 0) return; // insert the accelerator if (accel >= 0) { result[index].setAccel(accel); used.append(result[index].accelerator()); } // make sure we don't visit this one again accel_strings[index] = AccelString(); } } /********************************************************************* class PopupAccelManager - managing QPopupMenu widgets dynamically *********************************************************************/ PopupAccelManager::PopupAccelManager(QMenu *popup) : QObject(popup), m_popup(popup), m_count(-1) { aboutToShow(); // do one check and then connect to show connect(popup, SIGNAL(aboutToShow()), SLOT(aboutToShow())); } void PopupAccelManager::aboutToShow() { // Note: we try to be smart and avoid recalculating the accelerators // whenever possible. Unfortunately, there is no way to know if an // item has been added or removed, so we can not do much more than // to compare the items each time the menu is shown :-( if (m_count != (int)m_popup->actions().count()) { findMenuEntries(m_entries); calculateAccelerators(); m_count = m_popup->actions().count(); } else { AccelStringList entries; findMenuEntries(entries); if (entries != m_entries) { m_entries = entries; calculateAccelerators(); } } } void PopupAccelManager::calculateAccelerators() { // find the new accelerators QString used; AccelManagerAlgorithm::findAccelerators(m_entries, used); // change the menu entries setMenuEntries(m_entries); } void PopupAccelManager::findMenuEntries(AccelStringList &list) { QString s; list.clear(); // read out the menu entries foreach (QAction *maction, m_popup->actions()) { if (maction->isSeparator()) continue; s = maction->text(); // in full menus, look at entries with global accelerators last int weight = 50; if (s.contains('\t')) weight = 0; list.append(AccelString(s, weight)); // have a look at the popup as well, if present if (maction->menu()) PopupAccelManager::manage(maction->menu()); } } void PopupAccelManager::setMenuEntries(const AccelStringList &list) { uint cnt = 0; foreach (QAction *maction, m_popup->actions()) { if (maction->isSeparator()) continue; if (AcceleratorManagerPrivate::checkChange(list[cnt])) maction->setText(list[cnt].accelerated()); cnt++; } } void PopupAccelManager::manage(QMenu *popup) { // don't add more than one manager to a popup if (popup->findChild(QString()) == 0 ) new PopupAccelManager(popup); } void QWidgetStackAccelManager::manage( QStackedWidget *stack ) { if ( stack->findChild(QString()) == 0 ) new QWidgetStackAccelManager( stack ); } QWidgetStackAccelManager::QWidgetStackAccelManager(QStackedWidget *stack) : QObject(stack), m_stack(stack) { currentChanged(stack->currentIndex()); // do one check and then connect to show connect(stack, SIGNAL(currentChanged(int)), SLOT(currentChanged(int))); } bool QWidgetStackAccelManager::eventFilter ( QObject * watched, QEvent * e ) { // 'Paint' seems to be required for Cantata's PageWidget??? if ( (e->type() == QEvent::Show || e->type() == QEvent::Paint) && qApp->activeWindow() ) { AcceleratorManager::manage( qApp->activeWindow() ); watched->removeEventFilter( this ); } return false; } void QWidgetStackAccelManager::currentChanged(int child) { if (child < 0 || child >= static_cast(parent())->count()) { // NOTE: QStackedWidget emits currentChanged(-1) when it is emptied return; } static_cast(parent())->widget(child)->installEventFilter( this ); } void AcceleratorManager::setNoAccel( QWidget *widget ) { AcceleratorManagerPrivate::ignored_widgets[widget] = 1; } //#include "kacceleratormanager_private.moc" cantata-2.2.0/support/acceleratormanager.h000066400000000000000000000050631316350454000206370ustar00rootroot00000000000000/* This file is part of the KDE project Copyright (C) 2002 Matthias Hölzer-Klüpfel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef K_ACCELERATORMANAGER_H #define K_ACCELERATORMANAGER_H class QWidget; class QString; /** * KDE Accelerator manager. * * This class can be used to find a valid and working set of * accelerators for any widget. * * @author Matthias Hölzer-Klüpfel */ class AcceleratorManager { public: /** * Manages the accelerators of a widget. * * Call this function on the top widget of the hierarchy you * want to manage. It will fix the accelerators of the child widgets so * there are never duplicate accelerators. It also tries to put * accelerators on as many widgets as possible. * * The algorithm used tries to take the existing accelerators into * account, as well as the class of each widget. Hopefully, the result * is close to what you would assign manually. * * QPopupMenu's are managed dynamically, so when you add or remove entries, * the accelerators are reassigned. If you add or remove widgets to your * toplevel widget, you will have to call manage again to fix the * accelerators. * * @param widget The toplevel widget you want to manage. * @param programmers_mode if true, AcceleratorManager adds (&) for removed * accels and & before added accels */ static void manage(QWidget *widget, bool programmers_mode = false ); /** \internal returns the result of the last manage operation. */ static void last_manage(QString &added, QString &changed, QString &removed); /** * Use this method for a widget (and its children) you want no accels to be set on. */ static void setNoAccel( QWidget *widget ); }; #endif // K_ACCELERATORMANAGER_H cantata-2.2.0/support/acceleratormanager_private.h000066400000000000000000000107651316350454000223760ustar00rootroot00000000000000/* This file is part of the KDE project Copyright (C) 2002 Matthias H�zer-Klpfel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ACCELERATORMANAGER_PRIVATE_H #define ACCELERATORMANAGER_PRIVATE_H #include #include #include class QStackedWidget; class QMenu; /** * A string class handling accelerators. * * This class contains a string and knowledge about accelerators. * It keeps a list weights, telling how valuable each character * would be as an accelerator. * * @author Matthias H�zer-Klpfel */ class AccelString { public: AccelString() : m_pureText(), m_accel(-1) {} explicit AccelString(const QString &input, int initalWeight=-1); void calculateWeights(int initialWeight); const QString &pure() const { return m_pureText; } QString accelerated() const; int accel() const { return m_accel; } void setAccel(int accel) { m_accel = accel; } int originalAccel() const { return m_orig_accel; } QString originalText() const { return m_origText; } QChar accelerator() const; int maxWeight(int &index, const QString &used) const; bool operator == (const AccelString &c) const { return m_pureText == c.m_pureText && m_accel == c.m_accel && m_orig_accel == c.m_orig_accel; } private: int stripAccelerator(QString &input); void dump(); QString m_pureText, m_origText; int m_accel, m_orig_accel; QVector m_weight; }; typedef QList AccelStringList; /** * This class encapsulates the algorithm finding the 'best' * distribution of accelerators in a hierarchy of widgets. * * @author Matthias H�zer-Klpfel */ class AccelManagerAlgorithm { public: enum { // Default control weight DEFAULT_WEIGHT = 50, // Additional weight for first character in string FIRST_CHARACTER_EXTRA_WEIGHT = 50, // Additional weight for the beginning of a word WORD_BEGINNING_EXTRA_WEIGHT = 50, // Additional weight for the dialog buttons (large, we basically never want these reassigned) DIALOG_BUTTON_EXTRA_WEIGHT = 300, // Additional weight for a 'wanted' accelerator WANTED_ACCEL_EXTRA_WEIGHT = 150, // Default weight for an 'action' widget (ie, pushbuttons) ACTION_ELEMENT_WEIGHT = 50, // Default weight for group boxes (lowest priority) GROUP_BOX_WEIGHT = -2000, // Default weight for checkable group boxes (low priority) CHECKABLE_GROUP_BOX_WEIGHT = 20, // Default weight for menu titles MENU_TITLE_WEIGHT = 250, // Additional weight for KDE standard accelerators STANDARD_ACCEL = 300 }; static void findAccelerators(AccelStringList &result, QString &used); }; /** * This class manages a popup menu. It will notice if entries have been * added or changed, and will recalculate the accelerators accordingly. * * This is necessary for dynamic menus like for example in kicker. * * @author Matthias H�zer-Klpfel */ class PopupAccelManager : public QObject { Q_OBJECT public: static void manage(QMenu *popup); protected: PopupAccelManager(QMenu *popup); private Q_SLOTS: void aboutToShow(); private: void calculateAccelerators(); void findMenuEntries(AccelStringList &list); void setMenuEntries(const AccelStringList &list); QMenu *m_popup; AccelStringList m_entries; int m_count; }; class QWidgetStackAccelManager : public QObject { Q_OBJECT public: static void manage(QStackedWidget *popup); protected: QWidgetStackAccelManager(QStackedWidget *popup); private Q_SLOTS: void currentChanged(int child); bool eventFilter ( QObject * watched, QEvent * e ); private: void calculateAccelerators(); QStackedWidget *m_stack; AccelStringList m_entries; }; #endif // AcceleratorMANAGER_PRIVATE_H cantata-2.2.0/support/action.cpp000066400000000000000000000113771316350454000166350ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-09 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *************************************************************************** * Parts of this implementation are taken from KDE's kaction.cpp * ***************************************************************************/ #include "action.h" #include "config.h" #include "gtkstyle.h" #include "utils.h" #include #include Action::Action(QObject *parent) : QAction(parent) { init(); } Action::Action(const QString &text, QObject *parent, const QObject *receiver, const char *slot, const QKeySequence &shortcut) : QAction(parent) { init(); setText(text); setShortcut(shortcut); if(receiver && slot) connect(this, SIGNAL(triggered()), receiver, slot); } Action::Action(const QIcon &icon, const QString &text, QObject *parent, const QObject *receiver, const char *slot, const QKeySequence &shortcut) : QAction(parent) { init(); setIcon(icon); setText(text); setShortcut(shortcut); if(receiver && slot) connect(this, SIGNAL(triggered()), receiver, slot); } void Action::initIcon(QAction *act) { if (GtkStyle::isActive() && act) { act->setIconVisibleInMenu(false); } } static const char *constPlainToolTipProperty="plain-tt"; void Action::updateToolTip(QAction *act) { if (!act) { return; } QKeySequence sc=act->shortcut(); if (sc.isEmpty()) { act->setToolTip(act->property(constPlainToolTipProperty).toString()); act->setProperty(constPlainToolTipProperty, QString()); } else { QString tt=act->property(constPlainToolTipProperty).toString(); if (tt.isEmpty()) { tt=act->toolTip(); act->setProperty(constPlainToolTipProperty, tt); } act->setToolTip(QString::fromLatin1("%1 %2") .arg(tt) .arg(sc.toString(QKeySequence::NativeText))); } } const char * Action::constTtForSettings="tt-for-settings"; QString Action::settingsText(QAction *act) { if (act->property(constTtForSettings).toBool()) { QString tt=Utils::stripAcceleratorMarkers(act->property(constPlainToolTipProperty).toString()); if (tt.isEmpty()) { tt=Utils::stripAcceleratorMarkers(act->toolTip()); } if (!tt.isEmpty()) { return tt; } } return Utils::stripAcceleratorMarkers(act->text()); } void Action::init() { connect(this, SIGNAL(triggered(bool)), this, SLOT(slotTriggered())); setProperty("isShortcutConfigurable", true); } void Action::slotTriggered() { emit triggered(QApplication::mouseButtons(), QApplication::keyboardModifiers()); } bool Action::isShortcutConfigurable() const { return property("isShortcutConfigurable").toBool(); } void Action::setShortcutConfigurable(bool b) { setProperty("isShortcutConfigurable", b); } QKeySequence Action::shortcut(ShortcutTypes type) const { Q_ASSERT(type); if(type == DefaultShortcut) return property("defaultShortcut").value(); if(shortcuts().count()) return shortcuts().value(0); return QKeySequence(); } void Action::setShortcut(const QShortcut &shortcut, ShortcutTypes type) { setShortcut(shortcut.key(), type); } void Action::setShortcut(const QKeySequence &key, ShortcutTypes type) { Q_ASSERT(type); if(type & DefaultShortcut) setProperty("defaultShortcut", key); if(type & ActiveShortcut) QAction::setShortcut(key); } cantata-2.2.0/support/action.h000066400000000000000000000066061316350454000163010ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-09 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *************************************************************************** * Parts of this API have been shamelessly stolen from KDE's kaction.h * ***************************************************************************/ #ifndef ACTION_H_ #define ACTION_H_ #include #include /// A specialized QWidgetAction, enhanced by some KDE features /** This declares/implements a subset of KAction's API (notably we've left out global shortcuts * for now), which should make it easy to plug in KDE support later on. */ class Action : public QAction { Q_OBJECT Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) Q_PROPERTY(bool shortcutConfigurable READ isShortcutConfigurable WRITE setShortcutConfigurable) Q_FLAGS(ShortcutType) public: enum ShortcutType { ActiveShortcut = 0x01, DefaultShortcut = 0x02 }; Q_DECLARE_FLAGS(ShortcutTypes, ShortcutType) static void initIcon(QAction *act); static void updateToolTip(QAction *act); static QString settingsText(QAction *act); static const char * constTtForSettings; explicit Action(QObject *parent); Action(const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); Action(const QIcon &icon, const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); QKeySequence shortcut(ShortcutTypes types = ActiveShortcut) const; void setShortcut(const QShortcut &shortcut, ShortcutTypes type = ShortcutTypes(ActiveShortcut | DefaultShortcut)); void setShortcut(const QKeySequence &shortcut, ShortcutTypes type = ShortcutTypes(ActiveShortcut | DefaultShortcut)); bool isShortcutConfigurable() const; void setShortcutConfigurable(bool configurable); signals: void triggered(Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); private: void init(); private slots: void slotTriggered(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Action::ShortcutTypes) #endif cantata-2.2.0/support/actioncollection.cpp000066400000000000000000000233761316350454000207130ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-09 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *************************************************************************** * Parts of this implementation are based on KDE's KActionCollection. * ***************************************************************************/ #include "actioncollection.h" #include "action.h" #include "icon.h" #include #include #include static const char *constProp="Category"; static ActionCollection *coll=0; static QWidget *mainWidget=0; void ActionCollection::setMainWidget(QWidget *w) { mainWidget=w; } ActionCollection * ActionCollection::get() { if (!coll) { coll=new ActionCollection(mainWidget); coll->setProperty(constProp, QCoreApplication::applicationName()); if (mainWidget) { coll->addAssociatedWidget(mainWidget); } } return coll; } Action * ActionCollection::createAction(const QString &name, const QString &text, const char *icon, const QString &whatsThis) { Action *act = static_cast(addAction(name)); act->setText(text); if (0!=icon) { if ('m'==icon[0] && 'e'==icon[1] && 'd'==icon[2] && 'i'==icon[3] && 'a'==icon[4] && '-'==icon[5]) { act->setIcon(Icon::getMediaIcon(icon)); } else { act->setIcon(Icon(icon)); } } if (!whatsThis.isEmpty()) { act->setWhatsThis(whatsThis); } Action::initIcon(act); return act; } Action * ActionCollection::createAction(const QString &name, const QString &text, const QIcon &icon, const QString &whatsThis) { Action *act = static_cast(addAction(name)); act->setText(text); if (!icon.isNull()) { act->setIcon(icon); } if (!whatsThis.isEmpty()) { act->setWhatsThis(whatsThis); } Action::initIcon(act); return act; } void ActionCollection::updateToolTips() { foreach (QAction *act, actions()) { QString prev=act->toolTip(); Action::updateToolTip(act); if (prev!=act->toolTip()) { emit tooltipUpdated(act); } } } ActionCollection::ActionCollection(QObject *parent) : QObject(parent) { _connectTriggered = _connectHovered = false; } ActionCollection::~ActionCollection() { } void ActionCollection::clear() { _actionByName.clear(); qDeleteAll(_actions); _actions.clear(); } QAction *ActionCollection::action(const QString &name) const { return _actionByName.value(name, 0); } QList ActionCollection::actions() const { return _actions; } Action *ActionCollection::addAction(const QString &name, Action *action) { QAction *act = addAction(name, static_cast(action)); Q_UNUSED(act) Q_ASSERT(act == action); return action; } Action *ActionCollection::addAction(const QString &name, const QObject *receiver, const char *member) { Action *a = new Action(this); if(receiver && member) connect(a, SIGNAL(triggered(bool)), receiver, member); return addAction(name, a); } QAction *ActionCollection::addAction(const QString &name, QAction *action) { if(!action) return action; const QString origName = action->objectName(); QString indexName = name; if(indexName.isEmpty()) indexName = action->objectName(); else action->setObjectName(indexName); if(indexName.isEmpty()) indexName = indexName.sprintf("unnamed-%p", (void *)action); // do we already have this action? if(_actionByName.value(indexName, 0) == action) return action; // or maybe another action under this name? if(QAction *oldAction = _actionByName.value(indexName)) takeAction(oldAction); // do we already have this action under a different name? int oldIndex = _actions.indexOf(action); if(oldIndex != -1) { _actionByName.remove(origName); _actions.removeAt(oldIndex); } // add action _actionByName.insert(indexName, action); _actions.append(action); foreach(QWidget *widget, _associatedWidgets) { widget->addAction(action); } connect(action, SIGNAL(destroyed(QObject *)), SLOT(actionDestroyed(QObject *))); if(_connectHovered) connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); if(_connectTriggered) connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); emit inserted(action); return action; } void ActionCollection::removeAction(QAction *action) { delete takeAction(action); } QAction *ActionCollection::takeAction(QAction *action) { if(!unlistAction(action)) return 0; foreach(QWidget *widget, _associatedWidgets) { widget->removeAction(action); } action->disconnect(this); return action; } QString ActionCollection::configKey() const { QVariant v=property(constProp); if (v.isValid() && !v.toString().isEmpty()) { return QLatin1String("Shortcuts-")+v.toString(); } return QLatin1String("Shortcuts"); } void ActionCollection::readSettings() { QSettings s; s.beginGroup(configKey()); QStringList savedShortcuts = s.childKeys(); foreach(const QString &name, _actionByName.keys()) { if(!savedShortcuts.contains(name)) continue; Action *action = qobject_cast(_actionByName.value(name)); if(action) action->setShortcut(s.value(name, QKeySequence()).value(), Action::ActiveShortcut); } } void ActionCollection::writeSettings() const { QSettings s; s.beginGroup(configKey()); foreach(const QString &name, _actionByName.keys()) { Action *action = qobject_cast(_actionByName.value(name)); if(!action) continue; if(!action->isShortcutConfigurable()) continue; if(action->shortcut(Action::ActiveShortcut) == action->shortcut(Action::DefaultShortcut)) { s.remove(name); continue; } s.setValue(name, action->shortcut(Action::ActiveShortcut)); } } void ActionCollection::slotActionTriggered() { QAction *action = qobject_cast(sender()); if(action) emit actionTriggered(action); } void ActionCollection::slotActionHovered() { QAction *action = qobject_cast(sender()); if(action) emit actionHovered(action); } void ActionCollection::actionDestroyed(QObject *obj) { // remember that this is not an QAction anymore at this point QAction *action = static_cast(obj); unlistAction(action); } void ActionCollection::connectNotify(const QMetaMethod &signal) { if(_connectHovered && _connectTriggered) return; if(QMetaObject::normalizedSignature(SIGNAL(actionHovered(QAction*))) == signal.methodSignature()) { if(!_connectHovered) { _connectHovered = true; foreach (QAction* action, actions()) connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); } } else if(QMetaObject::normalizedSignature(SIGNAL(actionTriggered(QAction*))) == signal.methodSignature()) { if(!_connectTriggered) { _connectTriggered = true; foreach (QAction* action, actions()) connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); } } QObject::connectNotify(signal); } void ActionCollection::associateWidget(QWidget *widget) const { foreach(QAction *action, actions()) { if(!widget->actions().contains(action)) widget->addAction(action); } } void ActionCollection::addAssociatedWidget(QWidget *widget) { if(!_associatedWidgets.contains(widget)) { widget->addActions(actions()); _associatedWidgets.append(widget); connect(widget, SIGNAL(destroyed(QObject *)), SLOT(associatedWidgetDestroyed(QObject *))); } } void ActionCollection::removeAssociatedWidget(QWidget *widget) { foreach(QAction *action, actions()) widget->removeAction(action); _associatedWidgets.removeAll(widget); disconnect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(associatedWidgetDestroyed(QObject *))); } QList ActionCollection::associatedWidgets() const { return _associatedWidgets; } void ActionCollection::clearAssociatedWidgets() { foreach(QWidget *widget, _associatedWidgets) foreach(QAction *action, actions()) widget->removeAction(action); _associatedWidgets.clear(); } void ActionCollection::associatedWidgetDestroyed(QObject *obj) { _associatedWidgets.removeAll(static_cast(obj)); } bool ActionCollection::unlistAction(QAction *action) { // This might be called with a partly destroyed QAction! int index = _actions.indexOf(action); if(index == -1) return false; QString name = action->objectName(); _actionByName.remove(name); _actions.removeAt(index); // TODO: remove from ActionCategory if we ever get that return true; } cantata-2.2.0/support/actioncollection.h000066400000000000000000000112551316350454000203510ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-09 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * *************************************************************************** * This is a subset of the API of KDE's KActionCollection. * ***************************************************************************/ #ifndef ACTIONCOLLECTION_H_ #define ACTIONCOLLECTION_H_ #include #include #include #include class Action; class QAction; class QWidget; class QIcon; class ActionCollection : public QObject { Q_OBJECT public: explicit ActionCollection(QObject *parent); virtual ~ActionCollection(); static void setMainWidget(QWidget *w); static ActionCollection * get(); Action * createAction(const QString &name, const QString &text, const char *icon=0, const QString &whatsThis=QString()); Action * createAction(const QString &name, const QString &text, const QIcon &icon, const QString &whatsThis=QString()); /// Clears the entire action collection, deleting all actions. void clear(); /// Associate all action in this collection to the given \a widget. /** Not that this only adds all current actions in the collection to that widget; * subsequently added actions won't be added automatically. */ void associateWidget(QWidget *widget) const; /// Associate all actions in this collection to the given \a widget. /** Subsequently added actions will be automagically associated with this widget as well. */ void addAssociatedWidget(QWidget *widget); void removeAssociatedWidget(QWidget *widget); QList associatedWidgets() const; void clearAssociatedWidgets(); void readSettings(); void writeSettings() const; int count() const { return actions().count(); } bool isEmpty() const { return 0==actions().count(); } QAction *action(int index) const; QAction *action(const QString &name) const; QList actions() const; QAction *addAction(const QString &name, QAction *action); Action *addAction(const QString &name, Action *action); Action *addAction(const QString &name, const QObject *receiver = 0, const char *member = 0); void removeAction(QAction *action); QAction *takeAction(QAction *action); /// Create new action under the given name, add it to the collection and connect its triggered(bool) signal to the specified receiver. template ActionType *add(const QString &name, const QObject *receiver = 0, const char *member = 0) { ActionType *a = new ActionType(this); if(receiver && member) connect(a, SIGNAL(triggered(bool)), receiver, member); addAction(name, a); return a; } void updateToolTips(); Q_SIGNALS: void inserted(QAction *action); void actionHovered(QAction *action); void actionTriggered(QAction *action); void tooltipUpdated(QAction *); protected Q_SLOTS: virtual void slotActionTriggered(); protected: virtual void connectNotify(const QMetaMethod &signal); private Q_SLOTS: void slotActionHovered(); void actionDestroyed(QObject *); void associatedWidgetDestroyed(QObject *); private: bool unlistAction(QAction *); QString configKey() const; QMap _actionByName; QList _actions; QList _associatedWidgets; bool _connectHovered; bool _connectTriggered; }; #endif cantata-2.2.0/support/buddylabel.cpp000066400000000000000000000041571316350454000174650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "buddylabel.h" #include "pathrequester.h" #include #include #include #include BuddyLabel::BuddyLabel(const QString &text, QWidget *p, QWidget *b) : QLabel(text, p) { if (b) { setBuddy(b); } } BuddyLabel::BuddyLabel(QWidget *p, QWidget *b) : QLabel(p) { if (b) { setBuddy(b); } } bool BuddyLabel::event(QEvent *e) { if (QEvent::Shortcut==e->type()) { mouseReleaseEvent(0); e->accept(); return true; } else { return QLabel::event(e); } } void BuddyLabel::mouseReleaseEvent(QMouseEvent *) { if (buddy() && buddy()->isEnabled()) { PathRequester *pr=qobject_cast(buddy()); if (pr) { pr->setFocus(); return; } buddy()->setFocus(); QCheckBox *cb=qobject_cast(buddy()); if (cb) { cb->setChecked(!cb->isChecked()); return; } QRadioButton *rb=qobject_cast(buddy()); if (rb) { rb->setChecked(!rb->isChecked()); return; } QComboBox *combo=qobject_cast(buddy()); if (combo) { combo->showPopup(); return; } } } cantata-2.2.0/support/buddylabel.h000066400000000000000000000022401316350454000171210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BUDDYLABEL_H #define BUDDYLABEL_H #include class BuddyLabel : public QLabel { public: BuddyLabel(const QString &text, QWidget *p, QWidget *b=0); BuddyLabel(QWidget *p, QWidget *b=0); virtual ~BuddyLabel() { } protected: bool event(QEvent *e); void mouseReleaseEvent(QMouseEvent *); }; #endif cantata-2.2.0/support/capacitybar.h000066400000000000000000000022461316350454000173020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CAPACITY_BAR_H #define CAPACITY_BAR_H #include class CapacityBar : public QProgressBar { public: CapacityBar(QWidget *p) : QProgressBar(p) { } void update(const QString &text, double value) { setFormat(text); setRange(0, 100*100); setValue(value*100); } }; #endif cantata-2.2.0/support/combobox.cpp000066400000000000000000000100561316350454000171610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "combobox.h" #include "lineedit.h" #include "utils.h" #include #include #include #include // Max number of items before we try to force a scrollbar in popup menu... static int maxPopupItemCount=-1; ComboBox::ComboBox(QWidget *p) : QComboBox(p) , toggleState(false) { #if !defined Q_OS_WIN && !defined Q_OS_MAC if (-1==maxPopupItemCount) { if (QApplication::desktop()) { maxPopupItemCount=((QApplication::desktop()->height()/(QApplication::fontMetrics().height()*1.5))*0.75)+0.5; } else { maxPopupItemCount=32; } } #endif connect(this, SIGNAL(editTextChanged(QString)), this, SIGNAL(textChanged(QString))); } void ComboBox::setEditable(bool editable) { QComboBox::setEditable(editable); if (editable) { LineEdit *edit = new LineEdit(this); setLineEdit(edit); } } #if !defined Q_OS_WIN && !defined Q_OS_MAC void ComboBox::showPopup() { QStyleOptionComboBox opt; initStyleOption(&opt); toggleState=false; bool hack=0!=style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this, 0); if (hack && count()>(maxPopupItemCount-2)) { toggleState=!isEditable(); // Hacky, but if we set the combobox as editable - the style gives the // popup a scrollbar. This is more convenient if we have lots of items! if (toggleState) { setMaxVisibleItems(maxPopupItemCount); QComboBox::setEditable(true); lineEdit()->setReadOnly(true); } } QComboBox::showPopup(); if (hack && view()->parentWidget() && count()>maxPopupItemCount) { // Also, if the size of the popup is more than required for 32 items, then // restrict its height... int maxHeight=maxPopupItemCount*view()->sizeHintForRow(0); QRect geo(view()->parentWidget()->geometry()); QRect r(view()->parentWidget()->rect()); if (geo.height()>maxHeight) { geo=QRect(geo.x(), geo.y()+(geo.height()-maxHeight), geo.width(), maxHeight); r=QRect(r.x(), r.y()+(r.height()-maxHeight), r.width(), maxHeight); } QPoint popupBot=view()->parentWidget()->mapToGlobal(r.bottomLeft()); QPoint bot=mapToGlobal(rect().bottomLeft()); if (popupBot.y()parentWidget()->mapToGlobal(r.topLeft()); QPoint top=mapToGlobal(rect().topLeft()); if (popupTop.y()>top.y()) { geo=QRect(geo.x(), geo.y()-(popupTop.y()-top.y()), geo.width(), geo.height()); } } view()->parentWidget()->setGeometry(geo); // Hide scrollers - these look ugly... foreach (QObject *c, view()->parentWidget()->children()) { if (0==qstrcmp("QComboBoxPrivateScroller", c->metaObject()->className())) { static_cast(c)->setMaximumHeight(0); } } } } void ComboBox::hidePopup() { if (toggleState) { QComboBox::setEditable(false); toggleState=false; } QComboBox::hidePopup(); } #endif cantata-2.2.0/support/combobox.h000066400000000000000000000023531316350454000166270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COMBOBOX_H #define COMBOBOX_H #include class ComboBox : public QComboBox { Q_OBJECT public: ComboBox(QWidget *p); virtual ~ComboBox() { } void setEditable(bool editable); #if !defined Q_OS_WIN && !defined Q_OS_MAC void showPopup(); void hidePopup(); #endif Q_SIGNALS: void textChanged(const QString &t); private: bool toggleState; }; #endif cantata-2.2.0/support/configdialog.cpp000066400000000000000000000315771316350454000200110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "configdialog.h" #include "utils.h" #ifdef __APPLE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "icon.h" #include "osxstyle.h" #include "windowmanager.h" #else #include "pagewidget.h" #endif #ifdef __APPLE__ //#define ANIMATE_RESIZE static void drawFadedLine(QPainter *p, const QRect &r, const QColor &col, bool horiz, double fadeSize) { QPointF start(r.x()+0.5, r.y()+0.5); QPointF end(r.x()+(horiz ? r.width()-1 : 0)+0.5, r.y()+(horiz ? 0 : r.height()-1)+0.5); QLinearGradient grad(start, end); QColor fade(col); fade.setAlphaF(0.0); grad.setColorAt(0, fade); grad.setColorAt(fadeSize, col); grad.setColorAt(1.0-fadeSize, col); grad.setColorAt(1, fade); p->setPen(QPen(QBrush(grad), 1)); p->drawLine(start, end); } class ConfigDialogToolButton : public QToolButton { public: ConfigDialogToolButton(QWidget *p) : QToolButton(p) { setStyleSheet("QToolButton {border: 0}"); setMinimumWidth(56); } void paintEvent(QPaintEvent *e) { if (isChecked()) { QPainter p(this); QColor col(Qt::black); QRect r(rect()); if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_9) { col.setAlphaF(0.1); p.setClipRect(r); p.setRenderHint(QPainter::Antialiasing, true); p.fillPath(Utils::buildPath(r.adjusted(0, 0, 0, 32), 4), col); } else { QLinearGradient bgndGrad(r.topLeft(), r.bottomLeft()); col.setAlphaF(0.01); bgndGrad.setColorAt(0, col); bgndGrad.setColorAt(1, col); col.setAlphaF(0.1); bgndGrad.setColorAt(0.25, col); bgndGrad.setColorAt(0.75, col); p.fillRect(r, bgndGrad); p.setRenderHint(QPainter::Antialiasing, true); col.setAlphaF(0.25); drawFadedLine(&p, QRect(r.x(), r.y(), r.x(), r.y()+r.height()-1), col, false, 0.25); drawFadedLine(&p, QRect(r.x()+r.width()-1, r.y(), r.x()+1, r.y()+r.height()-1), col, false, 0.25); } } QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); if (isDown()) { opt.state&=QStyle::State_Sunken; } p.drawComplexControl(QStyle::CC_ToolButton, opt); } }; static const int constMinPad=16; #endif ConfigDialog::ConfigDialog(QWidget *parent, const QString &name, const QSize &defSize, bool instantApply) #ifdef __APPLE__ : QMainWindow(parent) , shown(false) , resizeAnim(0) #else : Dialog(parent, name, defSize) #endif { instantApply=false; // TODO!!!! #ifdef __APPLE__ Q_UNUSED(defSize) Q_UNUSED(name) setUnifiedTitleAndToolBarOnMac(true); toolBar=addToolBar("ToolBar"); toolBar->setMovable(false); toolBar->setContextMenuPolicy(Qt::PreventContextMenu); toolBar->setStyleSheet("QToolBar { spacing:0px} "); QWidget *left=new QWidget(toolBar); left->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); toolBar->addWidget(left); QWidget *right=new QWidget(toolBar); right->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); rightSpacer=toolBar->addWidget(right); left->setMinimumWidth(constMinPad); right->setMinimumWidth(constMinPad); QWidget *mw=new QWidget(this); QBoxLayout *lay=new QBoxLayout(QBoxLayout::TopToBottom, mw); stack=new QStackedWidget(mw); group=new QButtonGroup(toolBar); lay->addWidget(stack); if (instantApply) { buttonBox=0; setWindowFlags(Qt::Window|Qt::WindowTitleHint|Qt::CustomizeWindowHint|Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint); } else { buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply, Qt::Horizontal, mw); lay->addWidget(buttonBox); connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(macButtonPressed(QAbstractButton *))); buttonBox->setStyle(Dialog::buttonProxyStyle()); // Hide window buttons if not instany apply - dont want user just closing dialog setWindowFlags(Qt::Window|Qt::WindowTitleHint|Qt::CustomizeWindowHint|Qt::WindowMaximizeButtonHint); } group->setExclusive(true); setCentralWidget(mw); WindowManager *wm=new WindowManager(toolBar); wm->initialize(WindowManager::WM_DRAG_MENU_AND_TOOLBAR); wm->registerWidgetAndChildren(toolBar); #else bool limitedHeight=Utils::limitedHeight(this); pageWidget = new PageWidget(this, limitedHeight, !limitedHeight); setMainWidget(pageWidget); if (instantApply) { // TODO: What??? } else { setButtons(Dialog::Ok|Dialog::Cancel|Dialog::Apply); } #endif } ConfigDialog::~ConfigDialog() { #ifdef __APPLE__ OSXStyle::self()->removeWindow(this); #endif } void ConfigDialog::addPage(const QString &id, QWidget *widget, const QString &name, const QIcon &icon, const QString &header) { #ifdef __APPLE__ Page newPage; newPage.item=new ConfigDialogToolButton(this); newPage.item->setText(name); newPage.item->setIcon(icon); newPage.item->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); newPage.item->setCheckable(true); newPage.item->setChecked(false); newPage.item->setProperty("id", id); newPage.item->ensurePolished(); toolBar->insertWidget(rightSpacer, newPage.item); newPage.widget=widget; newPage.index=stack->count(); stack->addWidget(widget); group->addButton(newPage.item); pages.insert(id, newPage); connect(newPage.item, SIGNAL(toggled(bool)), this, SLOT(activatePage())); int sz=0; QMap::const_iterator it=pages.begin(); QMap::const_iterator end=pages.end(); for (; it!=end; ++it) { sz+=it.value().item->sizeHint().width()+4; } setMinimumWidth(sz+(2*constMinPad)+48); #else pages.insert(id, pageWidget->addPage(widget, name, icon, header)); #endif } bool ConfigDialog::setCurrentPage(const QString &id) { if (!pages.contains(id)) { return false; } #ifdef __APPLE__ QMap::const_iterator it=pages.find(id); if (it!=pages.end()) { stack->setCurrentIndex(it.value().index); setCaption(it.value().item->text()); it.value().item->setChecked(true); } #ifdef ANIMATE_RESIZE if (isVisible()) { it.value().widget->ensurePolished(); ensurePolished(); } int newH=buttonBox ? (it.value().widget->sizeHint().height()+toolBar->height()+buttonBox->height()+layout()->spacing()+(2*layout()->margin())) : (it.value().widget->sizeHint().height()+toolBar->height()+(2*layout()->margin())); if (isVisible()) { if (newH!=height()) { if (!resizeAnim) { resizeAnim=new QPropertyAnimation(this, "h"); resizeAnim->setDuration(250); } resizeAnim->setStartValue(height()); resizeAnim->setEndValue(newH); resizeAnim->start(); } } else { setFixedHeight(newH); resize(width(), newH); setMaximumHeight(QWIDGETSIZE_MAX); } #endif #else pageWidget->setCurrentPage(pages[id]); #endif return true; } #ifdef __APPLE__ void ConfigDialog::setH(int h) { if (resizeAnim && h==resizeAnim->endValue().toInt()) { setMaximumHeight(QWIDGETSIZE_MAX); resize(width(), h); } else { setFixedHeight(h); } } #endif QWidget * ConfigDialog::getPage(const QString &id) const { if (!pages.contains(id)) { return 0; } #ifdef __APPLE__ return pages[id].widget; #else return pages[id]->widget(); #endif } void ConfigDialog::slotButtonClicked(int button) { switch (button) { case Dialog::Ok: case Dialog::Apply: save(); break; case Dialog::Cancel: cancel(); reject(); #ifndef __APPLE__ // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); #endif break; default: break; } if (Dialog::Ok==button) { accept(); } #ifndef __APPLE__ Dialog::slotButtonClicked(button); #endif } void ConfigDialog::activatePage() { #ifdef __APPLE__ QToolButton *item=qobject_cast(sender()); if (item && item->isChecked()) { setCurrentPage(item->property("id").toString()); } #endif } #ifdef __APPLE__ void ConfigDialog::accept() { close(); } void ConfigDialog::reject() { close(); } void ConfigDialog::keyPressEvent(QKeyEvent *e) { // Only act on Esc if we are instant-apply (in which case there will be no button box)... if (!buttonBox && Qt::Key_Escape==e->key() && Qt::NoModifier==e->modifiers()) { close(); } } void ConfigDialog::hideEvent(QHideEvent *e) { OSXStyle::self()->removeWindow(this); QMainWindow::hideEvent(e); } void ConfigDialog::closeEvent(QCloseEvent *e) { OSXStyle::self()->removeWindow(this); QMainWindow::closeEvent(e); } static bool isFocusable(QWidget *w) { if (/*qobject_cast(w) || qobject_cast(w) ||*/ qobject_cast(w) || qobject_cast(w) || qobject_cast(w)) { if (w->isVisible() && w->isEnabled()) { return true; } } return false; } static QWidget *firstFocusableWidget(QWidget *w) { if (!w) { return 0; } if (isFocusable(w)) { return w; } QObjectList children=w->children(); // Try each child first... foreach (QObject *c, children) { if (qobject_cast(c)) { QWidget *cw=static_cast(c); if (isFocusable(cw)) { return cw; } } } // Now try grandchildren... foreach (QObject *c, children) { if (qobject_cast(c)) { QWidget *f=firstFocusableWidget(static_cast(c)); if (f) { return f; } } } return 0; } void ConfigDialog::showEvent(QShowEvent *e) { if (!shown) { QTimer::singleShot(0, this, SLOT(setFocus())); shown=true; ensurePolished(); QDesktopWidget *dw=QApplication::desktop(); if (dw) { move(QPoint((dw->availableGeometry(this).size().width()-width())/2, 86)); } } OSXStyle::self()->addWindow(this); QMainWindow::showEvent(e); if (parentWidget()) { move(pos().x(), parentWidget()->pos().y()+16); } } #endif void ConfigDialog::setFocus() { #ifdef __APPLE__ // When the pref dialog is shown, no widget has focus - and there is a small (4px x 4px?) upper left // corner of a focus highlight drawn in the upper left of the dialog. // To work-around this, after the dialog is shown, look for a widget that can be set to have focus... QWidget *w=firstFocusableWidget(stack->currentWidget()); if (w) { w->setFocus(); } #endif } void ConfigDialog::macButtonPressed(QAbstractButton *b) { #ifdef __APPLE__ if (!buttonBox) { return; } if (b==buttonBox->button(QDialogButtonBox::Ok)) { slotButtonClicked(Dialog::Ok); } else if (b==buttonBox->button(QDialogButtonBox::Apply)) { slotButtonClicked(Dialog::Apply); } else if (b==buttonBox->button(QDialogButtonBox::Cancel)) { slotButtonClicked(Dialog::Cancel); } #else Q_UNUSED(b) #endif } cantata-2.2.0/support/configdialog.h000066400000000000000000000055351316350454000174510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CONFIG_DIALOG_H #define CONFIG_DIALOG_H #include "dialog.h" #include "config.h" #include #ifdef __APPLE__ #include class QToolBar; class QToolButton; class QButtonGroup; class QStackedWidget; class QDialogButtonBox; class QPropertyAnimation; class QAction; #else class PageWidget; class PageWidgetItem; #endif class QIcon; class QAbstractButton; class ConfigDialog : public #ifdef __APPLE__ QMainWindow #else Dialog #endif { Q_OBJECT #ifdef __APPLE__ Q_PROPERTY(int h READ getH WRITE setH) #endif public: ConfigDialog(QWidget *parent, const QString &name=QString(), const QSize &defSize=QSize(), bool instantApply=false); virtual ~ConfigDialog(); void addPage(const QString &id, QWidget *widget, const QString &name, const QIcon &icon, const QString &header); bool setCurrentPage(const QString &id); QWidget *getPage(const QString &id) const; #ifdef __APPLE__ void setCaption(const QString &c) { setWindowTitle(c); } void accept(); void reject(); int getH() const { return height(); } void setH(int h); #endif virtual void save()=0; virtual void cancel()=0; public Q_SLOTS: void slotButtonClicked(int button); private Q_SLOTS: void activatePage(); void macButtonPressed(QAbstractButton *b); void setFocus(); private: #ifdef __APPLE__ void keyPressEvent(QKeyEvent *e); void showEvent(QShowEvent *e); void hideEvent(QHideEvent *e); void closeEvent(QCloseEvent *e); #endif private: #ifdef __APPLE__ struct Page { Page() : item(0), widget(0), index(0) { } QToolButton *item; QWidget *widget; int index; }; QToolBar *toolBar; QAction *rightSpacer; QButtonGroup *group; QStackedWidget *stack; QDialogButtonBox *buttonBox; QMap pages; bool shown; QPropertyAnimation *resizeAnim; #else PageWidget *pageWidget; QMap pages; #endif }; #endif cantata-2.2.0/support/configuration.cpp000066400000000000000000000033361316350454000202230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "configuration.h" QLatin1String Configuration::constMainGroup("General"); Configuration::Configuration(const QString &group) { if (group!=constMainGroup) { beginGroup(group); } } Configuration::~Configuration() { } int Configuration::get(const QString &key, int def, int min, int max) { int v=get(key, def); return vmax ? max : v); } QString Configuration::getFilePath(const QString &key, const QString &def) { #ifdef Q_OS_WIN return Utils::fixPath(QDir::fromNativeSeparators(get(key, def)), false); #else return Utils::tildaToHome(Utils::fixPath(get(key, def), false)); #endif } QString Configuration::getDirPath(const QString &key, const QString &def) { #ifdef Q_OS_WIN return Utils::fixPath(QDir::fromNativeSeparators(get(key, def))); #else return Utils::tildaToHome(Utils::fixPath(get(key, def))); #endif } cantata-2.2.0/support/configuration.h000066400000000000000000000101541316350454000176640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CONFIGURATION_H #define CONFIGURATION_H #include #include #include #include #include #include #include "utils.h" class Configuration : public QSettings { public: static QLatin1String constMainGroup; Configuration(const QString &group=constMainGroup); ~Configuration(); QString get(const QString &key, const QString &def) { return contains(key) ? value(key).toString() : def; } QStringList get(const QString &key, const QStringList &def) { return contains(key) ? value(key).toStringList() : def; } bool get(const QString &key, bool def) { return contains(key) ? value(key).toBool() : def; } int get(const QString &key, int def) { return contains(key) ? value(key).toInt() : def; } unsigned int get(const QString &key, unsigned int def) { return contains(key) ? value(key).toUInt() : def; } QByteArray get(const QString &key, const QByteArray &def) { return contains(key) ? value(key).toByteArray() : def; } QSize get(const QString &key, const QSize &def) { return contains(key) ? value(key).toSize() : def; } QDateTime get(const QString &key, const QDateTime &def) { return contains(key) ? value(key).toDateTime() : def; } void set(const QString &key, const QString &val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, const char *val) { if (!hasEntry(key) || get(key, QLatin1String(val))!=QLatin1String(val)) setValue(key, val); } void set(const QString &key, const QStringList &val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, bool val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, int val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, unsigned int val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, const QByteArray &val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, const QSize &val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } void set(const QString &key, const QDateTime &val) { if (!hasEntry(key) || get(key, val)!=val) setValue(key, val); } bool hasGroup(const QString &grp) { return -1!=childGroups().indexOf(grp); } void removeGroup(const QString &grp) { remove(grp); } bool hasEntry(const QString &key) { return contains(key); } void removeEntry(const QString &key) { remove(key); } int get(const QString &key, int def, int min, int max); QString getFilePath(const QString &key, const QString &def); void setFilePath(const QString &key, const QString &val) { return set(key, Utils::homeToTilda(val)); } QString getDirPath(const QString &key, const QString &def); void setDirPath(const QString &key, const QString &val) { return set(key, Utils::homeToTilda(val)); } }; #endif cantata-2.2.0/support/dialog.cpp000066400000000000000000000327221316350454000166140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dialog.h" #include "configuration.h" #include "utils.h" #include "monoicon.h" #ifdef Q_OS_MAC #include "osxstyle.h" #endif #include "icon.h" #include "acceleratormanager.h" #include #include #include #include #include Dialog::Dialog(QWidget *parent, const QString &name, const QSize &defSize) : QDialog(parent) , defButton(0) , buttonTypes(0) , mw(0) , buttonBox(0) , shown(false) { if (!name.isEmpty()) { setObjectName(name); Configuration cfg(name); cfgSize=cfg.get("size", QSize()); if (!cfgSize.isEmpty()) { QDialog::resize(cfgSize); } else if (!defSize.isEmpty()) { QDialog::resize(defSize); } } #ifdef Q_OS_MAC setWindowIcon(QIcon()); #endif } Dialog::~Dialog() { if (!objectName().isEmpty() && size()!=cfgSize) { Configuration cfg(objectName()); cfg.set("size", size()); } #ifdef Q_OS_MAC OSXStyle::self()->removeWindow(this); #endif } void Dialog::resize(const QSize &sz) { if (cfgSize.isEmpty()) { QDialog::resize(sz); cfgSize=sz; } } #ifdef Q_OS_MAC Dialog::ButtonProxyStyle::ButtonProxyStyle() : QProxyStyle() { setBaseStyle(qApp->style()); } int Dialog::ButtonProxyStyle::styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const { if (QStyle::SH_DialogButtonLayout==stylehint) { return QDialogButtonBox::GnomeLayout; } else { return QProxyStyle::styleHint(stylehint, opt, widget, returnData); } } Dialog::ButtonProxyStyle * Dialog::buttonProxyStyle() { static ButtonProxyStyle *style=0; if (!style) { style=new ButtonProxyStyle(); } return style; } #endif static QIcon monoIcon(const GuiItem &i) { static QColor col(QColor::Invalid); if (!i.red && !col.isValid()) { col=Utils::monoIconColor(); } return MonoIcon::icon((FontAwesome::icon)i.monoIcon, i.red ? MonoIcon::constRed : col, i.red ? MonoIcon::constRed : QColor(QColor::Invalid)); } namespace StdGuiItem { GuiItem ok() { return GuiItem(QObject::tr("&OK"), FontAwesome::check); } GuiItem cancel() { return GuiItem(QObject::tr("&Cancel"), FontAwesome::ban, true); } GuiItem yes() { return GuiItem(QObject::tr("&Yes"), FontAwesome::check); } GuiItem no() { return GuiItem(QObject::tr("&No"), FontAwesome::times, true); } GuiItem discard() { return GuiItem(QObject::tr("&Discard"), FontAwesome::eraser, true); } GuiItem save() { return GuiItem(QObject::tr("&Save"), FontAwesome::save); } GuiItem apply() { return GuiItem(QObject::tr("&Apply"), FontAwesome::check); } GuiItem close() { return GuiItem(QObject::tr("&Close"), FontAwesome::close, true); } GuiItem help() { return GuiItem(QObject::tr("&Help"), FontAwesome::lifering); } GuiItem overwrite() { return GuiItem(QObject::tr("&Overwrite")); } GuiItem reset() { return GuiItem(QObject::tr("&Reset"), FontAwesome::undo); } GuiItem cont() { return GuiItem(QObject::tr("&Continue"), FontAwesome::arrowright); } GuiItem del() { return GuiItem(QObject::tr("&Delete"), FontAwesome::trash, true); } GuiItem stop() { return GuiItem(QObject::tr("&Stop"), FontAwesome::times); } GuiItem remove() { return GuiItem(QObject::tr("&Remove"), FontAwesome::remove); } GuiItem back(bool useRtl) { return GuiItem(QObject::tr("&Previous"), useRtl && QApplication::isRightToLeft() ? FontAwesome::chevronright : FontAwesome::chevronleft); } GuiItem forward(bool useRtl) { return GuiItem(QObject::tr("&Next"), useRtl && QApplication::isRightToLeft() ? FontAwesome::chevronleft : FontAwesome::chevronright); } QSet standardNames() { static QSet names; if (names.isEmpty()) { QStringList strings = QStringList() << ok().text << cancel().text << yes().text << no().text << discard().text << save().text << apply().text << close().text << help().text << overwrite().text << reset().text << cont().text << del().text << stop().text << remove().text << back().text << forward().text; foreach (QString s, strings) { names.insert(s.remove("&")); } } return names; } } static QDialogButtonBox::StandardButton mapType(int btn) { switch (btn) { case Dialog::Help: return QDialogButtonBox::Help; case Dialog::Ok: return QDialogButtonBox::Ok; case Dialog::Apply: return QDialogButtonBox::Apply; case Dialog::Cancel: return QDialogButtonBox::Cancel; case Dialog::Close: return QDialogButtonBox::Close; case Dialog::No: return QDialogButtonBox::No; case Dialog::Yes: return QDialogButtonBox::Yes; case Dialog::Reset: return QDialogButtonBox::Reset; default: return QDialogButtonBox::NoButton; } } void Dialog::setButtons(ButtonCodes buttons) { if (buttonBox && buttons==buttonTypes) { return; } QFlags btns; if (buttons&Help) { btns|=QDialogButtonBox::Help; } if (buttons&Ok) { btns|=QDialogButtonBox::Ok; } if (buttons&Apply) { btns|=QDialogButtonBox::Apply; } if (buttons&Cancel) { btns|=QDialogButtonBox::Cancel; } if (buttons&Close) { btns|=QDialogButtonBox::Close; } if (buttons&No) { btns|=QDialogButtonBox::No; } if (buttons&Yes) { btns|=QDialogButtonBox::Yes; } if (buttons&Reset) { btns|=QDialogButtonBox::Reset; } buttonTypes=(int)btns; bool needToCreate=true; if (buttonBox) { needToCreate=false; buttonBox->clear(); buttonBox->setStandardButtons(btns); userButtons.clear(); } else { buttonBox = new QDialogButtonBox(btns, Qt::Horizontal, this); #ifdef Q_OS_MAC buttonBox->setStyle(buttonProxyStyle()); #endif } if (buttons&Help) { setButtonGuiItem(QDialogButtonBox::Help, StdGuiItem::help()); } if (buttons&Ok) { setButtonGuiItem(QDialogButtonBox::Ok, StdGuiItem::ok()); } if (buttons&Apply) { setButtonGuiItem(QDialogButtonBox::Apply, StdGuiItem::apply()); } if (buttons&Cancel) { setButtonGuiItem(QDialogButtonBox::Cancel, StdGuiItem::cancel()); } if (buttons&Close) { setButtonGuiItem(QDialogButtonBox::Close, StdGuiItem::close()); } if (buttons&No) { setButtonGuiItem(QDialogButtonBox::No, StdGuiItem::no()); } if (buttons&Yes) { setButtonGuiItem(QDialogButtonBox::Yes, StdGuiItem::yes()); } if (buttons&Reset) { setButtonGuiItem(QDialogButtonBox::Reset, StdGuiItem::reset()); } if (buttons&User3) { QPushButton *button=new QPushButton(buttonBox); userButtons.insert(User3, button); buttonBox->addButton(button, QDialogButtonBox::ActionRole); } if (buttons&User2) { QPushButton *button=new QPushButton(buttonBox); userButtons.insert(User2, button); buttonBox->addButton(button, QDialogButtonBox::ActionRole); } if (buttons&User1) { QPushButton *button=new QPushButton(buttonBox); userButtons.insert(User1, button); buttonBox->addButton(button, QDialogButtonBox::ActionRole); } if (needToCreate && mw && buttonBox) { create(); } } void Dialog::setDefaultButton(ButtonCode button) { QAbstractButton *b=getButton(button); if (b) { qobject_cast(b)->setDefault(true); } defButton=button; } void Dialog::setButtonText(ButtonCode button, const QString &text) { QAbstractButton *b=getButton(button); if (b) { b->setText(text); } } void Dialog::setButtonGuiItem(ButtonCode button, const GuiItem &item) { QAbstractButton *b=getButton(button); if (b) { b->setText(item.text); if (style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) { if (!item.icon.isEmpty()) { b->setIcon(Icon(item.icon)); } else if (item.monoIcon>0) { b->setIcon(monoIcon(item)); } else { b->setIcon(QIcon()); } } } } void Dialog::setButtonGuiItem(QDialogButtonBox::StandardButton button, const GuiItem &item) { QAbstractButton *b=buttonBox->button(button); if (b) { b->setText(item.text); if (style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) { if (!item.icon.isEmpty()) { b->setIcon(Icon(item.icon)); } else if (item.monoIcon>0) { b->setIcon(monoIcon(item)); } else { b->setIcon(QIcon()); } } } } void Dialog::setButtonMenu(ButtonCode button, QMenu *menu, ButtonPopupMode popupmode) { Q_UNUSED(popupmode) QAbstractButton *b=getButton(button); if (b) { qobject_cast(b)->setMenu(menu); } } void Dialog::enableButton(ButtonCode button, bool enable) { QAbstractButton *b=getButton(button); if (b) { b->setEnabled(enable); } } bool Dialog::isButtonEnabled(ButtonCode button) { QAbstractButton *b=getButton(button); return b ? b->isEnabled() : false; } void Dialog::setMainWidget(QWidget *widget) { if (mw) { return; } mw=widget; if (mw && buttonBox) { create(); } } void Dialog::slotButtonClicked(int button) { switch (button) { case Ok: accept(); break; case Cancel: reject(); break; case Close: reject(); break; default: break; } } void Dialog::buttonPressed(QAbstractButton *button) { if (buttonTypes&QDialogButtonBox::Help && button==buttonBox->button(QDialogButtonBox::Help)) { slotButtonClicked(Help); } else if (buttonTypes&QDialogButtonBox::Ok && button==buttonBox->button(QDialogButtonBox::Ok)) { slotButtonClicked(Ok); } else if (buttonTypes&QDialogButtonBox::Apply && button==buttonBox->button(QDialogButtonBox::Apply)) { slotButtonClicked(Apply); } else if (buttonTypes&QDialogButtonBox::Cancel && button==buttonBox->button(QDialogButtonBox::Cancel)) { slotButtonClicked(Cancel); } else if (buttonTypes&QDialogButtonBox::Close && button==buttonBox->button(QDialogButtonBox::Close)) { slotButtonClicked(Close); } else if (buttonTypes&QDialogButtonBox::No && button==buttonBox->button(QDialogButtonBox::No)) { slotButtonClicked(No); } else if (buttonTypes&QDialogButtonBox::Yes && button==buttonBox->button(QDialogButtonBox::Yes)) { slotButtonClicked(Yes); } else if (buttonTypes&QDialogButtonBox::Reset && button==buttonBox->button(QDialogButtonBox::Reset)) { slotButtonClicked(Reset); } else if (userButtons.contains(User1) && userButtons[User1]==button) { slotButtonClicked(User1); } else if (userButtons.contains(User2) && userButtons[User2]==button) { slotButtonClicked(User2); } else if (userButtons.contains(User3) && userButtons[User3]==button) { slotButtonClicked(User3); } } void Dialog::create() { QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, this); layout->addWidget(mw); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonPressed(QAbstractButton *))); } QAbstractButton *Dialog::getButton(ButtonCode button) { QDialogButtonBox::StandardButton mapped=mapType(button); QAbstractButton *b=QDialogButtonBox::NoButton==mapped ? 0 : buttonBox->button(mapped); if (!b && userButtons.contains(button)) { b=userButtons[button]; } return b; } void Dialog::showEvent(QShowEvent *e) { if (!shown) { shown=true; AcceleratorManager::manage(this); if (defButton) { setDefaultButton((ButtonCode)defButton); } if (buttonBox && mw) { QSize mwSize=mw->minimumSize(); if (mwSize.width()<16 || mwSize.height()<16) { mwSize=mw->minimumSizeHint(); } if (mwSize.width()>15 && mwSize.height()>15) { setMinimumHeight(qMax(minimumHeight(), buttonBox->height()+layout()->spacing()+mwSize.height()+(2*layout()->margin()))); setMinimumWidth(qMax(minimumWidth(), mwSize.width()+(2*layout()->margin()))); } } } #ifdef Q_OS_MAC if (!isModal()) { OSXStyle::self()->addWindow(this); } #endif QDialog::showEvent(e); } #ifdef Q_OS_MAC void Dialog::hideEvent(QHideEvent *e) { OSXStyle::self()->removeWindow(this); QDialog::hideEvent(e); } void Dialog::closeEvent(QCloseEvent *e) { OSXStyle::self()->removeWindow(this); QDialog::closeEvent(e); } #endif cantata-2.2.0/support/dialog.h000066400000000000000000000125261316350454000162610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DIALOG_H #define DIALOG_H #include "config.h" #include #include #include #include #include #ifdef Q_OS_MAC #include #endif struct GuiItem { GuiItem(const QString &t=QString(), const QString &i=QString()) : text(t), icon(i), monoIcon(0), red(false) { } GuiItem(const QString &t, int i, bool r=false) : text(t), monoIcon(i), red(r) { } QString text; QString icon; int monoIcon; bool red; }; namespace StdGuiItem { extern GuiItem ok(); extern GuiItem cancel(); extern GuiItem yes(); extern GuiItem no(); extern GuiItem discard(); extern GuiItem save(); extern GuiItem apply(); extern GuiItem close(); extern GuiItem help(); extern GuiItem overwrite(); extern GuiItem reset(); extern GuiItem cont(); extern GuiItem del(); extern GuiItem stop(); extern GuiItem remove(); extern GuiItem back(bool useRtl=false); extern GuiItem forward(bool useRtl=false); extern QSet standardNames(); }; class QAbstractButton; class QMenu; class Dialog : public QDialog { Q_OBJECT Q_ENUMS(ButtonCode) public: #ifdef Q_OS_MAC class ButtonProxyStyle : public QProxyStyle { public: ButtonProxyStyle(); int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const; }; static ButtonProxyStyle * buttonProxyStyle(); #endif enum ButtonCode { None = 0x00000000, Help = 0x00000001, ///< Show Help button. (this button will run the help set with setHelp) Default = 0x00000002, ///< Show Default button. Ok = 0x00000004, ///< Show Ok button. (this button accept()s the dialog; result set to QDialog::Accepted) Apply = 0x00000008, ///< Show Apply button. Try = 0x00000010, ///< Show Try button. Cancel = 0x00000020, ///< Show Cancel-button. (this button reject()s the dialog; result set to QDialog::Rejected) Close = 0x00000040, ///< Show Close-button. (this button closes the dialog) No = 0x00000080, ///< Show No button. (this button closes the dialog and sets the result to KDialog::No) Yes = 0x00000100, ///< Show Yes button. (this button closes the dialog and sets the result to KDialog::Yes) Reset = 0x00000200, ///< Show Reset button Details = 0x00000400, ///< Show Details button. (this button will show the detail widget set with setDetailsWidget) User1 = 0x00001000, ///< Show User defined button 1. User2 = 0x00002000, ///< Show User defined button 2. User3 = 0x00004000, ///< Show User defined button 3. NoDefault = 0x00008000 ///< Used when specifying a default button; indicates that no button should be marked by default. }; Q_DECLARE_FLAGS(ButtonCodes, ButtonCode) enum ButtonPopupMode { InstantPopup = 0, DelayedPopup = 1 }; Dialog(QWidget *parent, const QString &name=QString(), const QSize &defSize=QSize()); virtual ~Dialog(); void setCaption(const QString &cap) { setWindowTitle(cap); } void setButtons(ButtonCodes buttons); void setDefaultButton(ButtonCode button); void setButtonText(ButtonCode button, const QString &text); void setButtonGuiItem(ButtonCode button, const GuiItem &item); void setButtonMenu(ButtonCode button, QMenu *menu, ButtonPopupMode popupmode=InstantPopup); void enableButton(ButtonCode button, bool enable); void enableButtonOk(bool enable) { enableButton(Ok, enable); } bool isButtonEnabled(ButtonCode button); void setMainWidget(QWidget *widget); virtual void slotButtonClicked(int button); QWidget * mainWidget() { return mw; } const QSize & configuredSize() const { return cfgSize; } void resize(int w, int h) { resize(QSize(w, h)); } void resize(const QSize &sz); #ifdef Q_OS_MAC virtual void hideEvent(QHideEvent *e); virtual void closeEvent(QCloseEvent *e); #endif private Q_SLOTS: void buttonPressed(QAbstractButton *button); private: void create(); QAbstractButton *getButton(ButtonCode button); void setButtonGuiItem(QDialogButtonBox::StandardButton button, const GuiItem &item); virtual void showEvent(QShowEvent *e); private: int defButton; int buttonTypes; QWidget *mw; QDialogButtonBox *buttonBox; QMap userButtons; QSize cfgSize; bool shown; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Dialog::ButtonCodes) #endif cantata-2.2.0/support/fancytabwidget.cpp000066400000000000000000000547131316350454000203540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond */ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://qt.nokia.com/contact. ** **************************************************************************/ #include "fancytabwidget.h" #include "icon.h" #include "action.h" #include "utils.h" #include "config.h" #ifdef Q_OS_MAC #include "osxstyle.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include static inline int sidebarSpacing(bool withText) { int sp=Utils::scaleForDpi(12); if (!withText) { sp*=1.25; } return sp; } static inline Qt::TextElideMode elideMode() { return Qt::LeftToRight==QApplication::layoutDirection() ? Qt::ElideRight : Qt::ElideLeft; } static int largeIconSize=32; static int smallIconSize=16; void FancyTabWidget::setup() { largeIconSize=Icon::stdSize(Utils::scaleForDpi(32)); smallIconSize=Icon::stdSize(Utils::scaleForDpi(16)); } int FancyTabWidget::iconSize(bool large) { return large ? largeIconSize : smallIconSize; } static void drawIcon(const QIcon &icon, const QRect &r, QPainter *p, const QSize &iconSize, bool selected) { #ifdef Q_OS_WIN Q_UNUSED(selected); QPixmap px = icon.pixmap(iconSize, QIcon::Normal); #else QPixmap px = icon.pixmap(iconSize, selected ? QIcon::Selected : QIcon::Normal); #endif QSize layoutSize = px.size() / px.DEVICE_PIXEL_RATIO(); p->drawPixmap(r.x()+(r.width()-layoutSize.width())/2.0, r.y()+(r.height()-layoutSize.height())/2.0, layoutSize.width(), layoutSize.height(), px); } void FancyTabProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const { if (PE_FrameTabBarBase!=element) { QProxyStyle::drawPrimitive(element, option, p, widget); } } int FancyTabProxyStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const { if (SH_TabBar_Alignment==hint && widget && qobject_cast(widget)) { QTabBar::Shape shape=static_cast(widget)->shape(); if (QTabBar::RoundedNorth==shape || QTabBar::RoundedSouth==shape) { return (int)Qt::AlignCenter; } } return QProxyStyle::styleHint(hint, option, widget, returnData); } void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const { const QStyleOptionTab *tabOpt = qstyleoption_cast(option); if (element != CE_TabBarTab || !tabOpt) { QProxyStyle::drawControl(element, option, p, widget); return; } const QRect rect = tabOpt->rect; const bool selected = tabOpt->state&State_Selected; const bool underMouse = tabOpt->state&State_MouseOver; const bool verticalTabs = tabOpt->shape == QTabBar::RoundedWest; const QString text = tabOpt->text; QTransform m; if (verticalTabs) { m = QTransform::fromTranslate(rect.left(), rect.bottom()); m.rotate(-90); } else { m = QTransform::fromTranslate(rect.left(), rect.top()); } const QRect draw_rect(QPoint(0, 0), m.mapRect(rect).size()); p->save(); p->setTransform(m); QRect iconRect(QPoint(8, 0), tabOpt->iconSize); QRect textRect(iconRect.topRight() + QPoint(4, 0), draw_rect.size()); textRect.setRight(draw_rect.width()); iconRect.translate(0, (draw_rect.height() - iconRect.height()) / 2); if (selected || underMouse) { #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #elif defined Q_OS_WIN QColor col = option->palette.highlight().color(); col.setAlphaF(0.25); #else QColor col = option->palette.highlight().color(); #endif if (!selected) { #if defined Q_OS_WIN col.setAlphaF(0.1); #else col.setAlphaF(0.2); #endif } p->fillRect(rect, col); } int textFlags = Qt::AlignTop | Qt::AlignVCenter; #ifdef Q_OS_MAC p->setPen(selected && option->state&State_Active ? OSXStyle::self()->viewPalette().highlightedText().color() : OSXStyle::self()->viewPalette().foreground().color()); #elif defined Q_OS_WIN p->setPen(QApplication::palette().foreground().color()); #else p->setPen(selected && option->state&State_Active ? QApplication::palette().highlightedText().color() : QApplication::palette().foreground().color()); #endif drawIcon(tabOpt->icon, iconRect, p, tabOpt->iconSize, selected && option->state&State_Active); QString txt=text; txt.replace("&", ""); txt=p->fontMetrics().elidedText(txt, elideMode(), textRect.width()); p->drawText(textRect.translated(0, -1), textFlags, txt); p->restore(); } void FancyTabProxyStyle::polish(QWidget* widget) { if (QLatin1String("QTabBar")==QString(widget->metaObject()->className())) { widget->setMouseTracking(true); widget->installEventFilter(this); } QProxyStyle::polish(widget); } void FancyTabProxyStyle::polish(QApplication* app) { QProxyStyle::polish(app); } void FancyTabProxyStyle::polish(QPalette &palette) { QProxyStyle::polish(palette); } bool FancyTabProxyStyle::eventFilter(QObject* o, QEvent* e) { #ifndef Q_OS_MAC QTabBar *bar = qobject_cast(o); if (bar && (e->type() == QEvent::MouseMove || e->type() == QEvent::Leave)) { QMouseEvent *event = static_cast(e); const QString oldHoveredTab = bar->property("tab_hover").toString(); const QString hoveredTab = e->type() == QEvent::Leave ? QString() : bar->tabText(bar->tabAt(event->pos())); bar->setProperty("tab_hover", hoveredTab); if (oldHoveredTab != hoveredTab) { bar->update(); } } #endif return false; } FancyTab::FancyTab(FancyTabBar* tabbar) : QWidget(tabbar), tabbar(tabbar), underMouse(false) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); setAttribute(Qt::WA_Hover, true); } FancyTabBar::FancyTabBar(QWidget *parent, bool text, int iSize, Pos pos) : QWidget(parent) , withText(text) , pos(pos) , icnSize(iSize) { setFont(Utils::smallFont(font())); setAttribute(Qt::WA_Hover, true); setFocusPolicy(Qt::NoFocus); setMouseTracking(true); // Needed for hover events triggerTimer.setSingleShot(true); QBoxLayout* layout=0; if (Side!=pos) { setMinimumHeight(tabSizeHint().height()); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout=new QHBoxLayout; } else { setMinimumWidth(tabSizeHint().width()); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); layout=new QVBoxLayout; layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); } layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); // We use a zerotimer to keep the sidebar responsive connect(&triggerTimer, SIGNAL(timeout()), this, SLOT(emitCurrentIndex())); } FancyTabBar::~FancyTabBar() { } QSize FancyTab::sizeHint() const { int iconSize = tabbar->iconSize(); bool withText = tabbar->showText(); int spacing = sidebarSpacing(withText); int padding = FancyTabBar::Side==tabbar->position() ? Utils::scaleForDpi(12) : 0; if (withText) { QFontMetrics fm(font()); int textWidth = fm.width(text)*1.1; int width = qMax(iconSize, qMin(3*iconSize, textWidth)) + spacing; return QSize(width, iconSize + spacing + fm.height() + padding); } else { return QSize(iconSize + spacing + padding, iconSize + spacing + padding); } } QSize FancyTabBar::tabSizeHint() const { int spacing = sidebarSpacing(withText); int padding = Side==pos ? Utils::scaleForDpi(12) : 0; if (withText) { QFontMetrics fm(font()); int maxTw=0; foreach (FancyTab *tab, tabs) { maxTw=qMax(maxTw, tab->sizeHint().width()); } return QSize(qMax(icnSize + spacing, maxTw), icnSize + spacing + fm.height() + padding); } else { return QSize(icnSize + spacing, icnSize + spacing + padding); } } void FancyTabBar::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); for (int i = 0; i < count(); ++i) { if (i != currentIndex()) { paintTab(&p, i); } } // paint active tab last, since it overlaps the neighbors if (currentIndex() != -1) { paintTab(&p, currentIndex()); } } void FancyTab::enterEvent(QEvent*) { underMouse = true; } void FancyTab::leaveEvent(QEvent*) { underMouse = false; } QSize FancyTabBar::sizeHint() const { QSize sh = tabSizeHint(); return Side!=pos ? QSize(sh.width() * tabs.count(), sh.height()) : QSize(sh.width(), sh.height() * tabs.count()); } QSize FancyTabBar::minimumSizeHint() const { QSize sh = tabSizeHint(); return Side!=pos ? QSize(sh.width() * tabs.count(), sh.height()) : QSize(sh.width(), sh.height() * tabs.count()); } QRect FancyTabBar::tabRect(int index) const { return tabs[index]->geometry(); } QString FancyTabBar::tabToolTip(int index) const { return tabs[index]->toolTip(); } void FancyTabBar::setTabToolTip(int index, const QString &toolTip) { tabs[index]->setToolTip(toolTip); } // This keeps the sidebar responsive since // we get a repaint before loading the // mode itself void FancyTabBar::emitCurrentIndex() { emit currentChanged(currentIdx); } void FancyTabBar::mousePressEvent(QMouseEvent *e) { if (Qt::LeftButton!=e->button()) { return; } e->accept(); for (int index = 0; index < tabs.count(); ++index) { if (tabRect(index).contains(e->pos())) { currentIdx = index; update(); triggerTimer.start(0); break; } } } void FancyTabBar::addTab(const QIcon &icon, const QString &label, const QString &tt) { FancyTab *tab = new FancyTab(this); tab->icon = icon; tab->text = label; tabs.append(tab); if (!tt.isEmpty()) { tab->setToolTip(tt); } else if (!withText) { tab->setToolTip(label); } qobject_cast(layout())->insertWidget(layout()->count()-(Side==pos ? 1 : 0), tab); } void FancyTabBar::addSpacer(int size) { qobject_cast(layout())->insertSpacerItem(layout()->count()-1, new QSpacerItem(0, size, QSizePolicy::Fixed, QSizePolicy::Maximum)); } void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const { if (!validIndex(tabIndex)) { qWarning("invalid index"); return; } painter->save(); QRect rect = tabRect(tabIndex); bool selected = (tabIndex == currentIdx); bool underMouse = tabs[tabIndex]->underMouse; if (selected || underMouse) { #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #elif defined Q_OS_WIN QColor col = palette().highlight().color(); col.setAlphaF(0.25); #else QColor col = palette().highlight().color(); #endif if (!selected) { #if defined Q_OS_WIN col.setAlphaF(0.1); #else col.setAlphaF(0.2); #endif } painter->fillRect(rect, col); } selected = selected && (palette().currentColorGroup()==QPalette::Active || (palette().highlightedText().color()==palette().brush(QPalette::Active, QPalette::HighlightedText).color())); if (withText) { QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), elideMode(), width())); QRect tabTextRect(tabRect(tabIndex)); QRect tabIconRect(tabTextRect); tabIconRect.adjust(+4, +4, -4, -4); tabTextRect.translate(0, -2); #ifdef Q_OS_MAC painter->setPen(selected ? OSXStyle::self()->viewPalette().highlightedText().color() : OSXStyle::self()->viewPalette().foreground().color()); #elif defined Q_OS_WIN painter->setPen(QApplication::palette().foreground().color()); #else painter->setPen(selected ? QApplication::palette().highlightedText().color() : QApplication::palette().foreground().color()); #endif int textFlags = Qt::AlignCenter | Qt::AlignBottom; painter->drawText(tabTextRect, textFlags, tabText); const int textHeight = painter->fontMetrics().height(); tabIconRect.adjust(0, 4, 0, -textHeight); drawIcon(tabIcon(tabIndex), tabIconRect, painter, QSize(icnSize, icnSize), selected); } else { drawIcon(tabIcon(tabIndex), rect, painter, QSize(icnSize, icnSize), selected); } painter->restore(); } void FancyTabBar::setCurrentIndex(int index) { currentIdx = index; update(); emit currentChanged(currentIdx); } FancyTabWidget::FancyTabWidget(QWidget *parent) : QWidget(parent) , styleSetting(0) , tabBar(NULL) , stack_(new QStackedWidget(this)) , sideWidget(new QWidget) , sideLayout(new QVBoxLayout) , topLayout(new QVBoxLayout) , menu(0) , proxyStyle(new FancyTabProxyStyle) { sideLayout->setSpacing(0); sideLayout->setMargin(0); sideLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding)); sideWidget->setLayout(sideLayout); sideWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); topLayout->setMargin(0); topLayout->setSpacing(0); topLayout->addWidget(stack_); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->setMargin(0); mainLayout->setSpacing(0); mainLayout->addWidget(sideWidget); mainLayout->addLayout(topLayout); setLayout(mainLayout); } void FancyTabWidget::addTab(QWidget *tab, const QIcon &icon, const QString &label, const QString &tt, bool enabled) { stack_->addWidget(tab); items << Item(icon, label, tt, enabled); setMinimumWidth(128); } int FancyTabWidget::currentIndex() const { return stack_->currentIndex(); } QWidget * FancyTabWidget::currentWidget() const { return stack_->currentWidget(); } QWidget * FancyTabWidget::widget(int index) const { return stack_->widget(index); } int FancyTabWidget::count() const { return stack_->count(); } int FancyTabWidget::visibleCount() const { int c=0; foreach (const Item &i, items) { if (i.enabled) { c++; } } return c; } QSize FancyTabWidget::tabSize() const { if (FancyTabBar *bar = qobject_cast(tabBar)) { return bar->tabSizeHint(); } return QSize(); } void FancyTabWidget::setCurrentIndex(int idx) { if (!isEnabled(idx)) { return; } int index=IndexToTab(idx); if (FancyTabBar* bar = qobject_cast(tabBar)) { bar->setCurrentIndex(index); } else if (QTabBar* bar = qobject_cast(tabBar)) { bar->setCurrentIndex(index); showWidget(index); } else { stack_->setCurrentIndex(idx); // ?? IS this *ever* called??? } } void FancyTabWidget::showWidget(int index) { int idx=tabToIndex(index); stack_->setCurrentIndex(idx); emit currentChanged(idx); } void FancyTabWidget::setStyle(int s) { if (s==styleSetting && tabBar) { return; } // Remove previous tab bar delete tabBar; tabBar = NULL; // use_background_ = false; // Create new tab bar if (Tab==(s&Style_Mask) || (Small==(s&Style_Mask) && !(s&IconOnly))) { switch (s&Position_Mask) { default: case Side: makeTabBar(QApplication::isRightToLeft() ? QTabBar::RoundedEast : QTabBar::RoundedWest, !(s&IconOnly), true, Small==(s&Style_Mask)); break; case Top: makeTabBar(QTabBar::RoundedNorth, !(s&IconOnly), true, Small==(s&Style_Mask)); break; case Bot: makeTabBar(QTabBar::RoundedSouth, !(s&IconOnly), true, Small==(s&Style_Mask)); break; } } else { FancyTabBar* bar = new FancyTabBar(this, !(s&IconOnly), Small==(s&Style_Mask) ? smallIconSize : largeIconSize, Side==(s&Position_Mask) ? FancyTabBar::Side : (Top==(s&Position_Mask) ? FancyTabBar::Top : FancyTabBar::Bot)); switch (s&Position_Mask) { default: case Side: sideLayout->insertWidget(0, bar); break; case Bot: topLayout->insertWidget(1, bar); break; case Top: topLayout->insertWidget(0, bar); break; } tabBar = bar; int index=0; QList::Iterator it=items.begin(); QList::Iterator end=items.end(); for (; it!=end; ++it) { if (!(*it).enabled) { (*it).index=-1; continue; } if (Item::Type_Spacer==(*it).type) { bar->addSpacer((*it).spacerSize); } else { bar->addTab((*it).tabIcon, (*it).tabLabel, (*it).tabTooltip); } (*it).index=index++; } bar->setCurrentIndex(IndexToTab(stack_->currentIndex())); connect(bar, SIGNAL(currentChanged(int)), SLOT(showWidget(int))); } tabBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); styleSetting = s; emit styleChanged(s); update(); } void FancyTabWidget::toggleTab(int tab, bool show) { if (tab>=0 && tabsetShape(shape); bar->setDocumentMode(true); bar->setUsesScrollButtons(true); if (QTabBar::RoundedWest==shape) { bar->setIconSize(QSize(smallIconSize, smallIconSize)); } if (fancy) { bar->setStyle(proxyStyle.data()); } if (QTabBar::RoundedNorth==shape) { topLayout->insertWidget(0, bar); } else if (QTabBar::RoundedSouth==shape) { topLayout->insertWidget(1, bar); } else { sideLayout->insertWidget(0, bar); } int index=0; QList::Iterator it=items.begin(); QList::Iterator end=items.end(); for (; it!=end; ++it) { if ((*it).type != Item::Type_Tab || !(*it).enabled) { (*it).index=-1; continue; } QString label = (*it).tabLabel; if (QTabBar::RoundedWest==shape) { label = QFontMetrics(font()).elidedText(label, elideMode(), 120); } int tabId = -1; if (icons && text) { tabId = bar->addTab((*it).tabIcon, label); } else if (icons) { tabId = bar->addTab((*it).tabIcon, QString()); } else if (text) { tabId = bar->addTab(label); } if (!(*it).tabTooltip.isEmpty()) { bar->setTabToolTip(tabId, (*it).tabTooltip); } else if (!text) { bar->setTabToolTip(tabId, (*it).tabLabel); } (*it).index=index++; } bar->setCurrentIndex(IndexToTab(stack_->currentIndex())); connect(bar, SIGNAL(currentChanged(int)), SLOT(showWidget(int))); tabBar = bar; } int FancyTabWidget::tabToIndex(int tab) const { for (int i=0; i-1 && index-1 && index(tabBar)) { static_cast(tabBar)->setTabToolTip(item.index, item.tabTooltip); } else { static_cast(tabBar)->setTabToolTip(item.index, item.tabTooltip); } } } } void FancyTabWidget::recreate() { int s=styleSetting; styleSetting=0; setStyle(s); } void FancyTabWidget::setHiddenPages(const QStringList &hidden) { QSet h=hidden.toSet(); if (h==hiddenPages().toSet()) { return; } bool needToRecreate=false; bool needToSetCurrent=false; for (int i=0; imetaObject()->className())) { items[i].enabled=!items[i].enabled; emit tabToggled(i); needToRecreate=true; if (i==currentIndex()) { needToSetCurrent=true; } } } if (needToRecreate) { recreate(); } if (needToSetCurrent) { for (int i=0; imetaObject()->className(); } } } return pages; } cantata-2.2.0/support/fancytabwidget.h000066400000000000000000000146501316350454000200150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://qt.nokia.com/contact. ** **************************************************************************/ #ifndef FANCYTABWIDGET_H #define FANCYTABWIDGET_H #include #include #include #include #include #include #include class QMenu; class QPainter; class QSignalMapper; class QStackedWidget; class QStatusBar; class QVBoxLayout; class FancyTabProxyStyle : public QProxyStyle { Q_OBJECT public: void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const; void drawControl(ControlElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const; int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const; void polish(QWidget *widget); void polish(QApplication *app); void polish(QPalette &palette); protected: bool eventFilter(QObject *o, QEvent *e); }; class FancyTabBar; class FancyTab : public QWidget { public: FancyTab(FancyTabBar *tabbar); QSize sizeHint() const; QIcon icon; QString text; bool underMouse; protected: void enterEvent(QEvent *); void leaveEvent(QEvent *); private: FancyTabBar *tabbar; }; class FancyTabBar : public QWidget { Q_OBJECT public: enum Pos { Side, Top, Bot }; FancyTabBar(QWidget *parent, bool text, int iSize, Pos pos); ~FancyTabBar(); void paintEvent(QPaintEvent *event); void paintTab(QPainter *painter, int tabIndex) const; void mousePressEvent(QMouseEvent *); bool validIndex(int index) const { return index >= 0 && index < tabs.count(); } QSize sizeHint() const; QSize minimumSizeHint() const; void addTab(const QIcon &icon, const QString &label, const QString &tt); void addSpacer(int size = 40); void removeTab(int index) { delete tabs.takeAt(index); } void setCurrentIndex(int index); int currentIndex() const { return currentIdx; } void setTabToolTip(int index, const QString &toolTip); QString tabToolTip(int index) const; QIcon tabIcon(int index) const {return tabs.at(index)->icon; } QString tabText(int index) const { return tabs.at(index)->text; } int count() const {return tabs.count(); } QRect tabRect(int index) const; bool showText() const { return withText; } int iconSize() const { return icnSize; } QSize tabSizeHint() const; Pos position() const { return pos; } Q_SIGNALS: void currentChanged(int); public Q_SLOTS: void emitCurrentIndex(); private: int currentIdx; QList tabs; QTimer triggerTimer; bool withText : 1; Pos pos : 2; int icnSize; }; class FancyTabWidget : public QWidget { Q_OBJECT public: static void setup(); static int iconSize(bool large=true); FancyTabWidget(QWidget *parent); enum Style { Side = 0x0001, Top = 0x0002, Bot = 0x0003, Large = 0x0010, Small = 0x0020, Tab = 0x0030, IconOnly = 0x0100, Position_Mask = 0x000F, Style_Mask = 0x00F0, Options_Mask = 0x0100, All_Mask = Position_Mask|Style_Mask|Options_Mask }; struct Item { Item(const QIcon &icon, const QString &label, const QString &tt, bool en) : type(Type_Tab), tabLabel(label), tabTooltip(tt), tabIcon(icon), spacerSize(0), enabled(en), index(-1) {} Item(int size) : type(Type_Spacer), spacerSize(size), enabled(true), index(-1) {} enum Type { Type_Tab, Type_Spacer }; Type type; QString tabLabel; QString tabTooltip; QIcon tabIcon; int spacerSize; bool enabled; int index; }; void addTab(QWidget *tab, const QIcon &icon, const QString &label, const QString &tt=QString(), bool enabled=true); int currentIndex() const; QWidget * currentWidget() const; bool isEnabled(int index) const { return index>=0 && index=0 && index items; QWidget *tabBar; QStackedWidget *stack_; QWidget *sideWidget; QVBoxLayout *sideLayout; QVBoxLayout *topLayout; QMenu *menu; QScopedPointer proxyStyle; }; #endif // FANCYTABWIDGET_H cantata-2.2.0/support/flattoolbutton.cpp000066400000000000000000000034301316350454000204270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "flattoolbutton.h" #include "utils.h" #include #include #include FlatToolButton::FlatToolButton(QWidget *parent) : QToolButton(parent) { setStyleSheet("QToolButton {border: 0}"); } void FlatToolButton::paintEvent(QPaintEvent *e) { if (isChecked() || isDown()) { QPainter p(this); QColor col(palette().color(QPalette::WindowText)); QRect r(rect()); QPainterPath path=Utils::buildPath(QRectF(r.x()+0.5, r.y()+0.5, r.width()-1, r.height()-1), 2.5); p.setRenderHint(QPainter::Antialiasing, true); col.setAlphaF(0.4); p.setPen(col); p.drawPath(path); col.setAlphaF(0.1); p.fillPath(path, col); } QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); // if (isDown()) { // opt.state&=QStyle::State_Sunken; // } p.drawComplexControl(QStyle::CC_ToolButton, opt); } cantata-2.2.0/support/flattoolbutton.h000066400000000000000000000023141316350454000200740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FLATTOOLBUTTON_H #define FLATTOOLBUTTON_H #include class FlatToolButton : public QToolButton { public: #ifdef Q_OS_MAC explicit FlatToolButton(QWidget *parent = 0); void paintEvent(QPaintEvent *e); #else explicit FlatToolButton(QWidget *parent = 0) : QToolButton(parent) { setAutoRaise(true); } #endif }; #endif // MENUBUTTON_H cantata-2.2.0/support/globalstatic.h000066400000000000000000000021441316350454000174650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef GLOBAL_STATIC_H #define GLOBAL_STATIC_H #include #define GLOBAL_STATIC(CLASS, VAR) \ CLASS * CLASS::self() \ { \ static CLASS *VAR=0; \ if (!VAR) { \ VAR=new CLASS; \ } \ return VAR; \ } #endif cantata-2.2.0/support/gtkstyle.cpp000066400000000000000000000065761316350454000172330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gtkstyle.h" #include "config.h" #include "utils.h" #include "proxystyle.h" #include #include #include #include #include #include #include #if defined Q_OS_WIN || defined Q_OS_MAC || defined QT_NO_STYLE_GTK #define NO_GTK_SUPPORT #endif bool GtkStyle::isActive() { static bool usingGtkStyle=false; #if defined Q_OS_WIN || defined Q_OS_MAC || defined QT_NO_STYLE_GTK static bool init=false; if (!init) { init=true; usingGtkStyle=QApplication::style()->inherits("QGtkStyle"); if (usingGtkStyle) { QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus); } } #endif return usingGtkStyle; } void GtkStyle::drawSelection(const QStyleOptionViewItem &opt, QPainter *painter, double opacity) { static const int constMaxDimension=32; static QCache cache(30000); if (opt.rect.width()<2 || opt.rect.height()<2) { return; } int width=qMin(constMaxDimension, opt.rect.width()); QString key=QString::number(width)+QChar(':')+QString::number(opt.rect.height()); QPixmap *pix=cache.object(key); if (!pix) { pix=new QPixmap(width, opt.rect.height()); QStyleOptionViewItem styleOpt(opt); pix->fill(Qt::transparent); QPainter p(pix); styleOpt.state=opt.state; styleOpt.state&=~(QStyle::State_Selected|QStyle::State_MouseOver); styleOpt.state|=QStyle::State_Selected|QStyle::State_Enabled|QStyle::State_Active; styleOpt.viewItemPosition = QStyleOptionViewItem::OnlyOne; styleOpt.rect=QRect(0, 0, opt.rect.width(), opt.rect.height()); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &styleOpt, &p, 0); p.end(); cache.insert(key, pix, pix->width()*pix->height()); } double opacityB4=painter->opacity(); painter->setOpacity(opacity); if (opt.rect.width()>pix->width()) { int half=qMin(opt.rect.width()>>1, pix->width()>>1); painter->drawPixmap(opt.rect.x(), opt.rect.y(), pix->copy(0, 0, half, pix->height())); if ((half*2)!=opt.rect.width()) { painter->drawTiledPixmap(opt.rect.x()+half, opt.rect.y(), (opt.rect.width()-((2*half))), opt.rect.height(), pix->copy(half-1, 0, 1, pix->height())); } painter->drawPixmap((opt.rect.x()+opt.rect.width())-half, opt.rect.y(), pix->copy(half, 0, half, pix->height())); } else { painter->drawPixmap(opt.rect, *pix); } painter->setOpacity(opacityB4); } cantata-2.2.0/support/gtkstyle.h000066400000000000000000000021441316350454000166630ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef GTKSTYLE_H #define GTKSTYLE_H #include #include class QPainter; class QWidget; namespace GtkStyle { extern bool isActive(); extern void drawSelection(const QStyleOptionViewItem &opt, QPainter *painter, double opacity); } #endif cantata-2.2.0/support/icon.cpp000066400000000000000000000073611316350454000163060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "icon.h" #include "utils.h" #include #include #include int Icon::stdSize(int v) { if (v<19) { return 16; } else if (v<=28) { return 22; } else if (v<=40) { return 32; } else if (v<=56) { return 48; } else if (v<=90) { return 64; } if (Utils::isHighDpi()) { if (v<=160) { return 128; } else { return 256; } } return 128; } int Icon::dlgIconSize() { static int size=-1; if (-1==size) { QMessageBox *box=new QMessageBox(0); box->setVisible(false); box->setIcon(QMessageBox::Information); box->ensurePolished(); QPixmap pix=box->iconPixmap(); if (pix.isNull() || pix.width()<16) { size=stdSize(QApplication::fontMetrics().height()*3.5); } else { size=pix.width(); } box->deleteLater(); } return size; } void Icon::init(QToolButton *btn, bool setFlat) { static int size=-1; if (-1==size) { size=QApplication::fontMetrics().height(); if (size>22) { size=stdSize(size*1.1); } else { size=16; } } btn->setIconSize(QSize(size, size)); if (setFlat) { btn->setAutoRaise(true); } } Icon Icon::getMediaIcon(const QString &name) { static QList modes=QList() << QIcon::Normal << QIcon::Disabled << QIcon::Active << QIcon::Selected; Icon icn; Icon icon(name); foreach (QIcon::Mode mode, modes) { icn.addPixmap(icon.pixmap(QSize(64, 64), mode).scaled(QSize(28, 28), Qt::KeepAspectRatio, Qt::SmoothTransformation), mode); icn.addPixmap(icon.pixmap(QSize(48, 48), mode), mode); icn.addPixmap(icon.pixmap(QSize(32, 32), mode), mode); icn.addPixmap(icon.pixmap(QSize(24, 24), mode), mode); icn.addPixmap(icon.pixmap(QSize(22, 22), mode), mode); icn.addPixmap(icon.pixmap(QSize(16, 16), mode), mode); } return icn; } Icon Icon::create(const QString &name, const QList &sizes, bool andSvg) { Icon icon; foreach (int s, sizes) { icon.addFile(QLatin1Char(':')+name+QString::number(s), QSize(s, s)); } if (andSvg) { icon.addFile(QLatin1Char(':')+name+QLatin1String(".svg")); } return icon; } Icon::Icon(const QStringList &names) { foreach (const QString &name, names) { Icon icn(name); if (!icn.isNull()) { *this=icn; return; } } *this=Icon("unknown"); } QPixmap Icon::getScaledPixmap(const QIcon &icon, int w, int h, int base) { QList sizes=icon.availableSizes(); foreach (const QSize &s, sizes) { if (s.width()==w && s.height()==h) { return icon.pixmap(s); } } return icon.pixmap(base, base).scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation); } cantata-2.2.0/support/icon.h000066400000000000000000000032371316350454000157510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ICON_H #define ICON_H #include class QToolButton; class Icon : public QIcon { public: explicit Icon(const QString &icon) : QIcon(QIcon::fromTheme(icon)) { } Icon(const QIcon &i) : QIcon(i) { } Icon(const QStringList &names); Icon() { } static int stdSize(int s); static int dlgIconSize(); static void init(QToolButton *btn, bool setFlat=true); static Icon getMediaIcon(const QString &name); static QString currentTheme() { return QIcon::themeName(); } static Icon create(const QString &name, const QList &sizes, bool andSvg=false); enum Std { Close, Clear }; static QPixmap getScaledPixmap(const QIcon &icon, int w, int h, int base); QPixmap getScaledPixmap(int w, int h, int base) const { return getScaledPixmap(*this, w, h, base); } }; #endif cantata-2.2.0/support/inputdialog.cpp000066400000000000000000000072151316350454000176730ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "inputdialog.h" #include "gtkstyle.h" #include "lineedit.h" #include "dialog.h" #include "utils.h" #include #include #include InputDialog::InputDialog(const QString &caption, const QString &label, const QString &value, QLineEdit::EchoMode echo, QWidget *parent) : Dialog(parent) , spin(0) { init(false, caption, label); edit->setText(value); edit->setEchoMode(echo); enableOkButton(); connect(edit, SIGNAL(textChanged(QString)), this, SLOT(enableOkButton())); } InputDialog::InputDialog(const QString &caption, const QString &label, int value, int minValue, int maxValue, int step, QWidget *parent) : Dialog(parent) , edit(0) { init(true, caption, label); spin->setRange(minValue, maxValue); spin->setValue(value); spin->setSingleStep(step); } void InputDialog::addExtraWidget(const QString &label, QWidget *w) { w->setParent(mainWidget()); qobject_cast(mainWidget()->layout())->addRow(new QLabel(label, mainWidget()), w); } void InputDialog::init(bool intInput, const QString &caption, const QString &label) { extra = 0; setButtons(Ok|Cancel); QWidget *wid=new QWidget(this); QFormLayout *layout=new QFormLayout(wid); if (intInput) { spin=new QSpinBox(wid); spin->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); setMinimumWidth(Utils::scaleForDpi(300)); } else { edit=new LineEdit(wid); edit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); setMinimumWidth(Utils::scaleForDpi(350)); } layout->addRow(new QLabel(label, wid), intInput ? static_cast(spin) : static_cast(edit)); layout->setMargin(0); setCaption(caption); setMainWidget(wid); setButtons(Ok|Cancel); } void InputDialog::enableOkButton() { enableButton(Ok, 0==edit || !edit->text().trimmed().isEmpty()); } int InputDialog::getInteger(const QString &caption, const QString &label, int value, int minValue, int maxValue, int step, int base, bool *ok, QWidget *parent) { Q_UNUSED(base) InputDialog dlg(caption, label, value, minValue, maxValue, step, parent); if (QDialog::Accepted==dlg.exec()) { if (ok) { *ok=true; } return dlg.spin->value(); } else { if (ok) { *ok=false; } return value; } } QString InputDialog::getText(const QString &caption, const QString &label, QLineEdit::EchoMode echoMode, const QString &value, bool *ok, QWidget *parent) { InputDialog dlg(caption, label, value, echoMode, parent); if (QDialog::Accepted==dlg.exec()) { if (ok) { *ok=true; } return dlg.edit->text(); } else { if (ok) { *ok=false; } return value; } } cantata-2.2.0/support/inputdialog.h000066400000000000000000000047661316350454000173500ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INPUT_DIALOG_H #define INPUT_DIALOG_H #include #include #include #include "dialog.h" class LineEdit; class InputDialog : public Dialog { Q_OBJECT public: InputDialog(const QString &caption, const QString &label, const QString &value, QLineEdit::EchoMode echo, QWidget *parent); InputDialog(const QString &caption, const QString &label, int value, int minValue, int maxValue, int step, QWidget *parent); static QString getText(const QString &caption, const QString &label, QLineEdit::EchoMode echoMode, const QString &value=QString(), bool *ok=0, QWidget *parent=0); static int getInteger(const QString &caption, const QString &label, int value=0, int minValue=INT_MIN, int maxValue=INT_MAX, int step=1, int base=10, bool *ok=0, QWidget *parent=0); static QString getText(const QString &caption, const QString &label, const QString &value=QString(), bool *ok=0, QWidget *parent=0) { return getText(caption, label, QLineEdit::Normal, value, ok, parent); } static QString getPassword(const QString &value=QString(), bool *ok=0, QWidget *parent=0) { return getText(tr("Password"), tr("Please enter password:"), QLineEdit::Password, value, ok, parent); } void addExtraWidget(const QString &label, QWidget *w); QSpinBox *spinBox() { return spin; } LineEdit *lineEdit() { return edit; } private: void init(bool intInput, const QString &caption, const QString &label); private Q_SLOTS: void enableOkButton(); private: QSpinBox *spin; LineEdit *edit; QWidget *extra; }; #endif // INPUT_DIALOG_H cantata-2.2.0/support/keysequencewidget.cpp000066400000000000000000000322131316350454000210750ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-2015 by the Quassel Project * * devel@quassel-irc.org * * * * This class has been inspired by KDE's KKeySequenceWidget and uses * * some code snippets of its implementation, part of kdelibs. * * The original file is * * Copyright (C) 1998 Mark Donohoe * * Copyright (C) 2001 Ellis Whitehead * * Copyright (C) 2007 Andreas Hartmetz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include // This defines the unicode symbols for special keys (kCommandUnicode and friends) #ifdef Q_OS_MAC # include #endif #include "action.h" #include "actioncollection.h" #include "keysequencewidget.h" KeySequenceButton::KeySequenceButton(KeySequenceWidget *d_, QWidget *parent) : QPushButton(parent), d(d_) { } bool KeySequenceButton::event(QEvent *e) { if (d->isRecording() && e->type() == QEvent::KeyPress) { keyPressEvent(static_cast(e)); return true; } // The shortcut 'alt+c' ( or any other dialog local action shortcut ) // ended the recording and triggered the action associated with the // action. In case of 'alt+c' ending the dialog. It seems that those // ShortcutOverride events get sent even if grabKeyboard() is active. if (d->isRecording() && e->type() == QEvent::ShortcutOverride) { e->accept(); return true; } return QPushButton::event(e); } void KeySequenceButton::keyPressEvent(QKeyEvent *e) { int keyQt = e->key(); if (keyQt == -1) { // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key. // We cannot do anything useful with those (several keys have -1, indistinguishable) // and QKeySequence.toString() will also yield a garbage string. QMessageBox::information(this, tr("The key you just pressed is not supported by Qt."), tr("Unsupported Key")); return d->cancelRecording(); } uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); //don't have the return or space key appear as first key of the sequence when they //were pressed to start editing - catch and them and imitate their effect if (!d->isRecording() && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) { d->startRecording(); d->_modifierKeys = newModifiers; d->updateShortcutDisplay(); return; } // We get events even if recording isn't active. if (!d->isRecording()) return QPushButton::keyPressEvent(e); e->accept(); d->_modifierKeys = newModifiers; switch (keyQt) { case Qt::Key_AltGr: //or else we get unicode salad return; case Qt::Key_Shift: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Meta: case Qt::Key_Menu: //unused (yes, but why?) d->updateShortcutDisplay(); break; default: if (!(d->_modifierKeys & ~Qt::SHIFT)) { // It's the first key and no modifier pressed. Check if this is // allowed if (!d->isOkWhenModifierless(keyQt)) return; } // We now have a valid key press. if (keyQt) { if ((keyQt == Qt::Key_Backtab) && (d->_modifierKeys & Qt::SHIFT)) { keyQt = Qt::Key_Tab | d->_modifierKeys; } else if (d->isShiftAsModifierAllowed(keyQt)) { keyQt |= d->_modifierKeys; } else keyQt |= (d->_modifierKeys & ~Qt::SHIFT); d->_keySequence = QKeySequence(keyQt); d->doneRecording(); } } } void KeySequenceButton::keyReleaseEvent(QKeyEvent *e) { if (e->key() == -1) { // ignore garbage, see keyPressEvent() return; } if (!d->isRecording()) return QPushButton::keyReleaseEvent(e); e->accept(); uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); // if a modifier that belongs to the shortcut was released... if ((newModifiers & d->_modifierKeys) < d->_modifierKeys) { d->_modifierKeys = newModifiers; d->updateShortcutDisplay(); } } /******************************************************************************/ KeySequenceWidget::KeySequenceWidget(QWidget *parent) : QWidget(parent), _shortcutsModel(0), _isRecording(false), _modifierKeys(0) { QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); _keyButton = new KeySequenceButton(this, this); _keyButton->setFocusPolicy(Qt::StrongFocus); _keyButton->setIcon(QIcon::fromTheme("configure")); _keyButton->setToolTip(tr("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a.")); layout->addWidget(_keyButton); _clearButton = new QToolButton(this); layout->addWidget(_clearButton); if (qApp->isLeftToRight()) _clearButton->setIcon(QIcon::fromTheme("edit-clear-locationbar-rtl", QIcon::fromTheme("edit-clear"))); else _clearButton->setIcon(QIcon::fromTheme("edit-clear-locationbar-ltr", QIcon::fromTheme("edit-clear"))); setLayout(layout); connect(_keyButton, SIGNAL(clicked()), SLOT(startRecording())); connect(_keyButton, SIGNAL(clicked()), SIGNAL(clicked())); connect(_clearButton, SIGNAL(clicked()), SLOT(clear())); connect(_clearButton, SIGNAL(clicked()), SIGNAL(clicked())); } void KeySequenceWidget::setModel(ShortcutsModel *model) { Q_ASSERT(!_shortcutsModel); _shortcutsModel = model; } bool KeySequenceWidget::isOkWhenModifierless(int keyQt) const { //this whole function is a hack, but especially the first line of code // if (QKeySequence(keyQt).toString().length() == 1) // return false; switch (keyQt) { case Qt::Key_Return: // case Qt::Key_Space: case Qt::Key_Tab: case Qt::Key_Backtab: //does this ever happen? case Qt::Key_Backspace: case Qt::Key_Delete: return false; default: return true; } } bool KeySequenceWidget::isShiftAsModifierAllowed(int keyQt) const { // Shift only works as a modifier with certain keys. It's not possible // to enter the SHIFT+5 key sequence for me because this is handled as // '%' by qt on my keyboard. // The working keys are all hardcoded here :-( if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) return true; if (QChar(keyQt).isLetter()) return true; switch (keyQt) { case Qt::Key_Return: case Qt::Key_Space: case Qt::Key_Backspace: case Qt::Key_Escape: case Qt::Key_Print: case Qt::Key_ScrollLock: case Qt::Key_Pause: case Qt::Key_PageUp: case Qt::Key_PageDown: case Qt::Key_Insert: case Qt::Key_Delete: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Left: case Qt::Key_Right: return true; default: return false; } } void KeySequenceWidget::updateShortcutDisplay() { QString s = _keySequence.toString(QKeySequence::NativeText); s.replace('&', QLatin1String("&&")); if (_isRecording) { if (_modifierKeys) { #ifdef Q_OS_MAC if (_modifierKeys & Qt::META) s += QChar(kControlUnicode); if (_modifierKeys & Qt::ALT) s += QChar(kOptionUnicode); if (_modifierKeys & Qt::SHIFT) s += QChar(kShiftUnicode); if (_modifierKeys & Qt::CTRL) s += QChar(kCommandUnicode); #else if (_modifierKeys & Qt::META) s += tr("Meta", "Meta key") + '+'; if (_modifierKeys & Qt::CTRL) s += tr("Ctrl", "Ctrl key") + '+'; if (_modifierKeys & Qt::ALT) s += tr("Alt", "Alt key") + '+'; if (_modifierKeys & Qt::SHIFT) s += tr("Shift", "Shift key") + '+'; #endif } else { s = tr("Input", "What the user inputs now will be taken as the new shortcut"); } // make it clear that input is still going on s.append(" ..."); } if (s.isEmpty()) { s = tr("None", "No shortcut defined"); } s.prepend(' '); s.append(' '); _keyButton->setText(s); } void KeySequenceWidget::startRecording() { _modifierKeys = 0; _oldKeySequence = _keySequence; _keySequence = QKeySequence(); _conflictingIndex = QModelIndex(); _isRecording = true; _keyButton->grabKeyboard(); if (!QWidget::keyboardGrabber()) { qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active"; } _keyButton->setDown(true); updateShortcutDisplay(); } void KeySequenceWidget::doneRecording() { bool wasRecording = _isRecording; _isRecording = false; _keyButton->releaseKeyboard(); _keyButton->setDown(false); if (!wasRecording || _keySequence == _oldKeySequence) { // The sequence hasn't changed updateShortcutDisplay(); return; } if (!isKeySequenceAvailable(_keySequence)) { _keySequence = _oldKeySequence; } else if (wasRecording) { emit keySequenceChanged(_keySequence, _conflictingIndex); } updateShortcutDisplay(); } void KeySequenceWidget::cancelRecording() { _keySequence = _oldKeySequence; doneRecording(); } void KeySequenceWidget::setKeySequence(const QKeySequence &seq) { // oldKeySequence holds the key sequence before recording started, if setKeySequence() // is called while not recording then set oldKeySequence to the existing sequence so // that the keySequenceChanged() signal is emitted if the new and previous key // sequences are different if (!isRecording()) _oldKeySequence = _keySequence; _keySequence = seq; _clearButton->setVisible(!_keySequence.isEmpty()); doneRecording(); } void KeySequenceWidget::clear() { setKeySequence(QKeySequence()); // setKeySequence() won't emit a signal when we're not recording emit keySequenceChanged(QKeySequence()); } bool KeySequenceWidget::isKeySequenceAvailable(const QKeySequence &seq) { if (seq.isEmpty()) return true; // We need to access the root model, not the filtered one for (int cat = 0; cat < _shortcutsModel->rowCount(); cat++) { QModelIndex catIdx = _shortcutsModel->index(cat, 0); for (int r = 0; r < _shortcutsModel->rowCount(catIdx); r++) { QModelIndex actIdx = _shortcutsModel->index(r, 0, catIdx); Q_ASSERT(actIdx.isValid()); if (actIdx.data(ShortcutsModel::ActiveShortcutRole).value() != seq) continue; if (!actIdx.data(ShortcutsModel::IsConfigurableRole).toBool()) { QMessageBox::warning(this, tr("Shortcut Conflict"), tr("The \"%1\" shortcut is already in use, and cannot be configured.\nPlease choose another one.").arg(seq.toString(QKeySequence::NativeText)), QMessageBox::Ok); return false; } QMessageBox box(QMessageBox::Warning, tr("Shortcut Conflict"), (tr("The \"%1\" shortcut is ambiguous with the shortcut for the following action:") + "
    • %2

    " + tr("Do you want to reassign this shortcut to the selected action?") ).arg(seq.toString(QKeySequence::NativeText), actIdx.data().toString()), QMessageBox::Cancel, this); box.addButton(tr("Reassign"), QMessageBox::AcceptRole); if (box.exec() == QMessageBox::Cancel) return false; _conflictingIndex = actIdx; return true; } } return true; } cantata-2.2.0/support/keysequencewidget.h000066400000000000000000000077661316350454000205610ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2005-2015 by the Quassel Project * * devel@quassel-irc.org * * * * This class has been inspired by KDE's KKeySequenceWidget and uses * * some code snippets of its implementation, part of kdelibs. * * The original file is * * Copyright (C) 1998 Mark Donohoe * * Copyright (C) 2001 Ellis Whitehead * * Copyright (C) 2007 Andreas Hartmetz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KEYSEQUENCEWIDGET_H #define KEYSEQUENCEWIDGET_H #include #include #include #include #include "shortcutsmodel.h" class Action; class ActionCollection; class KeySequenceButton; class QToolButton; class KeySequenceWidget : public QWidget { Q_OBJECT public: KeySequenceWidget(QWidget *parent = 0); void setModel(ShortcutsModel *model); public slots: void setKeySequence(const QKeySequence &seq); signals: /** * This signal is emitted when the current key sequence has changed by user input * \param seq The key sequence the user has chosen * \param conflicting The index of an action that needs to have its shortcut removed. The user has already been * asked to agree (if he declines, this signal won't be emitted at all). */ void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting = QModelIndex()); void clicked(); private slots: void updateShortcutDisplay(); void startRecording(); void cancelRecording(); void clear(); private: inline bool isRecording() const { return _isRecording; } void doneRecording(); bool isOkWhenModifierless(int keyQt) const; bool isShiftAsModifierAllowed(int keyQt) const; bool isKeySequenceAvailable(const QKeySequence &seq); ShortcutsModel *_shortcutsModel; bool _isRecording; QKeySequence _keySequence, _oldKeySequence; uint _modifierKeys; QModelIndex _conflictingIndex; KeySequenceButton *_keyButton; QToolButton *_clearButton; friend class KeySequenceButton; }; /*****************************************************************************/ class KeySequenceButton : public QPushButton { Q_OBJECT public: explicit KeySequenceButton(KeySequenceWidget *d, QWidget *parent = 0); protected: virtual bool event(QEvent *event); virtual void keyPressEvent(QKeyEvent *event); virtual void keyReleaseEvent(QKeyEvent *event); private: KeySequenceWidget *d; }; #endif // KEYSEQUENCEWIDGET_H cantata-2.2.0/support/kmessagewidget.cpp000066400000000000000000000304421316350454000203550ustar00rootroot00000000000000/* This file is part of the KDE libraries * * Copyright (c) 2011 Aurélien Gâteau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "kmessagewidget.h" #include "utils.h" #include "monoicon.h" #include "icon.h" #include "squeezedtextlabel.h" #include "flattoolbutton.h" #include #include #include #include #include #include #include #include #include #include //--------------------------------------------------------------------- // KMsgWidgetPrivate //--------------------------------------------------------------------- #if 0 class KMsgWidgetPrivate { public: void init(KMsgWidget*); KMsgWidget* q; QFrame* content; QLabel* iconLabel; QLabel* textLabel; QToolButton* closeButton; QTimeLine* timeLine; KMsgWidget::MessageType messageType; bool wordWrap; QList buttons; QPixmap contentSnapShot; void createLayout(); void updateSnapShot(); void updateLayout(); void slotTimeLineChanged(qreal); void slotTimeLineFinished(); }; #endif void KMsgWidgetPrivate::init(KMsgWidget *q_ptr) { q = q_ptr; q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); timeLine = new QTimeLine(500, q); QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal))); QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished())); content = new QFrame(q); content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); wordWrap = false; #ifdef Q_OS_MAC bool closeOnLeft=true; #else bool closeOnLeft=Utils::Unity==Utils::currentDe(); #endif if (closeOnLeft) { iconLabel=0; } else { iconLabel = new QLabel(content); iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } textLabel = new SqueezedTextLabel(content); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); QAction* closeAction = new QAction(q); closeAction->setIcon(MonoIcon::icon(FontAwesome::close, MonoIcon::constRed, MonoIcon::constRed)); closeAction->setToolTip(QObject::tr("Close")); QObject::connect(closeAction, SIGNAL(triggered()), q, SLOT(animatedHide())); closeButton = new FlatToolButton(content); closeButton->setDefaultAction(closeAction); #ifdef Q_OS_MAC closeButton->setStyleSheet("QToolButton {border: 0}"); #endif q->setMessageType(KMsgWidget::Information); } void KMsgWidgetPrivate::createLayout() { delete content->layout(); content->resize(q->size()); qDeleteAll(buttons); buttons.clear(); Q_FOREACH(QAction* action, q->actions()) { QToolButton* button = new QToolButton(content); button->setDefaultAction(action); if (content->style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) { button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { button->setToolButtonStyle(Qt::ToolButtonTextOnly); } buttons.append(button); } // AutoRaise reduces visual clutter, but we don't want to turn it on if // there are other buttons, otherwise the close button will look different // from the others. closeButton->setAutoRaise(!iconLabel || buttons.isEmpty()); if (wordWrap) { QGridLayout* layout = new QGridLayout(content); // Set alignment to make sure icon does not move down if text wraps if (iconLabel) { layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); } else { layout->addWidget(closeButton, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); } layout->addWidget(textLabel, 0, 1); layout->setMargin(4); QHBoxLayout* buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); Q_FOREACH(QToolButton* button, buttons) { // For some reason, calling show() is necessary if wordwrap is true, // otherwise the buttons do not show up. It is not needed if // wordwrap is false. button->show(); buttonLayout->addWidget(button); } if (iconLabel) { buttonLayout->addWidget(closeButton); } layout->addItem(buttonLayout, 1, 0, 1, 2); } else { QHBoxLayout* layout = new QHBoxLayout(content); if (iconLabel) { layout->addWidget(iconLabel); } else { layout->addWidget(closeButton); } layout->addWidget(textLabel); Q_FOREACH(QToolButton* button, buttons) { layout->addWidget(button); } if (iconLabel) { layout->addWidget(closeButton); } layout->setMargin(4); }; if (q->isVisible()) { q->setFixedHeight(content->sizeHint().height()); } q->updateGeometry(); } void KMsgWidgetPrivate::updateLayout() { if (content->layout()) { createLayout(); } } void KMsgWidgetPrivate::updateSnapShot() { // Attention: updateSnapShot calls QWidget::render(), which causes the whole // window layouts to be activated. Calling this method from resizeEvent() // can lead to infinite recursion, see: // https://bugs.kde.org/show_bug.cgi?id=311336 contentSnapShot = QPixmap(content->size()); contentSnapShot.fill(Qt::transparent); content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); } void KMsgWidgetPrivate::slotTimeLineChanged(qreal value) { q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); q->update(); } void KMsgWidgetPrivate::slotTimeLineFinished() { if (timeLine->direction() == QTimeLine::Forward) { // Show // We set the whole geometry here, because it may be wrong if a // KMessageWidget is shown right when the toplevel window is created. content->setGeometry(0, 0, q->width(), bestContentHeight()); } else { // Hide q->hide(); } } int KMsgWidgetPrivate::bestContentHeight() const { int height = content->heightForWidth(q->width()); if (height == -1) { height = content->sizeHint().height(); } return height; } //--------------------------------------------------------------------- // KMsgWidget //--------------------------------------------------------------------- KMsgWidget::KMsgWidget(QWidget* parent) : QFrame(parent) , d(new KMsgWidgetPrivate) { d->init(this); } KMsgWidget::KMsgWidget(const QString& text, QWidget* parent) : QFrame(parent) , d(new KMsgWidgetPrivate) { d->init(this); setText(text); } KMsgWidget::~KMsgWidget() { delete d; } QString KMsgWidget::text() const { return d->textLabel->text(); } void KMsgWidget::setText(const QString& text) { d->textLabel->setText(text); updateGeometry(); } KMsgWidget::MessageType KMsgWidget::messageType() const { return d->messageType; } void KMsgWidget::setMessageType(KMsgWidget::MessageType type) { d->messageType = type; QIcon icon; QColor bg0, bg1, bg2, border, fg; bool useIcon=d->iconLabel; switch (type) { case Positive: if (useIcon) { icon = QIcon::fromTheme("dialog-ok"); } bg1=QColor(0xAB, 0xC7, 0xED); fg=Qt::black; border = Qt::blue; break; case Information: if (useIcon) { icon = QIcon::fromTheme("dialog-information"); } // There is no "information" background role in KColorScheme, use the // colors of highlighted items instead bg1=QColor(0x98, 0xBC, 0xE3); fg=Qt::black; border = Qt::blue; break; case Warning: if (useIcon) { icon = QIcon::fromTheme("dialog-warning"); } bg1=QColor(0xED, 0xC6, 0x62); fg=Qt::black; border = Qt::red; break; case Error: if (useIcon) { icon = QIcon::fromTheme("dialog-error"); } bg1=QColor(0xeb, 0xbb, 0xbb); fg=Qt::black; border = Qt::red; break; } // Colors bg0 = bg1.lighter(110); bg2 = bg1.darker(110); d->content->setStyleSheet( QString(".QFrame {" "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," " stop: 0 %1," " stop: 0.1 %2," " stop: 1.0 %3);" "border: 1px solid %4;" "margin: %5px;" "}" ".QLabel { color: %6; }" ) .arg(bg0.name()) .arg(bg1.name()) .arg(bg2.name()) .arg(border.name()) // DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin .arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) -1) .arg(fg.name()) ); if (useIcon && !icon.isNull()) { // Icon const int size = Icon::stdSize(fontMetrics().height()*1.5); d->iconLabel->setPixmap(icon.pixmap(size)); } } //QSize KMsgWidget::sizeHint() const //{ // ensurePolished(); // return d->content->sizeHint(); //} //QSize KMsgWidget::minimumSizeHint() const //{ // ensurePolished(); // return d->content->minimumSizeHint(); //} bool KMsgWidget::event(QEvent* event) { if (event->type() == QEvent::Polish && !d->content->layout()) { d->createLayout(); } return QFrame::event(event); } void KMsgWidget::resizeEvent(QResizeEvent* event) { QFrame::resizeEvent(event); if (d->timeLine->state() == QTimeLine::NotRunning) { d->content->resize(width(), d->bestContentHeight()); } } int KMsgWidget::heightForWidth(int width) const { ensurePolished(); return d->content->heightForWidth(width); } void KMsgWidget::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); if (d->timeLine->state() == QTimeLine::Running) { QPainter painter(this); painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue()); painter.drawPixmap(0, 0, d->contentSnapShot); } } void KMsgWidget::showEvent(QShowEvent* event) { // Keep this method here to avoid breaking binary compatibility: // QFrame::showEvent() used to be reimplemented. QFrame::showEvent(event); } bool KMsgWidget::wordWrap() const { return d->wordWrap; } void KMsgWidget::setWordWrap(bool wordWrap) { d->wordWrap = wordWrap; d->textLabel->setWordWrap(wordWrap); d->updateLayout(); } bool KMsgWidget::isCloseButtonVisible() const { return d->closeButton->isVisible(); } void KMsgWidget::setCloseButtonVisible(bool show) { d->closeButton->setVisible(show); } void KMsgWidget::addAction(QAction* action) { QFrame::addAction(action); d->updateLayout(); } void KMsgWidget::removeAction(QAction* action) { QFrame::removeAction(action); d->updateLayout(); } void KMsgWidget::animatedShow() { if (isVisible()) { return; } QFrame::show(); setFixedHeight(0); int wantedHeight = d->bestContentHeight(); d->content->setGeometry(0, -wantedHeight, width(), wantedHeight); d->updateSnapShot(); d->timeLine->setDirection(QTimeLine::Forward); if (d->timeLine->state() == QTimeLine::NotRunning) { d->timeLine->start(); } } void KMsgWidget::animatedHide() { if (!isVisible()) { return; } d->content->move(0, -d->content->height()); d->updateSnapShot(); d->timeLine->setDirection(QTimeLine::Backward); if (d->timeLine->state() == QTimeLine::NotRunning) { d->timeLine->start(); } } cantata-2.2.0/support/kmessagewidget.h000066400000000000000000000133651316350454000200270ustar00rootroot00000000000000/* This file is part of the KDE libraries * * Copyright (c) 2011 Aurélien Gâteau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef KMESSAGEWIDGET_H #define KMESSAGEWIDGET_H #include class KMsgWidgetPrivate; class SqueezedTextLabel; /** * @short A widget to provide feedback or propose opportunistic interactions. * * KMsgWidget can be used to provide inline positive or negative * feedback, or to implement opportunistic interactions. * * As a feedback widget, KMsgWidget provides a less intrusive alternative * to "OK Only" message boxes. If you do not need the modalness of KMessageBox, * consider using KMsgWidget instead. * * Negative feedback * * The KMsgWidget can be used as a secondary indicator of failure: the * first indicator is usually the fact the action the user expected to happen * did not happen. * * Example: User fills a form, clicks "Submit". * * @li Expected feedback: form closes * @li First indicator of failure: form stays there * @li Second indicator of failure: a KMsgWidget appears on top of the * form, explaining the error condition * * When used to provide negative feedback, KMsgWidget should be placed * close to its context. In the case of a form, it should appear on top of the * form entries. * * KMsgWidget should get inserted in the existing layout. Space should not * be reserved for it, otherwise it becomes "dead space", ignored by the user. * KMsgWidget should also not appear as an overlay to prevent blocking * access to elements the user needs to interact with to fix the failure. * * Positive feedback * * KMsgWidget can be used for positive feedback but it shouldn't be * overused. It is often enough to provide feedback by simply showing the * results of an action. * * Examples of acceptable uses: * * @li Confirm success of "critical" transactions * @li Indicate completion of background tasks * * Example of inadapted uses: * * @li Indicate successful saving of a file * @li Indicate a file has been successfully removed * * Opportunistic interaction * * Opportunistic interaction is the situation where the application suggests to * the user an action he could be interested in perform, either based on an * action the user just triggered or an event which the application noticed. * * Example of acceptable uses: * * @li A browser can propose remembering a recently entered password * @li A music collection can propose ripping a CD which just got inserted * @li A chat application may notify the user a "special friend" just connected * * @author Aurélien Gâteau * @since 4.7 */ class KMsgWidget : public QFrame { Q_OBJECT Q_ENUMS(MessageType) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible) Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType) public: enum MessageType { Positive, Information, Warning, Error }; /** * Constructs a KMsgWidget with the specified parent. */ explicit KMsgWidget(QWidget *parent = 0); explicit KMsgWidget(const QString &text, QWidget *parent = 0); ~KMsgWidget(); QString text() const; bool wordWrap() const; bool isCloseButtonVisible() const; MessageType messageType() const; void addAction(QAction *action); void removeAction(QAction *action); // QSize sizeHint() const; // QSize minimumSizeHint() const; int heightForWidth(int width) const; public Q_SLOTS: void setText(const QString &text); void setWordWrap(bool wordWrap); void setCloseButtonVisible(bool visible); void setMessageType(KMsgWidget::MessageType type); /** * Show the widget using an animation, unless * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. */ void animatedShow(); /** * Hide the widget using an animation, unless * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. */ void animatedHide(); protected: void paintEvent(QPaintEvent *event); bool event(QEvent *event); void resizeEvent(QResizeEvent *event); void showEvent(QShowEvent *event); private: KMsgWidgetPrivate *const d; friend class KMsgWidgetPrivate; Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) }; class QTimeLine; class QLabel; class QToolButton; class KMsgWidgetPrivate { public: void init(KMsgWidget*); KMsgWidget* q; QFrame* content; QLabel* iconLabel; SqueezedTextLabel* textLabel; QToolButton* closeButton; QTimeLine* timeLine; KMsgWidget::MessageType messageType; bool wordWrap; QList buttons; QPixmap contentSnapShot; void createLayout(); void updateSnapShot(); void updateLayout(); void slotTimeLineChanged(qreal); void slotTimeLineFinished(); int bestContentHeight() const; }; #endif /* KMESSAGEWIDGET_H */ cantata-2.2.0/support/lineedit.cpp000066400000000000000000000025631316350454000171520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "lineedit.h" #include void LineEdit::setReadOnly(bool e) { QLineEdit::setReadOnly(e); if (e) { QPalette p(palette()); p.setColor(QPalette::Active, QPalette::Base, p.color(QPalette::Active, QPalette::Window)); p.setColor(QPalette::Disabled, QPalette::Base, p.color(QPalette::Disabled, QPalette::Window)); p.setColor(QPalette::Inactive, QPalette::Base, p.color(QPalette::Inactive, QPalette::Window)); setPalette(p); } else { setPalette(qApp->palette()); } } cantata-2.2.0/support/lineedit.h000066400000000000000000000021121316350454000166050ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LINEEDIT_H #define LINEEDIT_H #include class LineEdit : public QLineEdit { public: LineEdit(QWidget *parent = 0) : QLineEdit(parent) { setClearButtonEnabled(true); } void setReadOnly(bool e); }; #endif // LIENEDIT_H cantata-2.2.0/support/messagebox.cpp000066400000000000000000000150151316350454000175060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "messagebox.h" #include "icon.h" #include "dialog.h" #include "config.h" #include "gtkstyle.h" #include "acceleratormanager.h" #include #include #include #include #include MessageBox::ButtonCode map(QMessageBox::StandardButton c) { switch (c) { case QMessageBox::Yes: return MessageBox::Yes; case QMessageBox::No: return MessageBox::No; default: case QMessageBox::Cancel: return MessageBox::Cancel; } } #ifdef Q_OS_MAC static void splitMessage(const QString &orig, QString &msg, QString &sub) { static QStringList constSeps=QStringList() << QLatin1String("\n\n") << QLatin1String("

    "); msg=orig; foreach (const QString &sep, constSeps) { QStringList parts=orig.split(sep); if (parts.count()>1) { msg=parts.takeAt(0); sub=parts.join(sep); return; } } } void MessageBox::error(QWidget *parent, const QString &message, const QString &title) { QString msg; QString sub; splitMessage(message, msg, sub); QMessageBox box(QMessageBox::Critical, title.isEmpty() ? QObject::tr("Error") : title, msg, QMessageBox::Ok, parent, Qt::Sheet); box.setInformativeText(sub); //AcceleratorManager::manage(&box); box.exec(); } void MessageBox::information(QWidget *parent, const QString &message, const QString &title) { QString msg; QString sub; splitMessage(message, msg, sub); QMessageBox box(QMessageBox::Information, title.isEmpty() ? QObject::tr("Information") : title, msg, QMessageBox::Ok, parent, Qt::Sheet); box.setInformativeText(sub); //AcceleratorManager::manage(&box); box.exec(); } #endif MessageBox::ButtonCode MessageBox::questionYesNoCancel(QWidget *parent, const QString &message, const QString &title, const GuiItem &yesText, const GuiItem &noText, bool showCancel, bool isWarning) { #ifdef Q_OS_MAC QString msg; QString sub; splitMessage(message, msg, sub); QMessageBox box(isWarning ? QMessageBox::Warning : QMessageBox::Question, title.isEmpty() ? (isWarning ? QObject::tr("Warning") : QObject::tr("Question")) : title, msg, QMessageBox::Yes|QMessageBox::No|(showCancel ? QMessageBox::Cancel : QMessageBox::NoButton), parent, Qt::Sheet); box.setInformativeText(sub); #else QMessageBox box(isWarning ? QMessageBox::Warning : QMessageBox::Question, title.isEmpty() ? (isWarning ? QObject::tr("Warning") : QObject::tr("Question")) : title, message, QMessageBox::Yes|QMessageBox::No|(showCancel ? QMessageBox::Cancel : QMessageBox::NoButton), parent); #endif bool btnIcons=box.style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons); box.setDefaultButton(isWarning && !showCancel ? QMessageBox::No : QMessageBox::Yes); if (!yesText.text.isEmpty()) { QAbstractButton *btn=box.button(QMessageBox::Yes); btn->setText(yesText.text); btn->setIcon(!yesText.icon.isEmpty() && btnIcons ? Icon(yesText.icon) : Icon()); } if (!noText.text.isEmpty()) { QAbstractButton *btn=box.button(QMessageBox::No); btn->setText(noText.text); btn->setIcon(!noText.icon.isEmpty() && btnIcons ? Icon(noText.icon) : Icon()); } AcceleratorManager::manage(&box); return -1==box.exec() ? Cancel : map(box.standardButton(box.clickedButton())); } namespace MessageBox { class YesNoListDialog : public Dialog { public: YesNoListDialog(QWidget *p) : Dialog(p) { } void slotButtonClicked(int b) { switch(b) { case Dialog::Ok: case Dialog::Yes: accept(); break; case Dialog::No: reject(); break; } } }; } MessageBox::ButtonCode MessageBox::msgListEx(QWidget *parent, Type type, const QString &message, const QStringList &strlist, const QString &title) { MessageBox::YesNoListDialog *dlg=new MessageBox::YesNoListDialog(parent); dlg->setAttribute(Qt::WA_DeleteOnClose); QWidget *wid=new QWidget(dlg); QGridLayout *lay=new QGridLayout(wid); QLabel *iconLabel=new QLabel(wid); int iconSize=Icon::dlgIconSize(); iconLabel->setFixedSize(iconSize, iconSize); switch(type) { case Error: dlg->setCaption(title.isEmpty() ? QObject::tr("Error") : title); dlg->setButtons(Dialog::Ok); iconLabel->setPixmap(Icon("dialog-error").pixmap(iconSize, iconSize)); break; case Question: dlg->setCaption(title.isEmpty() ? QObject::tr("Question") : title); dlg->setButtons(Dialog::Yes|Dialog::No); iconLabel->setPixmap(Icon("dialog-question").pixmap(iconSize, iconSize)); break; case Warning: dlg->setCaption(title.isEmpty() ? QObject::tr("Warning") : title); dlg->setButtons(Dialog::Yes|Dialog::No); iconLabel->setPixmap(Icon("dialog-warning").pixmap(iconSize, iconSize)); break; case Information: dlg->setCaption(title.isEmpty() ? QObject::tr("Information") : title); dlg->setButtons(Dialog::Ok); iconLabel->setPixmap(Icon("dialog-information").pixmap(iconSize, iconSize)); break; } lay->addWidget(iconLabel, 0, 0, 1, 1); QLabel *msgLabel=new QLabel(message, wid); msgLabel->setWordWrap(true); msgLabel->setTextInteractionFlags(Qt::NoTextInteraction); lay->addWidget(msgLabel, 0, 1, 1, 1); QListWidget *list=new QListWidget(wid); lay->addWidget(list, 1, 0, 1, 2); lay->setMargin(0); list->insertItems(0, strlist); dlg->setMainWidget(wid); #ifdef Q_OS_MAC dlg->setWindowFlags((dlg->windowFlags()&(~Qt::WindowType_Mask))|Qt::Sheet); #endif return QDialog::Accepted==dlg->exec() ? Yes : No; } cantata-2.2.0/support/messagebox.h000066400000000000000000000103031316350454000171460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MESSAGEBOX_H #define MESSAGEBOX_H #include "dialog.h" #include namespace MessageBox { enum ButtonCode { Yes=QMessageBox::Yes, No=QMessageBox::No, Cancel=QMessageBox::Cancel }; enum Type { Error = QMessageBox::Critical, Question = QMessageBox::Question, Warning = QMessageBox::Warning, Information = QMessageBox::Information }; extern ButtonCode questionYesNoCancel(QWidget *parent, const QString &message, const QString &title=QString(), const GuiItem &yesText=StdGuiItem::yes(), const GuiItem &noText=StdGuiItem::no(), bool showCancel=true, bool isWarning=false); inline ButtonCode questionYesNo(QWidget *parent, const QString &message, const QString &title=QString(), const GuiItem &yesText=StdGuiItem::yes(), const GuiItem &noText=StdGuiItem::no()) { return questionYesNoCancel(parent, message, title, yesText, noText, false); } inline ButtonCode warningYesNoCancel(QWidget *parent, const QString &message, const QString &title=QString(), const GuiItem &yesText=StdGuiItem::yes(), const GuiItem &noText=StdGuiItem::no()) { return questionYesNoCancel(parent, message, title, yesText, noText, true, true); } inline ButtonCode warningYesNo(QWidget *parent, const QString &message, const QString &title=QString(), const GuiItem &yesText=StdGuiItem::yes(), const GuiItem &noText=StdGuiItem::no()) { return questionYesNoCancel(parent, message, title, yesText, noText, false, true); } #ifdef Q_OS_MAC extern void error(QWidget *parent, const QString &message, const QString &title=QString()); extern void information(QWidget *parent, const QString &message, const QString &title=QString()); #else inline void error(QWidget *parent, const QString &message, const QString &title=QString()) { QMessageBox::critical(parent, title.isEmpty() ? QObject::tr("Error") : title, message); } inline void information(QWidget *parent, const QString &message, const QString &title=QString()) { QMessageBox::information(parent, title.isEmpty() ? QObject::tr("Information") : title, message); } #endif extern ButtonCode msgListEx(QWidget *parent, Type type, const QString &message, const QStringList &strlist, const QString &title=QString()); inline void errorListEx(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title=QString()) { msgListEx(parent, Error, message, strlist, title); } inline void errorList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title=QString()) { msgListEx(parent, Error, message, strlist, title); } inline ButtonCode questionYesNoList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title=QString()) { return msgListEx(parent, Question, message, strlist, title); } inline ButtonCode warningYesNoList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title=QString()) { return msgListEx(parent, Warning, message, strlist, title); } inline void informationList(QWidget *parent, const QString &message, const QStringList &strlist, const QString &title=QString()) { msgListEx(parent, Information, message, strlist, title); } } #endif cantata-2.2.0/support/messagewidget.cpp000066400000000000000000000045031316350454000202010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "messagewidget.h" MessageWidget::MessageWidget(QWidget *parent) : KMsgWidget(parent) , active(false) , msgType(Positive) { } MessageWidget::~MessageWidget() { } void MessageWidget::setMessage(const QString &msg, MessageType type, bool showCloseButton) { if (isActive() && !msg.isEmpty() && type!=msgType) { setVisible(false); } msgType=type; if (msg.isEmpty() && isVisible()) { setVisible(false); return; } // QString text=msg; // if (text.length()>154) { // text=text.left(150)+QLatin1String("..."); // } // if (msg.length()>500) { // setToolTip(msg.left(500)+QLatin1String("...")); // } else { // setToolTip(msg); // } // setText(text); setText(msg); setToolTip(msg); setMessageType(type); setCloseButtonVisible(showCloseButton); #if defined NO_ANIMATED_SHOW setVisible(true); #else if (!parentWidget()->isVisible()) { show(); setVisible(true); } else { animatedShow(); } #endif } void MessageWidget::setVisible(bool v) { active=v; KMsgWidget::setVisible(v); emit visible(v); } void MessageWidget::removeAllActions() { QList acts=actions(); foreach (QAction *a, acts) { removeAction(a); } } void MessageWidget::setActions(const QList acts) { if (acts==actions()) { return; } removeAllActions(); foreach (QAction *a, acts) { addAction(a); } } cantata-2.2.0/support/messagewidget.h000066400000000000000000000034361316350454000176520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MESSAGEWIDGET_H #define MESSAGEWIDGET_H #include "kmessagewidget.h" #include class MessageWidget : public KMsgWidget { Q_OBJECT public: MessageWidget(QWidget *parent); virtual ~MessageWidget(); void setError(const QString &msg, bool showCloseButton=true) { setMessage(msg, Error, showCloseButton); } void setInformation(const QString &msg, bool showCloseButton=true) { setMessage(msg, Information, showCloseButton); } void setWarning(const QString &msg, bool showCloseButton=true) { setMessage(msg, Warning, showCloseButton); } void setVisible(bool v); bool isActive() const { return active; } void removeAllActions(); void setActions(const QList acts); bool showingError() const { return Error==msgType; } Q_SIGNALS: void visible(bool); private: void setMessage(const QString &msg, MessageType type, bool showCloseButton); private: bool active; MessageType msgType; }; #endif cantata-2.2.0/support/monoicon.cpp000066400000000000000000000147751316350454000172060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "monoicon.h" #include "utils.h" #ifdef Q_OS_MAC #include "osxstyle.h" #endif #include #include #include #include #include #include #include #include #include class MonoIconEngine : public QIconEngine { public: MonoIconEngine(const QString &file, FontAwesome::icon fa, const QColor &col, const QColor &sel) : fileName(file) , fontAwesomeIcon(fa) , color(col) , selectedColor(sel) { } virtual ~MonoIconEngine() {} MonoIconEngine * clone() const { return new MonoIconEngine(fileName, fontAwesomeIcon, color, selectedColor); } virtual void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) { Q_UNUSED(state) QColor col=QIcon::Selected==mode ? selectedColor : color; if (QIcon::Selected==mode && !col.isValid()) { #ifdef Q_OS_MAC col=OSXStyle::self()->viewPalette().highlightedText().color(); #else col=QApplication::palette().highlightedText().color(); #endif } QString key=(fileName.isEmpty() ? QString::number(fontAwesomeIcon) : fileName)+ QLatin1Char('-')+QString::number(rect.width())+QLatin1Char('-')+QString::number(rect.height())+QLatin1Char('-')+col.name(); QPixmap pix; if (!QPixmapCache::find(key, &pix)) { pix=QPixmap(rect.width(), rect.height()); pix.fill(Qt::transparent); QPainter p(&pix); if (fileName.isEmpty()) { QString fontName; if (FontAwesome::ex_one==fontAwesomeIcon) { fontName="serif"; } else { // Load fontawesome, if it is not already loaded if (fontAwesomeFontName.isEmpty()) { QStringList loadedFontFamilies = QFontDatabase::applicationFontFamilies(QFontDatabase::addApplicationFont(Utils::systemDir(QLatin1String("fonts"))+QLatin1String("fontawesome-webfont.ttf"))); if (!loadedFontFamilies.empty()) { fontAwesomeFontName= loadedFontFamilies.at(0); } } fontName=fontAwesomeFontName; } QFont font(fontName); int pixelSize=rect.height(); if (FontAwesome::ex_one==fontAwesomeIcon) { font.setBold(true); } else if (pixelSize>10) { static const int constScale=14; static const int constHalfScale=constScale/2; pixelSize=((pixelSize/constScale)*constScale)+((pixelSize%constScale)>=constHalfScale ? constScale : 0); pixelSize=qMin(pixelSize, rect.height()); if (FontAwesome::bars==fontAwesomeIcon && pixelSize%constHalfScale) { pixelSize-=1; } else if (FontAwesome::list==fontAwesomeIcon && pixelSize%constHalfScale) { pixelSize+=1; } } font.setPixelSize(pixelSize); font.setStyleStrategy(QFont::PreferAntialias); font.setHintingPreference(QFont::PreferFullHinting); p.setFont(font); p.setPen(col); p.setRenderHint(QPainter::Antialiasing, true); if (FontAwesome::ex_one==fontAwesomeIcon) { QString str=QString::number(fontAwesomeIcon); p.drawText(QRect(0, 0, rect.width(), rect.height()), str, QTextOption(Qt::AlignHCenter|Qt::AlignVCenter)); p.drawText(QRect(1, 0, rect.width(), rect.height()), str, QTextOption(Qt::AlignHCenter|Qt::AlignVCenter)); } else { p.drawText(QRect(0, 0, rect.width(), rect.height()), QString(QChar(static_cast(fontAwesomeIcon))), QTextOption(Qt::AlignCenter|Qt::AlignVCenter)); } } else { QSvgRenderer renderer; QFile f(fileName); QByteArray bytes; if (f.open(QIODevice::ReadOnly)) { bytes=f.readAll(); } if (!bytes.isEmpty()) { bytes.replace("#000", col.name().toLatin1()); } renderer.load(bytes); renderer.render(&p, QRect(0, 0, rect.width(), rect.height())); } QPixmapCache::insert(key, pix); } if (QIcon::Disabled==mode) { painter->save(); painter->setOpacity(painter->opacity()*0.35); } painter->drawPixmap(rect.topLeft(), pix); if (QIcon::Disabled==mode) { painter->restore(); } } virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { QPixmap pix(size); pix.fill(Qt::transparent); QPainter painter(&pix); paint(&painter, QRect(QPoint(0, 0), size), mode, state); return pix; } private: QString fileName; FontAwesome::icon fontAwesomeIcon; QColor color; QColor selectedColor; static QString fontAwesomeFontName; }; QString MonoIconEngine::fontAwesomeFontName; const QColor MonoIcon::constRed(196, 32, 32); QIcon MonoIcon::icon(const QString &fileName, const QColor &col, const QColor &sel) { return QIcon(new MonoIconEngine(fileName, (FontAwesome::icon)0, col, sel)); } QIcon MonoIcon::icon(const FontAwesome::icon icon, const QColor &col, const QColor &sel) { return QIcon(new MonoIconEngine(QString(), icon, col, sel)); } cantata-2.2.0/support/monoicon.h000066400000000000000000000725371316350454000166530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MONO_ICON_H #define MONO_ICON_H #include /** * This enum is taken from QtAwesome: * * MIT Licensed * * Copyright 2013-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved. * Author Rick Blommers */ namespace FontAwesome { enum icon { ex_one = 0x0001, adjust = 0xf042, adn = 0xf170, aligncenter = 0xf037, alignjustify = 0xf039, alignleft = 0xf036, alignright = 0xf038, ambulance = 0xf0f9, anchor = 0xf13d, android = 0xf17b, angellist = 0xf209, angledoubledown = 0xf103, angledoubleleft = 0xf100, angledoubleright = 0xf101, angledoubleup = 0xf102, angledown = 0xf107, angleleft = 0xf104, angleright = 0xf105, angleup = 0xf106, apple = 0xf179, archive = 0xf187, areachart = 0xf1fe, arrowcircledown = 0xf0ab, arrowcircleleft = 0xf0a8, arrowcircleodown = 0xf01a, arrowcircleoleft = 0xf190, arrowcircleoright = 0xf18e, arrowcircleoup = 0xf01b, arrowcircleright = 0xf0a9, arrowcircleup = 0xf0aa, arrowdown = 0xf063, arrowleft = 0xf060, arrowright = 0xf061, arrowup = 0xf062, arrows = 0xf047, arrowsalt = 0xf0b2, arrowsh = 0xf07e, arrowsv = 0xf07d, asterisk = 0xf069, at = 0xf1fa, automobile = 0xf1b9, backward = 0xf04a, ban = 0xf05e, bank = 0xf19c, barchart = 0xf080, barcharto = 0xf080, barcode = 0xf02a, bars = 0xf0c9, bed = 0xf236, beer = 0xf0fc, behance = 0xf1b4, behancesquare = 0xf1b5, bell = 0xf0f3, bello = 0xf0a2, bellslash = 0xf1f6, bellslasho = 0xf1f7, bicycle = 0xf206, binoculars = 0xf1e5, birthdaycake = 0xf1fd, bitbucket = 0xf171, bitbucketsquare = 0xf172, bitcoin = 0xf15a, bold = 0xf032, bolt = 0xf0e7, bomb = 0xf1e2, book = 0xf02d, bookmark = 0xf02e, bookmarko = 0xf097, briefcase = 0xf0b1, btc = 0xf15a, bug = 0xf188, building = 0xf1ad, buildingo = 0xf0f7, bullhorn = 0xf0a1, bullseye = 0xf140, bus = 0xf207, buysellads = 0xf20d, cab = 0xf1ba, calculator = 0xf1ec, calendar = 0xf073, calendaro = 0xf133, camera = 0xf030, cameraretro = 0xf083, car = 0xf1b9, caretdown = 0xf0d7, caretleft = 0xf0d9, caretright = 0xf0da, caretsquareodown = 0xf150, caretsquareoleft = 0xf191, caretsquareoright = 0xf152, caretsquareoup = 0xf151, caretup = 0xf0d8, cartarrowdown = 0xf218, cartplus = 0xf217, cc = 0xf20a, ccamex = 0xf1f3, ccdiscover = 0xf1f2, ccmastercard = 0xf1f1, ccpaypal = 0xf1f4, ccstripe = 0xf1f5, ccvisa = 0xf1f0, certificate = 0xf0a3, chain = 0xf0c1, chainbroken = 0xf127, check = 0xf00c, checkcircle = 0xf058, checkcircleo = 0xf05d, checksquare = 0xf14a, checksquareo = 0xf046, chevroncircledown = 0xf13a, chevroncircleleft = 0xf137, chevroncircleright = 0xf138, chevroncircleup = 0xf139, chevrondown = 0xf078, chevronleft = 0xf053, chevronright = 0xf054, chevronup = 0xf077, child = 0xf1ae, circle = 0xf111, circleo = 0xf10c, circleonotch = 0xf1ce, circlethin = 0xf1db, clipboard = 0xf0ea, clocko = 0xf017, close = 0xf00d, cloud = 0xf0c2, clouddownload = 0xf0ed, cloudupload = 0xf0ee, cny = 0xf157, code = 0xf121, codefork = 0xf126, codepen = 0xf1cb, coffee = 0xf0f4, cog = 0xf013, cogs = 0xf085, columns = 0xf0db, comment = 0xf075, commento = 0xf0e5, comments = 0xf086, commentso = 0xf0e6, compass = 0xf14e, compress = 0xf066, connectdevelop = 0xf20e, copy = 0xf0c5, copyright = 0xf1f9, creditcard = 0xf09d, crop = 0xf125, crosshairs = 0xf05b, css3 = 0xf13c, cube = 0xf1b2, cubes = 0xf1b3, cut = 0xf0c4, cutlery = 0xf0f5, dashboard = 0xf0e4, dashcube = 0xf210, database = 0xf1c0, dedent = 0xf03b, delicious = 0xf1a5, desktop = 0xf108, deviantart = 0xf1bd, diamond = 0xf219, digg = 0xf1a6, dollar = 0xf155, dotcircleo = 0xf192, download = 0xf019, dribbble = 0xf17d, dropbox = 0xf16b, drupal = 0xf1a9, edit = 0xf044, eject = 0xf052, ellipsish = 0xf141, ellipsisv = 0xf142, empire = 0xf1d1, envelope = 0xf0e0, envelopeo = 0xf003, envelopesquare = 0xf199, eraser = 0xf12d, eur = 0xf153, euro = 0xf153, exchange = 0xf0ec, exclamation = 0xf12a, exclamationcircle = 0xf06a, exclamationtriangle = 0xf071, expand = 0xf065, externallink = 0xf08e, externallinksquare = 0xf14c, eye = 0xf06e, eyeslash = 0xf070, eyedropper = 0xf1fb, facebook = 0xf09a, facebookf = 0xf09a, facebookofficial = 0xf230, facebooksquare = 0xf082, fastbackward = 0xf049, fastforward = 0xf050, fax = 0xf1ac, female = 0xf182, fighterjet = 0xf0fb, file = 0xf15b, filearchiveo = 0xf1c6, fileaudioo = 0xf1c7, filecodeo = 0xf1c9, fileexcelo = 0xf1c3, fileimageo = 0xf1c5, filemovieo = 0xf1c8, fileo = 0xf016, filepdfo = 0xf1c1, filephotoo = 0xf1c5, filepictureo = 0xf1c5, filepowerpointo = 0xf1c4, filesoundo = 0xf1c7, filetext = 0xf15c, filetexto = 0xf0f6, filevideoo = 0xf1c8, filewordo = 0xf1c2, filezipo = 0xf1c6, fileso = 0xf0c5, film = 0xf008, filter = 0xf0b0, fire = 0xf06d, fireextinguisher = 0xf134, flag = 0xf024, flagcheckered = 0xf11e, flago = 0xf11d, flash = 0xf0e7, flask = 0xf0c3, flickr = 0xf16e, floppyo = 0xf0c7, folder = 0xf07b, foldero = 0xf114, folderopen = 0xf07c, folderopeno = 0xf115, font = 0xf031, forumbee = 0xf211, forward = 0xf04e, foursquare = 0xf180, frowno = 0xf119, futbolo = 0xf1e3, gamepad = 0xf11b, gavel = 0xf0e3, gbp = 0xf154, ge = 0xf1d1, gear = 0xf013, gears = 0xf085, genderless = 0xf1db, gift = 0xf06b, git = 0xf1d3, gitsquare = 0xf1d2, github = 0xf09b, githubalt = 0xf113, githubsquare = 0xf092, gittip = 0xf184, glass = 0xf000, globe = 0xf0ac, google = 0xf1a0, googleplus = 0xf0d5, googleplussquare = 0xf0d4, googlewallet = 0xf1ee, graduationcap = 0xf19d, gratipay = 0xf184, group = 0xf0c0, hsquare = 0xf0fd, hackernews = 0xf1d4, handodown = 0xf0a7, handoleft = 0xf0a5, handoright = 0xf0a4, handoup = 0xf0a6, hddo = 0xf0a0, header = 0xf1dc, headphones = 0xf025, heart = 0xf004, hearto = 0xf08a, heartbeat = 0xf21e, history = 0xf1da, home = 0xf015, hospitalo = 0xf0f8, hotel = 0xf236, html5 = 0xf13b, ils = 0xf20b, image = 0xf03e, inbox = 0xf01c, indent = 0xf03c, info = 0xf129, infocircle = 0xf05a, inr = 0xf156, instagram = 0xf16d, institution = 0xf19c, ioxhost = 0xf208, italic = 0xf033, joomla = 0xf1aa, jpy = 0xf157, jsfiddle = 0xf1cc, key = 0xf084, keyboardo = 0xf11c, krw = 0xf159, language = 0xf1ab, laptop = 0xf109, lastfm = 0xf202, lastfmsquare = 0xf203, leaf = 0xf06c, leanpub = 0xf212, legal = 0xf0e3, lemono = 0xf094, leveldown = 0xf149, levelup = 0xf148, lifebouy = 0xf1cd, lifebuoy = 0xf1cd, lifering = 0xf1cd, lifesaver = 0xf1cd, lightbulbo = 0xf0eb, linechart = 0xf201, link = 0xf0c1, linkedin = 0xf0e1, linkedinsquare = 0xf08c, linux_os = 0xf17c, list = 0xf03a, listalt = 0xf022, listol = 0xf0cb, listul = 0xf0ca, locationarrow = 0xf124, lock = 0xf023, longarrowdown = 0xf175, longarrowleft = 0xf177, longarrowright = 0xf178, longarrowup = 0xf176, magic = 0xf0d0, magnet = 0xf076, mailforward = 0xf064, mailreply = 0xf112, mailreplyall = 0xf122, male = 0xf183, mapmarker = 0xf041, mars = 0xf222, marsdouble = 0xf227, marsstroke = 0xf229, marsstrokeh = 0xf22b, marsstrokev = 0xf22a, maxcdn = 0xf136, meanpath = 0xf20c, medium = 0xf23a, medkit = 0xf0fa, meho = 0xf11a, mercury = 0xf223, microphone = 0xf130, microphoneslash = 0xf131, minus = 0xf068, minuscircle = 0xf056, minussquare = 0xf146, minussquareo = 0xf147, mobile = 0xf10b, mobilephone = 0xf10b, money = 0xf0d6, moono = 0xf186, mortarboard = 0xf19d, motorcycle = 0xf21c, music = 0xf001, navicon = 0xf0c9, neuter = 0xf22c, newspapero = 0xf1ea, openid = 0xf19b, outdent = 0xf03b, pagelines = 0xf18c, paintbrush = 0xf1fc, paperplane = 0xf1d8, paperplaneo = 0xf1d9, paperclip = 0xf0c6, paragraph = 0xf1dd, paste = 0xf0ea, pause = 0xf04c, paw = 0xf1b0, paypal = 0xf1ed, pencil = 0xf040, pencilsquare = 0xf14b, pencilsquareo = 0xf044, phone = 0xf095, phonesquare = 0xf098, photo = 0xf03e, pictureo = 0xf03e, piechart = 0xf200, piedpiper = 0xf1a7, piedpiperalt = 0xf1a8, pinterest = 0xf0d2, pinterestp = 0xf231, pinterestsquare = 0xf0d3, plane = 0xf072, play = 0xf04b, playcircle = 0xf144, playcircleo = 0xf01d, plug = 0xf1e6, plus = 0xf067, pluscircle = 0xf055, plussquare = 0xf0fe, plussquareo = 0xf196, poweroff = 0xf011, print = 0xf02f, puzzlepiece = 0xf12e, qq = 0xf1d6, qrcode = 0xf029, question = 0xf128, questioncircle = 0xf059, quoteleft = 0xf10d, quoteright = 0xf10e, ra = 0xf1d0, random = 0xf074, rebel = 0xf1d0, recycle = 0xf1b8, reddit = 0xf1a1, redditsquare = 0xf1a2, refresh = 0xf021, remove = 0xf00d, renren = 0xf18b, reorder = 0xf0c9, repeat = 0xf01e, reply = 0xf112, replyall = 0xf122, retweet = 0xf079, rmb = 0xf157, road = 0xf018, rocket = 0xf135, rotateleft = 0xf0e2, rotateright = 0xf01e, rouble = 0xf158, rss = 0xf09e, rsssquare = 0xf143, rub = 0xf158, ruble = 0xf158, rupee = 0xf156, save = 0xf0c7, scissors = 0xf0c4, search = 0xf002, searchminus = 0xf010, searchplus = 0xf00e, sellsy = 0xf213, send = 0xf1d8, sendo = 0xf1d9, server = 0xf233, share = 0xf064, sharealt = 0xf1e0, sharealtsquare = 0xf1e1, sharesquare = 0xf14d, sharesquareo = 0xf045, shekel = 0xf20b, sheqel = 0xf20b, shield = 0xf132, ship = 0xf21a, shirtsinbulk = 0xf214, shoppingcart = 0xf07a, signin = 0xf090, signout = 0xf08b, signal = 0xf012, simplybuilt = 0xf215, sitemap = 0xf0e8, skyatlas = 0xf216, skype = 0xf17e, slack = 0xf198, sliders = 0xf1de, slideshare = 0xf1e7, smileo = 0xf118, soccerballo = 0xf1e3, sort = 0xf0dc, sortalphaasc = 0xf15d, sortalphadesc = 0xf15e, sortamountasc = 0xf160, sortamountdesc = 0xf161, sortasc = 0xf0de, sortdesc = 0xf0dd, sortdown = 0xf0dd, sortnumericasc = 0xf162, sortnumericdesc = 0xf163, sortup = 0xf0de, soundcloud = 0xf1be, spaceshuttle = 0xf197, spinner = 0xf110, spoon = 0xf1b1, spotify = 0xf1bc, square = 0xf0c8, squareo = 0xf096, stackexchange = 0xf18d, stackoverflow = 0xf16c, star = 0xf005, starhalf = 0xf089, starhalfempty = 0xf123, starhalffull = 0xf123, starhalfo = 0xf123, staro = 0xf006, steam = 0xf1b6, steamsquare = 0xf1b7, stepbackward = 0xf048, stepforward = 0xf051, stethoscope = 0xf0f1, stop = 0xf04d, streetview = 0xf21d, strikethrough = 0xf0cc, stumbleupon = 0xf1a4, stumbleuponcircle = 0xf1a3, subscript = 0xf12c, subway = 0xf239, suitcase = 0xf0f2, suno = 0xf185, superscript = 0xf12b, support = 0xf1cd, table = 0xf0ce, tablet = 0xf10a, tachometer = 0xf0e4, tag = 0xf02b, tags = 0xf02c, tasks = 0xf0ae, taxi = 0xf1ba, tencentweibo = 0xf1d5, terminal = 0xf120, textheight = 0xf034, textwidth = 0xf035, th = 0xf00a, thlarge = 0xf009, thlist = 0xf00b, thumbtack = 0xf08d, thumbsdown = 0xf165, thumbsodown = 0xf088, thumbsoup = 0xf087, thumbsup = 0xf164, ticket = 0xf145, times = 0xf00d, timescircle = 0xf057, timescircleo = 0xf05c, tint = 0xf043, toggledown = 0xf150, toggleleft = 0xf191, toggleoff = 0xf204, toggleon = 0xf205, toggleright = 0xf152, toggleup = 0xf151, train = 0xf238, transgender = 0xf224, transgenderalt = 0xf225, trash = 0xf1f8, trasho = 0xf014, tree = 0xf1bb, trello = 0xf181, trophy = 0xf091, truck = 0xf0d1, fa_try = 0xf195, // add prefix fa_ (try is a keyword) tty = 0xf1e4, tumblr = 0xf173, tumblrsquare = 0xf174, turkishlira = 0xf195, twitch = 0xf1e8, twitter = 0xf099, twittersquare = 0xf081, umbrella = 0xf0e9, underline = 0xf0cd, undo = 0xf0e2, university = 0xf19c, unlink = 0xf127, unlock = 0xf09c, unlockalt = 0xf13e, unsorted = 0xf0dc, upload = 0xf093, usd = 0xf155, user = 0xf007, usermd = 0xf0f0, userplus = 0xf234, usersecret = 0xf21b, usertimes = 0xf235, users = 0xf0c0, venus = 0xf221, venusdouble = 0xf226, venusmars = 0xf228, viacoin = 0xf237, videocamera = 0xf03d, vimeosquare = 0xf194, vine = 0xf1ca, vk = 0xf189, volumedown = 0xf027, volumeoff = 0xf026, volumeup = 0xf028, warning = 0xf071, wechat = 0xf1d7, weibo = 0xf18a, weixin = 0xf1d7, whatsapp = 0xf232, wheelchair = 0xf193, wifi = 0xf1eb, windows = 0xf17a, won = 0xf159, wordpress = 0xf19a, wrench = 0xf0ad, xing = 0xf168, xingsquare = 0xf169, yahoo = 0xf19e, yelp = 0xf1e9, yen = 0xf157, youtube = 0xf167, youtubeplay = 0xf16a, youtubesquare = 0xf166, podcast = 0xf2ce }; } namespace MonoIcon { extern const QColor constRed; extern QIcon icon(const QString &fileName, const QColor &col, const QColor &sel=QColor(QColor::Invalid)); extern QIcon icon(const FontAwesome::icon icon, const QColor &col, const QColor &sel=QColor(QColor::Invalid)); }; #endif // MonoIcon_H cantata-2.2.0/support/osxstyle.cpp000066400000000000000000000134321316350454000172440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "osxstyle.h" #include "globalstatic.h" #include "actioncollection.h" #include "action.h" #include "utils.h" #include #include #include #include #include #include #include #include #include GLOBAL_STATIC(OSXStyle, instance) OSXStyle::OSXStyle() : view(0) , windowMenu(0) , closeAct(0) , minAct(0) , zoomAct(0) { } const QPalette & OSXStyle::viewPalette() { return viewWidget()->palette(); } void OSXStyle::drawSelection(QStyleOptionViewItem opt, QPainter *painter, double opacity) { opt.palette=viewPalette(); if (opacity<0.999) { QColor col(opt.palette.highlight().color()); col.setAlphaF(opacity); opt.palette.setColor(opt.palette.currentColorGroup(), QPalette::Highlight, col); } QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, viewWidget()); } QColor OSXStyle::monoIconColor() { return QColor(96, 96, 96); } void OSXStyle::initWindowMenu(QMainWindow *mw) { if (!windowMenu && mw) { windowMenu=new QMenu(tr("&Window"), mw); closeAct=ActionCollection::get()->createAction("close-window", tr("Close")); minAct=ActionCollection::get()->createAction("minimize-window", tr("Minimize")); zoomAct=ActionCollection::get()->createAction("zoom-window", tr("Zoom")); windowMenu->addAction(closeAct); windowMenu->addAction(minAct); windowMenu->addAction(zoomAct); windowMenu->addSeparator(); addWindow(mw); mw->menuBar()->addMenu(windowMenu); actions[mw]->setChecked(true); connect(qApp, SIGNAL(focusWindowChanged(QWindow *)), SLOT(focusWindowChanged(QWindow *))); closeAct->setShortcut(Qt::ControlModifier+Qt::Key_W); minAct->setShortcut(Qt::ControlModifier+Qt::Key_M); connect(closeAct, SIGNAL(triggered()), SLOT(closeWindow())); connect(minAct, SIGNAL(triggered()), SLOT(minimizeWindow())); connect(zoomAct, SIGNAL(triggered()), SLOT(zoomWindow())); controlActions(mw); } } void OSXStyle::addWindow(QWidget *w) { if (w && windowMenu && !actions.contains(w)) { QAction *action=windowMenu->addAction(w->windowTitle()); action->setCheckable(true); connect(action, SIGNAL(triggered()), this, SLOT(showWindow())); connect(w, SIGNAL(windowTitleChanged(QString)), this, SLOT(windowTitleChanged())); actions.insert(w, action); } } void OSXStyle::removeWindow(QWidget *w) { if (w && windowMenu && actions.contains(w)) { QAction *act=actions.take(w); windowMenu->removeAction(act); disconnect(act, SIGNAL(triggered()), this, SLOT(showWindow())); disconnect(w, SIGNAL(windowTitleChanged(QString)), this, SLOT(windowTitleChanged())); act->deleteLater(); } } void OSXStyle::showWindow() { QAction *act=qobject_cast(sender()); if (!act) { return; } QMap::Iterator it=actions.begin(); QMap::Iterator end=actions.end(); for (; it!=end; ++it) { if (it.value()==act) { Utils::raiseWindow(it.key()); } act->setChecked(it.value()==act); } } void OSXStyle::windowTitleChanged() { QWidget *w=qobject_cast(sender()); if (!w) { return; } if (actions.contains(w)) { actions[w]->setText(w->windowTitle()); } } void OSXStyle::focusWindowChanged(QWindow *win) { QMap::Iterator it=actions.begin(); QMap::Iterator end=actions.end(); for (; it!=end; ++it) { if (it.key()->windowHandle()==win) { it.value()->setChecked(true); controlActions(it.key()); } else { it.value()->setChecked(false); } } } void OSXStyle::closeWindow() { QWidget *w=currentWindow(); if (w) { w->close(); } } void OSXStyle::minimizeWindow() { QWidget *w=currentWindow(); if (w) { w->showMinimized(); } } void OSXStyle::zoomWindow() { QWidget *w=currentWindow(); if (w) { if (w->isMaximized()) { w->showNormal(); } else { w->showMaximized(); } } } QWidget * OSXStyle::currentWindow() { QMap::Iterator it=actions.begin(); QMap::Iterator end=actions.end(); for (; it!=end; ++it) { if (it.value()->isChecked()) { return it.key(); } } return 0; } void OSXStyle::controlActions(QWidget *w) { closeAct->setEnabled(w && w->windowFlags()&Qt::WindowCloseButtonHint); minAct->setEnabled(w && w->windowFlags()&Qt::WindowMinimizeButtonHint); zoomAct->setEnabled(w && w->minimumHeight()!=w->maximumHeight()); } QTreeWidget * OSXStyle::viewWidget() { if (!view) { view=new QTreeWidget(); view->ensurePolished(); } return view; } cantata-2.2.0/support/osxstyle.h000066400000000000000000000037061316350454000167140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef OSXSTYLE_H #define OSXSTYLE_H #include #include #include class QPalette; class QTreeWidget; class QPainter; class QColor; class QWidget; class QMenu; class QAction; class QMainWindow; class QWindow; class Action; class OSXStyle : public QObject { Q_OBJECT public: static OSXStyle * self(); OSXStyle(); const QPalette & viewPalette(); void drawSelection(QStyleOptionViewItem opt, QPainter *painter, double opacity); QColor monoIconColor(); void initWindowMenu(QMainWindow *mw); public: void addWindow(QWidget *w); void removeWindow(QWidget *w); private Q_SLOTS: void showWindow(); void windowTitleChanged(); void focusWindowChanged(QWindow *win); void closeWindow(); void minimizeWindow(); void zoomWindow(); private: QWidget * currentWindow(); void controlActions(QWidget *w); QTreeWidget * viewWidget(); private: QTreeWidget *view; QMenu *windowMenu; QMap actions; QAction *closeAct; QAction *minAct; QAction *zoomAct; friend class Dialog; }; #endif // OSXSTYLE_H cantata-2.2.0/support/pagewidget.cpp000066400000000000000000000307521316350454000174760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "pagewidget.h" #include "icon.h" #include "gtkstyle.h" #include "dialog.h" #include #include #include #include #include #include #include #include #include #include #include static int layoutText(QTextLayout *layout, int maxWidth) { qreal height = 0; int textWidth = 0; layout->beginLayout(); while (true) { QTextLine line = layout->createLine(); if (!line.isValid()) { break; } line.setLineWidth(maxWidth); line.setPosition(QPointF(0, height)); height += line.height(); textWidth = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5)); } layout->endLayout(); return textWidth; } class PageWidgetItemDelegate : public QStyledItemDelegate { public: PageWidgetItemDelegate(QObject *parent, bool std) : QStyledItemDelegate(parent) , standard(std) , underMouse(false) { if (!standard) { int height=QApplication::fontMetrics().height(); iconSize=height>22 ? Icon::stdSize(height*2.5) : 32; } } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } bool mouseOver=option.state&QStyle::State_MouseOver; bool selected=option.state&QStyle::State_Selected; if (standard) { if (GtkStyle::isActive()) { bool mouseOver=option.state&QStyle::State_MouseOver; QStyleOptionViewItem opt = option; initStyleOption(&opt, index); if (!underMouse) { mouseOver=false; } if (mouseOver) { opt.showDecorationSelected=true; GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); opt.showDecorationSelected=false; opt.state&=~(QStyle::State_MouseOver|QStyle::State_Selected); opt.backgroundBrush=QBrush(Qt::transparent); if (selected) { opt.palette.setBrush(QPalette::Text, opt.palette.highlightedText()); } } QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); } else { QStyledItemDelegate::paint(painter, option, index); } return; } bool gtk=mouseOver && GtkStyle::isActive(); bool drawBgnd=true; if (!underMouse) { if (mouseOver && !selected) { drawBgnd=false; } mouseOver=false; } const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize); QFontMetrics fm = painter->fontMetrics(); QSize layoutSize = pixmap.size() / pixmap.DEVICE_PIXEL_RATIO(); QTextLayout iconTextLayout(text, option.font); QTextOption textOption(Qt::AlignHCenter); iconTextLayout.setTextOption(textOption); int maxWidth = qMax(3 * layoutSize.width(), 8 * fm.height()); layoutText(&iconTextLayout, maxWidth); QPen pen = painter->pen(); QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); if (drawBgnd) { if (mouseOver && gtk) { GtkStyle::drawSelection(opt, painter, selected ? 0.75 : 0.25); } else { style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); } } #ifndef Q_OS_WIN if (selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else #endif { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->drawPixmap(option.rect.x() + (option.rect.width()/2)-(layoutSize.width()/2), option.rect.y() + 5, pixmap); if (!text.isEmpty()) { iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width()/2)-(maxWidth/2), option.rect.y() + layoutSize.height()+7)); } painter->setPen(pen); drawFocus(painter, option, option.rect); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (standard) { return QStyledItemDelegate::sizeHint(option, index); } if (!index.isValid()) { return QSize(0, 0); } const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize); QFontMetrics fm = option.fontMetrics; int gap = fm.height(); QSize layoutSize = pixmap.size() / pixmap.DEVICE_PIXEL_RATIO(); if (layoutSize.height() == 0) { /** * No pixmap loaded yet, we'll use the default icon size in this case. */ layoutSize=QSize(iconSize, iconSize); } QTextLayout iconTextLayout(text, option.font); int wt = layoutText(&iconTextLayout, qMax(3 * layoutSize.width(), 8 * fm.height())); int ht = iconTextLayout.boundingRect().height(); int width, height; if (text.isEmpty()) { height = layoutSize.height(); } else { height = layoutSize.height() + ht + 10; } width = qMax(wt, layoutSize.width()) + gap; return QSize(width, height); } void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Background); QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); } } void setUnderMouse(bool u) { underMouse=u; } bool standardList() const { return standard; } private: bool standard; int iconSize; bool underMouse; }; class PageWidgetViewEventHandler : public QObject { public: PageWidgetViewEventHandler(PageWidgetItemDelegate *d, QListWidget *v) : QObject(v) , delegate(d) , view(v) { } protected: bool eventFilter(QObject *obj, QEvent *event) { if (delegate) { if (QEvent::Enter==event->type()) { delegate->setUnderMouse(true); view->viewport()->update(); } else if (QEvent::Leave==event->type()) { delegate->setUnderMouse(false); view->viewport()->update(); } } return QObject::eventFilter(obj, event); } protected: PageWidgetItemDelegate *delegate; QListWidget *view; }; PageWidgetItem::PageWidgetItem(QWidget *p, const QString &header, const QIcon &icon, QWidget *cfg, bool showHeader) : QWidget(p) , wid(cfg) { QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, this); if (showHeader) { QBoxLayout *titleLayout=new QBoxLayout(QBoxLayout::LeftToRight, 0); titleLayout->addWidget(new QLabel(""+header+"", this)); titleLayout->addItem(new QSpacerItem(16, 16, QSizePolicy::Expanding, QSizePolicy::Minimum)); static int iconSize=-1; if (-1==iconSize) { iconSize=QApplication::fontMetrics().height(); if (iconSize>20) { iconSize=Icon::stdSize(iconSize*1.25); } else { iconSize=22; } } QLabel *icn=new QLabel(this); icn->setPixmap(icon.pixmap(iconSize, iconSize)); titleLayout->addWidget(icn); layout->addLayout(titleLayout); layout->addItem(new QSpacerItem(8, 8, QSizePolicy::Fixed, QSizePolicy::Fixed)); } layout->addWidget(cfg); layout->setMargin(0); cfg->setParent(this); adjustSize(); } PageWidget::PageWidget(QWidget *p, bool listView, bool headers) : QWidget(p) , showHeaders(headers) { QBoxLayout *layout=new QBoxLayout(QBoxLayout::LeftToRight, this); list = new QListWidget(p); stack = new QStackedWidget(p); connect(list, SIGNAL(currentRowChanged(int)), stack, SLOT(setCurrentIndex(int))); connect(stack, SIGNAL(currentChanged(int)), this, SIGNAL(currentPageChanged())); layout->addWidget(list); layout->addWidget(stack); layout->setMargin(0); list->setViewMode(QListView::ListMode); list->setVerticalScrollMode(QListView::ScrollPerPixel); list->setMovement(QListView::Static); PageWidgetItemDelegate *delegate=new PageWidgetItemDelegate(list, listView); list->setItemDelegate(delegate); list->setSelectionBehavior(QAbstractItemView::SelectItems); list->setSelectionMode(QAbstractItemView::SingleSelection); list->setAttribute(Qt::WA_MouseTracking); list->installEventFilter(new PageWidgetViewEventHandler(delegate, list)); } PageWidgetItem * PageWidget::addPage(QWidget *widget, const QString &name, const QIcon &icon, const QString &header) { PageWidgetItem *page=new PageWidgetItem(stack, header, icon, widget, showHeaders); QListWidgetItem *listItem=new QListWidgetItem(name, list); listItem->setIcon(icon); stack->addWidget(page); list->addItem(listItem); int rows = list->model()->rowCount(); int width = 0; for (int i = 0; i < rows; ++i) { QSize rowSize=list->sizeHintForIndex(list->model()->index(i, 0)); width = qMax(width, rowSize.width()); } width+=static_cast(list->itemDelegate())->standardList() ? 8 : 25; list->setFixedWidth(width); QSize stackSize = stack->size(); for (int i = 0; i < stack->count(); ++i) { const QWidget *widget = stack->widget(i); if (widget) { stackSize = stackSize.expandedTo(widget->minimumSizeHint()); } } setMinimumHeight(qMax(minimumHeight(), stackSize.height())); setMinimumWidth(qMax(minimumWidth(), stackSize.width()+width+layout()->spacing())); list->setCurrentRow(0); stack->setCurrentIndex(0); pages.insert(listItem, page); return page; } int PageWidget::count() { return list->count(); } PageWidgetItem * PageWidget::currentPage() const { return static_cast(stack->currentWidget()); } void PageWidget::setCurrentPage(PageWidgetItem *item) { if (!item) { return; } QMap::ConstIterator it(pages.constBegin()); QMap::ConstIterator end(pages.constEnd()); for (; it!=end; ++it) { if (it.value()==item) { list->setCurrentItem(it.key()); } } } void PageWidget::setFocus() { list->setFocus(); } cantata-2.2.0/support/pagewidget.h000066400000000000000000000035231316350454000171370ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PAGEWIDGET_H #define PAGEWIDGET_H #include "icon.h" #include #include class QListWidget; class QListWidgetItem; class QStackedWidget; class PageWidgetItem : public QWidget { public: PageWidgetItem(QWidget *p, const QString &header, const QIcon &icon, QWidget *cfg, bool showHeader); virtual ~PageWidgetItem() { } QWidget * widget() const { return wid; } private: QWidget *wid; }; class PageWidget : public QWidget { Q_OBJECT public: PageWidget(QWidget *p, bool listView=false, bool headers=true); virtual ~PageWidget() { } PageWidgetItem * addPage(QWidget *widget, const QString &name, const QIcon &icon, const QString &header); int count(); PageWidgetItem * currentPage() const; void setCurrentPage(PageWidgetItem *item); public Q_SLOTS: void setFocus(); Q_SIGNALS: void currentPageChanged(); private: bool showHeaders; QListWidget *list; QStackedWidget *stack; QMap pages; }; #endif cantata-2.2.0/support/pathrequester.cpp000066400000000000000000000037221316350454000202470ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "pathrequester.h" #include "icon.h" #include "utils.h" #include #include static QIcon icon; void PathRequester::setIcon(const QIcon &icn) { icon=icn; } PathRequester::PathRequester(QWidget *parent) : QWidget(parent) , dirMode(true) { QHBoxLayout *layout=new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); edit=new LineEdit(this); btn=new FlatToolButton(this); layout->addWidget(edit); layout->addWidget(btn); btn->setAutoRaise(true); btn->setIcon(icon.isNull() ? Icon("document-open") : icon); connect(btn, SIGNAL(clicked(bool)), SLOT(choose())); connect(edit, SIGNAL(textChanged(const QString &)), SIGNAL(textChanged(const QString &))); } void PathRequester::choose() { QString item=dirMode ? QFileDialog::getExistingDirectory(this, tr("Select Folder"), text()) : QFileDialog::getOpenFileName(this, tr("Select File"), Utils::getDir(text()), filter); if (!item.isEmpty()) { setText(item); } } void PathRequester::setEnabled(bool e) { edit->setEnabled(e); btn->setEnabled(e); } cantata-2.2.0/support/pathrequester.h000066400000000000000000000034471316350454000177200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PATH_REQUESER_H #define PATH_REQUESER_H #include "lineedit.h" #include "flattoolbutton.h" #include class PathRequester : public QWidget { Q_OBJECT public: static void setIcon(const QIcon &icn); PathRequester(QWidget *parent); virtual ~PathRequester() { } QString text() const { return QDir::fromNativeSeparators(edit->text()); } void setText(const QString &t) { edit->setText(QDir::toNativeSeparators(t)); } void setButtonVisible(bool v) { btn->setVisible(v); } void setFocus() { edit->setFocus(); } void setDirMode(bool m) { dirMode=m; } LineEdit * lineEdit() const { return edit; } QToolButton * button() const { return btn; } void setFilter(const QString &f) { filter=f; } public Q_SLOTS: void setEnabled(bool e); Q_SIGNALS: void textChanged(const QString &); private Q_SLOTS: void choose(); private: LineEdit *edit; FlatToolButton *btn; bool dirMode; QString filter; }; #endif cantata-2.2.0/support/proxystyle.cpp000066400000000000000000000073001316350454000176110ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "proxystyle.h" #include "acceleratormanager.h" #include "gtkstyle.h" #include "utils.h" #include "monoicon.h" #include #include #include #include #if !defined Q_OS_MAC static const char * constAccelProp="managed-accel"; #endif const char * ProxyStyle::constModifyFrameProp="mod-frame"; ProxyStyle::ProxyStyle(int modView) : modViewFrame(modView) { #if !defined Q_OS_WIN && !defined Q_OS_MAC editClearIcon=MonoIcon::icon(FontAwesome::timescircle, QColor(128, 128, 128), QColor(128, 128, 128)); #endif } void ProxyStyle::polish(QWidget *widget) { #if !defined Q_OS_MAC if (widget && qobject_cast(widget) && !widget->property(constAccelProp).isValid()) { AcceleratorManager::manage(widget); widget->setProperty(constAccelProp, true); } #endif baseStyle()->polish(widget); } int ProxyStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const { return SH_DialogButtonBox_ButtonsHaveIcons == hint ? false : baseStyle()->styleHint(hint, option, widget, returnData); } void ProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { baseStyle()->drawPrimitive(element, option, painter, widget); if (modViewFrame && PE_Frame==element && widget) { QVariant v=widget->property(constModifyFrameProp); if (v.isValid()) { int mod=v.toInt(); const QRect &r=option->rect; if (option->palette.base().color()==Qt::transparent) { painter->setPen(QPen(QApplication::palette().color(QPalette::Base), 1)); } else { painter->setPen(QPen(option->palette.base(), 1)); } if (mod&VF_Side && modViewFrame&VF_Side) { if (Qt::LeftToRight==option->direction) { painter->drawLine(r.topRight()+QPoint(0, 1), r.bottomRight()+QPoint(0, -1)); } else { painter->drawLine(r.topLeft()+QPoint(0, 1), r.bottomLeft()+QPoint(0, -1)); } } if (mod&VF_Top && modViewFrame&VF_Top) { painter->drawLine(r.topLeft()+QPoint(1, 0), r.topRight()); } } } } #if !defined Q_OS_WIN && !defined Q_OS_MAC QPixmap ProxyStyle::standardPixmap(StandardPixmap sp, const QStyleOption *opt, const QWidget *widget) const { QPixmap pixmap=baseStyle()->standardPixmap(sp, opt, widget); if (SP_LineEditClearButton==sp) { return editClearIcon.pixmap(pixmap.size()); } return pixmap; } QIcon ProxyStyle::standardIcon(StandardPixmap sp, const QStyleOption *opt, const QWidget *widget) const { if (SP_LineEditClearButton==sp) { return editClearIcon; } return baseStyle()->standardIcon(sp, opt, widget); } #endif cantata-2.2.0/support/proxystyle.h000066400000000000000000000040761316350454000172650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PROXYSTYLE_H #define PROXYSTYLE_H #include #include class ProxyStyle : public QProxyStyle { public: static const char * constModifyFrameProp; enum FrameMod { VF_None = 0x00, VF_Side = 0x01, VF_Top = 0x02 }; ProxyStyle(int modView=VF_None); virtual ~ProxyStyle() { } void setModifyViewFrame(int modView) { modViewFrame=modView; } int modifyViewFrame() const { return modViewFrame; } void polish(QPalette &pal) { QProxyStyle::polish(pal); } void polish(QApplication *app) { QProxyStyle::polish(app); } void polish(QWidget *widget); int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const; void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; #if !defined Q_OS_WIN && !defined Q_OS_MAC QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *opt, const QWidget *widget) const; QIcon standardIcon(StandardPixmap sp, const QStyleOption *opt, const QWidget *widget) const; #endif private: int modViewFrame; #if !defined Q_OS_WIN && !defined Q_OS_MAC QIcon editClearIcon; #endif }; #endif cantata-2.2.0/support/shortcutsmodel.cpp000066400000000000000000000156651316350454000204430ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "shortcutsmodel.h" #include "action.h" #include "actioncollection.h" #include "utils.h" #include ShortcutsModel::ShortcutsModel(const QHash &actionCollections, QObject *parent) : QAbstractItemModel(parent), _changedCount(0) { _showIcons=!QCoreApplication::testAttribute(Qt::AA_DontShowIconsInMenus); for(int r = 0; r < actionCollections.values().count(); r++) { ActionCollection *coll = actionCollections.values().at(r); Item *item = new Item(); item->row = r; item->collection = coll; for(int i = 0; i < coll->actions().count(); i++) { Action *action = qobject_cast(coll->actions().at(i)); if(!action) continue; Item *actionItem = new Item(); actionItem->parentItem = item; actionItem->row = i; actionItem->collection = coll; actionItem->action = action; actionItem->shortcut = action->shortcut(); item->actionItems.append(actionItem); } _categoryItems.append(item); } } ShortcutsModel::~ShortcutsModel() { qDeleteAll(_categoryItems); } QModelIndex ShortcutsModel::parent(const QModelIndex &child) const { if(!child.isValid()) return QModelIndex(); Item *item = static_cast(child.internalPointer()); Q_ASSERT(item); if(!item->parentItem) return QModelIndex(); return createIndex(item->parentItem->row, 0, item->parentItem); } QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex &parent) const { if(parent.isValid()) return createIndex(row, column, static_cast(parent.internalPointer())->actionItems.at(row)); // top level category item return createIndex(row, column, _categoryItems.at(row)); } int ShortcutsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; /*if(!parent.isValid()) return 2; Item *item = static_cast(parent.internalPointer()); Q_ASSERT(item); if(!item->parentItem) return 2; return 2;*/ } int ShortcutsModel::rowCount(const QModelIndex &parent) const { if(!parent.isValid()) return _categoryItems.count(); Item *item = static_cast(parent.internalPointer()); Q_ASSERT(item); if(!item->parentItem) return item->actionItems.count(); return 0; } QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); switch(section) { case 0: return tr("Action"); case 1: return tr("Shortcut"); default: return QVariant(); } } QVariant ShortcutsModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) return QVariant(); Item *item = static_cast(index.internalPointer()); Q_ASSERT(item); if(!item->parentItem) { if(index.column() != 0) return QVariant(); switch(role) { case Qt::DisplayRole: return item->collection->property("Category"); default: return QVariant(); } } Action *action = qobject_cast(item->action); Q_ASSERT(action); switch(role) { case Qt::DisplayRole: switch(index.column()) { case 0: { return Action::settingsText(action); } case 1: return item->shortcut.toString(QKeySequence::NativeText); default: return QVariant(); } case Qt::DecorationRole: if(index.column() == 0 && _showIcons) return action->icon(); return QVariant(); case ActionRole: return QVariant::fromValue(action); case DefaultShortcutRole: return action->shortcut(Action::DefaultShortcut); case ActiveShortcutRole: return item->shortcut; case IsConfigurableRole: return action->isShortcutConfigurable(); default: return QVariant(); } } bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(role != ActiveShortcutRole) return false; if(!index.parent().isValid()) return false; Item *item = static_cast(index.internalPointer()); Q_ASSERT(item); Q_ASSERT(item->action); QKeySequence newSeq = value.value(); QKeySequence oldSeq = item->shortcut; QKeySequence storedSeq = item->action->shortcut(Action::ActiveShortcut); item->shortcut = newSeq; emit dataChanged(index, index.sibling(index.row(), 1)); if(oldSeq == storedSeq && newSeq != storedSeq) { if(++_changedCount == 1) emit hasChanged(true); } else if(oldSeq != storedSeq && newSeq == storedSeq) { if(--_changedCount == 0) emit hasChanged(false); } return true; } void ShortcutsModel::load() { foreach(Item *catItem, _categoryItems) { foreach(Item *actItem, catItem->actionItems) { actItem->shortcut = actItem->action->shortcut(Action::ActiveShortcut); } } emit dataChanged(index(0, 1), index(rowCount()-1, 1)); if(_changedCount != 0) { _changedCount = 0; emit hasChanged(false); } } void ShortcutsModel::commit() { foreach(Item *catItem, _categoryItems) { foreach(Item *actItem, catItem->actionItems) { actItem->action->setShortcut(actItem->shortcut, Action::ActiveShortcut); } catItem->collection->writeSettings(); } if(_changedCount != 0) { _changedCount = 0; emit hasChanged(false); } } void ShortcutsModel::defaults() { for(int cat = 0; cat < rowCount(); cat++) { QModelIndex catidx = index(cat, 0); for(int act = 0; act < rowCount(catidx); act++) { QModelIndex actidx = index(act, 1, catidx); setData(actidx, actidx.data(DefaultShortcutRole), ActiveShortcutRole); } } } cantata-2.2.0/support/shortcutsmodel.h000066400000000000000000000102541316350454000200750ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SHORTCUTSMODEL_H #define SHORTCUTSMODEL_H #include #include class Action; class ActionCollection; //! Model that exposes the actions from one or more ActionCollections /** This model takes one or more ActionCollections and exposes their actions as model items. * Note that the ShortcutsModel will not react to changes in the ActionCollection (e.g. adding, * removing actions), because it is supposed to be used after all actions being defined. */ class ShortcutsModel : public QAbstractItemModel { Q_OBJECT public: enum Role { ActionRole = Qt::UserRole, DefaultShortcutRole, ActiveShortcutRole, IsConfigurableRole }; ShortcutsModel(const QHash &actionCollections, QObject *parent = 0); ~ShortcutsModel(); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &child) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; bool setData(const QModelIndex &index, const QVariant &value, int role = ActiveShortcutRole); public slots: //! Load shortcuts from the ActionCollections /** Note that this will not rebuild the internal structure of the model, as we assume the * ActionCollections to be static during the lifetime of the settingspage. This will merely * re-read the shortcuts currently set in Quassel. */ void load(); //! Load default shortcuts from the ActionCollections /** Note that this will not rebuild the internal structure of the model, as we assume the * ActionCollections to be static during the lifetime of the settingspage. This will update * the model's state from the ActionCollections' defaults. */ void defaults(); //! Commit the model changes to the ActionCollections void commit(); inline bool hasChanged() const { return _changedCount; } signals: //! Reflects the difference between model contents and the ActionCollections we loaded this from void hasChanged(bool changed); private: struct Item { inline Item() : row(-1), parentItem(0), collection(0), action(0) { } inline ~Item() { qDeleteAll(actionItems); } int row; Item *parentItem; ActionCollection *collection; Action *action; QKeySequence shortcut; QList actionItems; }; QList _categoryItems; int _changedCount; bool _showIcons; }; #endif // SHORTCUTSMODEL_H cantata-2.2.0/support/shortcutssettingswidget.cpp000066400000000000000000000143501316350454000223750ustar00rootroot00000000000000 /*************************************************************************** * Copyright (C) 2010 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "shortcutssettingswidget.h" #include "action.h" #include "actioncollection.h" #include "shortcutsmodel.h" ShortcutsFilter::ShortcutsFilter(QObject *parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); } void ShortcutsFilter::setFilterString(const QString &filterString) { _filterString = filterString; invalidateFilter(); } bool ShortcutsFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if(!source_parent.isValid()) return true; QModelIndex index = source_parent.model()->index(source_row, 0, source_parent); Q_ASSERT(index.isValid()); if(!qobject_cast(index.data(ShortcutsModel::ActionRole).value())->isShortcutConfigurable()) return false; for(int col = 0; col < source_parent.model()->columnCount(source_parent); col++) { if(source_parent.model()->index(source_row, col, source_parent).data().toString().contains(_filterString, Qt::CaseInsensitive)) return true; } return false; } /****************************************************************************/ ShortcutsSettingsWidget::ShortcutsSettingsWidget(const QHash &actionCollections, QWidget *parent) : QWidget(parent), _shortcutsModel(new ShortcutsModel(actionCollections, this)), _shortcutsFilter(new ShortcutsFilter(this)) { setupUi(this); _shortcutsFilter->setSourceModel(_shortcutsModel); shortcutsView->setModel(_shortcutsFilter); shortcutsView->expandAll(); shortcutsView->resizeColumnToContents(0); shortcutsView->sortByColumn(0, Qt::AscendingOrder); shortcutsView->setUniformRowHeights(true); if (1==_shortcutsModel->rowCount()) { shortcutsView->setIndentation(0); shortcutsView->setRootIndex(_shortcutsFilter->index(0, 0)); } keySequenceWidget->setModel(_shortcutsModel); connect(keySequenceWidget, SIGNAL(keySequenceChanged(QKeySequence,QModelIndex)), SLOT(keySequenceChanged(QKeySequence,QModelIndex))); connect(shortcutsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(setWidgetStates())); setWidgetStates(); connect(useDefault, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault())); connect(useCustom, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault())); // connect(_shortcutsModel, SIGNAL(hasChanged(bool)), SLOT(setChangedState(bool))); // fugly, but directly setting it from the ctor doesn't seem to work QTimer::singleShot(0, searchEdit, SLOT(setFocus())); } QTreeView * ShortcutsSettingsWidget::view() { return shortcutsView; } void ShortcutsSettingsWidget::setWidgetStates() { if(shortcutsView->currentIndex().isValid() && shortcutsView->currentIndex().parent().isValid()) { QKeySequence active = shortcutsView->currentIndex().data(ShortcutsModel::ActiveShortcutRole).value(); QKeySequence def = shortcutsView->currentIndex().data(ShortcutsModel::DefaultShortcutRole).value(); defaultShortcut->setText(def.isEmpty()? tr("None") : def.toString(QKeySequence::NativeText)); actionBox->setEnabled(true); if(active == def) { useDefault->setChecked(true); keySequenceWidget->setKeySequence(QKeySequence()); } else { useCustom->setChecked(true); keySequenceWidget->setKeySequence(active); } } else { defaultShortcut->setText(tr("None")); actionBox->setEnabled(false); useDefault->setChecked(true); keySequenceWidget->setKeySequence(QKeySequence()); } } void ShortcutsSettingsWidget::on_searchEdit_textChanged(const QString &text) { _shortcutsFilter->setFilterString(text); } void ShortcutsSettingsWidget::keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting) { if(conflicting.isValid()) _shortcutsModel->setData(conflicting, QKeySequence(), ShortcutsModel::ActiveShortcutRole); QModelIndex rowIdx = _shortcutsFilter->mapToSource(shortcutsView->currentIndex()); Q_ASSERT(rowIdx.isValid()); _shortcutsModel->setData(rowIdx, seq, ShortcutsModel::ActiveShortcutRole); setWidgetStates(); } void ShortcutsSettingsWidget::toggledCustomOrDefault() { if(!shortcutsView->currentIndex().isValid()) return; QModelIndex index = _shortcutsFilter->mapToSource(shortcutsView->currentIndex()); Q_ASSERT(index.isValid()); if(useDefault->isChecked()) { _shortcutsModel->setData(index, index.data(ShortcutsModel::DefaultShortcutRole)); } else { _shortcutsModel->setData(index, QKeySequence()); } setWidgetStates(); // If custom is selected, and the action has no short-cut, setWidgetStates() re-checks // the default radio. This is a bit counter-intuitive, so ensure whichever radio caused // the toggle, that it is checked. QRadioButton *btn=qobject_cast(sender()); if (btn) { btn->setChecked(true); } } void ShortcutsSettingsWidget::save() { _shortcutsModel->commit(); } cantata-2.2.0/support/shortcutssettingswidget.h000066400000000000000000000051631316350454000220440ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SHORTCUTSSETTINGSWIDGET_H #define SHORTCUTSSETTINGSWIDGET_H #include #include "support/ui_shortcutssettingswidget.h" class ActionCollection; class ShortcutsModel; class ShortcutsFilter : public QSortFilterProxyModel { Q_OBJECT public: ShortcutsFilter(QObject *parent = 0); public Q_SLOTS: void setFilterString(const QString &filterString); protected: virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; private: QString _filterString; }; class ShortcutsSettingsWidget : public QWidget, private Ui::ShortcutsSettingsWidget { Q_OBJECT public: ShortcutsSettingsWidget(const QHash &actionCollections, QWidget *parent = 0); inline bool hasDefaults() const { return true; } QTreeView * view(); public Q_SLOTS: void save(); private Q_SLOTS: void on_searchEdit_textChanged(const QString &text); void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting); void setWidgetStates(); void toggledCustomOrDefault(); private: ShortcutsModel *_shortcutsModel; ShortcutsFilter *_shortcutsFilter; }; #endif // SHORTCUTSSETTINGSPAGE_H cantata-2.2.0/support/shortcutssettingswidget.ui000066400000000000000000000071541316350454000222340ustar00rootroot00000000000000 ShortcutsSettingsWidget 0 0 497 481 0 Search: searchEdit QAbstractItemView::NoEditTriggers false true true false true true true Shortcut for Selected Action Default: None Custom: Qt::Horizontal 346 20 KeySequenceWidget QWidget
    support/keysequencewidget.h
    1
    LineEdit QLineEdit
    support/lineedit.h
    0
    BuddyLabel QLabel
    support/buddylabel.h
    searchEdit shortcutsView useDefault useCustom
    cantata-2.2.0/support/spinner.cpp000066400000000000000000000067101316350454000170310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "spinner.h" #include "utils.h" #include #include #include #include #include #include Spinner::Spinner(QObject *p, bool inMiddle) : QWidget(0) , timer(0) , space(Utils::scaleForDpi(4)) , value(0) , active(false) , central(inMiddle) , onView(false) { Q_UNUSED(p) int size=fontMetrics().height()*1.5; setVisible(false); setMinimumSize(size, size); setMaximumSize(size, size); setAttribute(Qt::WA_TransparentForMouseEvents, true); } void Spinner::setWidget(QWidget *widget) { setParent(widget); onView=qobject_cast(widget); } void Spinner::start() { value=0; setVisible(true); setPosition(); active=true; if (!timer) { timer=new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); } timer->start(75); } void Spinner::stop() { setVisible(false); active=false; if (timer) { timer->stop(); } } static const int constSpinnerSteps=64; void Spinner::paintEvent(QPaintEvent *event) { static const int constParts=8; int lineWidth(Utils::scaleForDpi(2)); QPainter p(this); QRectF rectangle(1.5, 1.5, size().width()-3, size().height()-3); QColor col(palette().color(QPalette::Text)); p.setRenderHint(QPainter::Antialiasing, true); p.setClipRect(event->rect()); double size=(360*16)/(2.0*constParts); for (int i=0; i=constSpinnerSteps) { value=0; } } void Spinner::setPosition() { QWidget *pw=parentWidget(); int hSpace=space+(onView && pw && static_cast(pw)->verticalScrollBar() && static_cast(pw)->verticalScrollBar()->isVisible() ? static_cast(pw)->verticalScrollBar()->width()+2 : 0); QPoint current=pos(); QPoint desired=central ? QPoint((parentWidget()->size().width()-size().width())/2, (parentWidget()->size().height()-size().height())/2) : QApplication::isRightToLeft() ? QPoint(hSpace, space) : QPoint(parentWidget()->size().width()-(size().width()+hSpace), space); if (current!=desired) { move(desired); } } cantata-2.2.0/support/spinner.h000066400000000000000000000025721316350454000165000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SPINNER_H #define SPINNER_H #include class QTimer; class Spinner : public QWidget { Q_OBJECT public: Spinner(QObject *p, bool inMiddle=false); virtual ~Spinner() { } void setWidget(QWidget *widget); void start(); void stop(); void paintEvent(QPaintEvent *event); bool isActive() const { return active; } private Q_SLOTS: void timeout(); private: void setPosition(); private: QTimer *timer; int space; int value; bool active; bool central; bool onView; }; #endif cantata-2.2.0/support/squeezedtextlabel.cpp000066400000000000000000000032101316350454000210750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "squeezedtextlabel.h" SqueezedTextLabel::SqueezedTextLabel(QWidget *p) : QLabel(p) { setTextElideMode(isRightToLeft() ? Qt::ElideLeft : Qt::ElideRight); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } void SqueezedTextLabel::setTextElideMode(Qt::TextElideMode mode) { elideMode=mode; setAlignment((Qt::ElideLeft==elideMode ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); } void SqueezedTextLabel::elideText() { QFontMetrics fm(fontMetrics()); int labelWidth = size().width(); int lineWidth = fm.width(originalText); if (lineWidth > labelWidth) { QLabel::setText(fm.elidedText(originalText, elideMode, labelWidth)); setToolTip(originalText); } else { QLabel::setText(originalText); setToolTip(QString()); } } cantata-2.2.0/support/squeezedtextlabel.h000066400000000000000000000032141316350454000205460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SQUEEZEDTEXTLABEL_H #define SQUEEZEDTEXTLABEL_H #include #include class QResizeEvent; class SqueezedTextLabel : public QLabel { public: SqueezedTextLabel(QWidget *p); void setText(const QString &text) { originalText=text; elideText(); } const QString & fullText() const { return originalText; } void setTextElideMode(Qt::TextElideMode mode); QSize minimumSizeHint() const { QSize sh = QLabel::minimumSizeHint(); sh.setWidth(-1); return sh; } QSize sizeHint() const { return QSize(fontMetrics().width(originalText), QLabel::sizeHint().height()); } void resizeEvent(QResizeEvent *) { elideText(); } private: void elideText(); private: QString originalText; Qt::TextElideMode elideMode; }; #endif // SQUEEZEDTEXTLABEL_H cantata-2.2.0/support/thread.cpp000066400000000000000000000064211316350454000166210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "thread.h" #include "globalstatic.h" #include "utils.h" #include #include #include #include #include #ifndef _MSC_VER #include #endif static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void ThreadCleaner::enableDebug() { debugEnabled=true; } static void segvHandler(int i) { if (debugEnabled) qWarning() << "SEGV handler called"; _exit(i); } GLOBAL_STATIC(ThreadCleaner, instance) void ThreadCleaner::stopAll() { DBUG << "Remaining threads:" << threads.count(); foreach (Thread *thread, threads) { DBUG << "Cleanup" << thread->objectName(); disconnect(thread, SIGNAL(finished()), this, SLOT(threadFinished())); } foreach (Thread *thread, threads) { thread->stop(); } QList stillRunning; foreach (Thread *thread, threads) { if (thread->wait(250)) { delete thread; } else { stillRunning.append(thread); DBUG << "Failed to close" << thread->objectName(); } } // Terminate any still running threads... signal(SIGSEGV, segvHandler); // Ignore SEGV in case a thread throws an error... foreach (Thread *thread, stillRunning) { thread->terminate(); } } void ThreadCleaner::threadFinished() { Thread *thread=qobject_cast(sender()); if (thread) { thread->deleteLater(); threads.removeAll(thread); DBUG << "Thread finished" << thread->objectName() << "Total threads:" << threads.count(); } } void ThreadCleaner::add(Thread *thread) { threads.append(thread); connect(thread, SIGNAL(finished()), this, SLOT(threadFinished())); DBUG << "Thread created" << thread->objectName() << "Total threads:" << threads.count(); } Thread::Thread(const QString &name, QObject *p) : QThread(p) { setObjectName(name); ThreadCleaner::self()->add(this); } Thread::~Thread() { DBUG << objectName() << "destroyed"; } void Thread::run() { Utils::initRand(); QThread::run(); } QTimer * Thread::createTimer(QObject *parent) { QTimer *timer=new QTimer(parent ? parent : this); connect(this, SIGNAL(finished()), timer, SLOT(stop())); return timer; } void Thread::deleteTimer(QTimer *timer) { if (timer) { disconnect(this, SIGNAL(finished()), timer, SLOT(stop())); timer->deleteLater(); } } cantata-2.2.0/support/thread.h000066400000000000000000000036601316350454000162700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef THREAD_H #define THREAD_H #include class QTimer; // ThreadCleaner *needs* to reside in the GUI thread. When a 'Thread' is created it will connect // its finished signal to threadFinished(), this then calls deleteLater() to ensure that the // thread is finished before it is deleted - and is deleted in the gui thread. class Thread; class ThreadCleaner : public QObject { Q_OBJECT public: static void enableDebug(); static ThreadCleaner * self(); ThreadCleaner() { } ~ThreadCleaner() { } // This function must *ONLY* be called from GUI thread... void stopAll(); public Q_SLOTS: void threadFinished(); private: void add(Thread *thread); private: QList threads; friend class Thread; }; class Thread : public QThread { Q_OBJECT public: Thread(const QString &name, QObject *p=0); virtual ~Thread(); // Make QThread::msleep accessible! using QThread::msleep; virtual void run(); QTimer * createTimer(QObject *parent=0); void deleteTimer(QTimer *timer); public Q_SLOTS: void stop() { quit(); } }; #endif cantata-2.2.0/support/urllabel.cpp000066400000000000000000000033101316350454000171460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "urllabel.h" #include #include #include #include UrlLabel::UrlLabel(QWidget *p) : QLabel(p) , pressed(false) { setCursor(QCursor(Qt::PointingHandCursor)); setContextMenuPolicy(Qt::NoContextMenu); } void UrlLabel::setText(const QString &t) { QLabel::setText(""+t+""); } void UrlLabel::setProperty(const char *name, const QVariant &value) { if (name && !strcmp(name, "text") && QVariant::String==value.type()) { setText(value.toString()); } } void UrlLabel::mousePressEvent(QMouseEvent *ev) { if (Qt::LeftButton==ev->buttons()) { pressed=true; } } void UrlLabel::mouseReleaseEvent(QMouseEvent *) { if (pressed) { pressed=false; if (this==QApplication::widgetAt(QCursor::pos())) { emit leftClickedUrl(); } } } cantata-2.2.0/support/urllabel.h000066400000000000000000000024571316350454000166260ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef URLLABEL_H #define URLLABEL_H #include #include class UrlLabel : public QLabel { Q_OBJECT public: UrlLabel(QWidget *p); virtual ~UrlLabel() { } void setText(const QString &t); void setProperty(const char *name, const QVariant &value); Q_SIGNALS: void leftClickedUrl(); protected: void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *); private: bool pressed; }; #endif // URLLABEL_H cantata-2.2.0/support/utils.cpp000066400000000000000000000667311316350454000165240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "utils.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _MSC_VER #include #include #else #include #endif #include #include #ifndef Q_OS_WIN #include #include #endif #include #if QT_QTDBUS_FOUND && !defined Q_OS_MAC && !defined Q_OS_WIN #include #include #endif const QLatin1Char Utils::constDirSep('/'); const QLatin1String Utils::constDirSepStr("/"); const char * Utils::constDirSepCharStr="/"; static const QLatin1String constHttp("http://"); QString Utils::fixPath(const QString &dir, bool ensureEndsInSlash) { QString d(dir); if (!d.isEmpty() && !d.startsWith(constHttp)) { #ifdef Q_OS_WIN // Windows shares can be \\host\share (which gets converted to //host/share) // so if th epath starts with // we need to keep this double slash. bool startsWithDoubleSlash=d.length()>2 && d.startsWith(QLatin1String("//")); #endif d.replace(QLatin1String("//"), constDirSepStr); #ifdef Q_OS_WIN if (startsWithDoubleSlash) { // Re add first slash d=QLatin1Char('/')+d; } #endif } d.replace(QLatin1String("/./"), constDirSepStr); if (ensureEndsInSlash && !d.isEmpty() && !d.endsWith(constDirSep)) { d+=constDirSep; } return d; } #ifndef Q_OS_WIN static const QLatin1String constTilda("~"); QString Utils::homeToTilda(const QString &s) { QString hp=QDir::homePath(); if (s==hp) { return constTilda; } if (s.startsWith(hp+constDirSepStr)) { return constTilda+fixPath(s.mid(hp.length()), false); } return s; } QString Utils::tildaToHome(const QString &s) { if (s==constTilda) { return fixPath(QDir::homePath()); } if (s.startsWith(constTilda+constDirSep)) { return fixPath(QDir::homePath()+constDirSepStr+s.mid(1), false); } return s; } #endif QString Utils::getDir(const QString &file) { bool isCueFile=file.contains("/cue:///") && file.contains("?pos="); QString d(file); int slashPos(d.lastIndexOf(constDirSep)); if(slashPos!=-1) { d.remove(slashPos+1, d.length()); } if (isCueFile) { d.remove("cue:///"); } return fixPath(d); } QString Utils::getFile(const QString &file) { QString d(file); int slashPos=d.lastIndexOf(constDirSep); if (-1!=slashPos) { d.remove(0, slashPos+1); } return d; } QString Utils::changeExtension(const QString &file, const QString &extension) { if (extension.isEmpty()) { return file; } QString f(file); int pos=f.lastIndexOf('.'); if (pos>1) { f=f.left(pos+1); } if (f.endsWith('.')) { return f+(extension.startsWith('.') ? extension.mid(1) : extension); } return f+(extension.startsWith('.') ? extension : (QChar('.')+extension)); } bool Utils::isDirReadable(const QString &dir) { #ifdef Q_OS_WIN if (dir.isEmpty()) { return false; } else { QDir d(dir); bool dirReadable=d.isReadable(); // Handle cases where dir is set to \\server\ (i.e. no shared folder is set in path) if (!dirReadable && dir.startsWith(QLatin1String("//")) && d.isRoot() && (dir.length()-1)==dir.indexOf(Utils::constDirSep, 2)) { dirReadable=true; } return dirReadable; } #else return dir.isEmpty() ? false : QDir(dir).isReadable(); #endif } QString Utils::strippedText(QString s) { s.remove(QString::fromLatin1("...")); int i = 0; while (i < s.size()) { ++i; if (s.at(i - 1) != QLatin1Char('&')) { continue; } if (i < s.size() && s.at(i) == QLatin1Char('&')) { ++i; } s.remove(i - 1, 1); } return s.trimmed(); } QString Utils::stripAcceleratorMarkers(QString label) { int p = 0; forever { p = label.indexOf('&', p); if(p < 0 || p + 1 >= label.length()) { break; } if(label.at(p + 1).isLetterOrNumber() || label.at(p + 1) == '&') { label.remove(p, 1); } ++p; } return label; } QString Utils::convertPathForDisplay(const QString &path, bool isFolder) { if (path.isEmpty() || path.startsWith(constHttp)) { return path; } QString p(path); if (p.endsWith(constDirSep)) { p=p.left(p.length()-1); } /* TODO: Display ~/Music or /home/user/Music / /Users/user/Music ??? p=homeToTilda(QDir::toNativeSeparators(p)); */ return QDir::toNativeSeparators(isFolder && p.endsWith(constDirSep) ? p.left(p.length()-1) : p); } QString Utils::convertPathFromDisplay(const QString &path, bool isFolder) { QString p=path.trimmed(); if (p.isEmpty()) { return p; } if (p.startsWith(constHttp)) { return fixPath(p); } return tildaToHome(fixPath(QDir::fromNativeSeparators(p), isFolder)); } #ifndef Q_OS_WIN gid_t Utils::getGroupId(const char *groupName) { static bool init=false; static gid_t gid=0; if (init) { return gid; } init=true; // First of all see if current group is actually 'groupName'!!! gid_t egid=getegid(); struct group *group=getgrgid(egid); if (group && 0==strcmp(group->gr_name, groupName)) { gid=egid; return gid; } // Now see if user is a member of 'groupName' struct passwd *pw=getpwuid(geteuid()); if (!pw) { return gid; } group=getgrnam(groupName); if (group) { for (int i=0; group->gr_mem[i]; ++i) { if (0==strcmp(group->gr_mem[i], pw->pw_name)) { gid=group->gr_gid; return gid; } } } return gid; } /* * Set file permissions. * If user is a memeber of "users" group, then set file as owned by and writeable by "users" group. */ void Utils::setFilePerms(const QString &file, const char *groupName) { // // Clear any umask before setting file perms mode_t oldMask(umask(0000)); gid_t gid=getGroupId(groupName); QByteArray fn=QFile::encodeName(file); ::chmod(fn.constData(), 0==gid ? 0644 : 0664); if (0!=gid) { int rv=::chown(fn.constData(), geteuid(), gid); Q_UNUSED(rv) } // Reset umask ::umask(oldMask); } #else void Utils::setFilePerms(const QString &file, const char *groupName) { Q_UNUSED(file) Q_UNUSED(groupName) } #endif /* * Create directory, and set its permissions. * If user is a memeber of "audio" group, then set dir as owned by and writeable by "audio" group. */ bool Utils::createWorldReadableDir(const QString &dir, const QString &base, const char *groupName) { #ifdef Q_OS_WIN Q_UNUSED(base) Q_UNUSED(groupName) return makeDir(dir, 0775); #else // // Clear any umask before dir is created mode_t oldMask(umask(0000)); gid_t gid=base.isEmpty() ? 0 : getGroupId(groupName); bool status(makeDir(dir, 0==gid ? 0755 : 0775)); if (status && 0!=gid && dir.startsWith(base)) { QStringList parts=dir.mid(base.length()).split(constDirSep); QString d(base); foreach (const QString &p, parts) { d+=constDirSep+p; int rv=::chown(QFile::encodeName(d).constData(), geteuid(), gid); Q_UNUSED(rv) } } // Reset umask ::umask(oldMask); return status; #endif } // Copied from KDE... START #include #include #include // kde_file.h #ifndef Q_OS_WIN #if (defined _LFS64_LARGEFILE) && (defined _LARGEFILE64_SOURCE) && (!defined _GNU_SOURCE) && (!defined __sun) #define KDE_stat ::stat64 #define KDE_lstat ::lstat64 #define KDE_struct_stat struct stat64 #define KDE_mkdir ::mkdir #else #define KDE_stat ::stat #define KDE_lstat ::lstat #define KDE_struct_stat struct stat #define KDE_mkdir ::mkdir #endif #endif // Q_OS_WIN // kstandarddirs.h bool Utils::makeDir(const QString &dir, int mode) { // we want an absolute path if (QDir::isRelativePath(dir)) { return false; } #ifdef Q_OS_WIN Q_UNUSED(mode) return QDir().mkpath(dir); #else QString target = dir; uint len = target.length(); // append trailing slash if missing if (dir.at(len - 1) != QLatin1Char('/')) { target += QLatin1Char('/'); } QString base; uint i = 1; while ( i < len ) { KDE_struct_stat st; int pos = target.indexOf(QLatin1Char('/'), i); base += target.mid(i - 1, pos - i + 1); QByteArray baseEncoded = QFile::encodeName(base); // bail out if we encountered a problem if (KDE_stat(baseEncoded, &st) != 0) { // Directory does not exist.... // Or maybe a dangling symlink ? if (KDE_lstat(baseEncoded, &st) == 0) (void)unlink(baseEncoded); // try removing if (KDE_mkdir(baseEncoded, static_cast(mode)) != 0) { baseEncoded.prepend( "trying to create local folder " ); perror(baseEncoded.constData()); return false; // Couldn't create it :-( } } i = pos + 1; } return true; #endif } QString Utils::formatByteSize(double size) { static bool useSiUnites=false; static QLocale locale; #ifndef Q_OS_WIN static bool init=false; if (!init) { init=true; const char *env=qgetenv("KDE_FULL_SESSION"); QString dm=env && 0==strcmp(env, "true") ? QLatin1String("KDE") : QString(qgetenv("XDG_CURRENT_DESKTOP")); useSiUnites=!dm.isEmpty() && QLatin1String("KDE")!=dm; } #endif int unit = 0; double multiplier = useSiUnites ? 1000.0 : 1024.0; while (qAbs(size) >= multiplier && unit < 3) { size /= multiplier; unit++; } if (useSiUnites) { switch(unit) { case 0: return QObject::tr("%1 B").arg(size); case 1: return QObject::tr("%1 kB").arg(locale.toString(size, 'f', 1)); case 2: return QObject::tr("%1 MB").arg(locale.toString(size, 'f', 1)); default: case 3: return QObject::tr("%1 GB").arg(locale.toString(size, 'f', 1)); } } else { switch(unit) { case 0: return QObject::tr("%1 B").arg(size); case 1: return QObject::tr("%1 KiB").arg(locale.toString(size, 'f', 1)); case 2: return QObject::tr("%1 MiB").arg(locale.toString(size, 'f', 1)); default: case 3: return QObject::tr("%1 GiB").arg(locale.toString(size, 'f', 1)); } } } #if defined Q_OS_WIN #define KPATH_SEPARATOR ';' // #define KDIR_SEPARATOR '\\' /* faster than QDir::separator() */ #else #define KPATH_SEPARATOR ':' // #define KDIR_SEPARATOR '/' /* faster than QDir::separator() */ #endif static inline QString equalizePath(QString &str) { #ifdef Q_OS_WIN // filter pathes through QFileInfo to have always // the same case for drive letters QFileInfo f(str); if (f.isAbsolute()) return f.absoluteFilePath(); else #endif return str; } static void tokenize(QStringList &tokens, const QString &str, const QString &delim) { const int len = str.length(); QString token; for(int index = 0; index < len; index++) { if (delim.contains(str[index])) { tokens.append(equalizePath(token)); token.clear(); } else { token += str[index]; } } if (!token.isEmpty()) { tokens.append(equalizePath(token)); } } #ifdef Q_OS_WIN static QStringList executableExtensions() { QStringList ret = QString::fromLocal8Bit(qgetenv("PATHEXT")).split(QLatin1Char(';')); if (!ret.contains(QLatin1String(".exe"), Qt::CaseInsensitive)) { // If %PATHEXT% does not contain .exe, it is either empty, malformed, or distorted in ways that we cannot support, anyway. ret.clear(); ret << QLatin1String(".exe") << QLatin1String(".com") << QLatin1String(".bat") << QLatin1String(".cmd"); } return ret; } #endif static QStringList systemPaths(const QString &pstr) { QStringList tokens; QString p = pstr; if( p.isEmpty() ) { p = QString::fromLocal8Bit( qgetenv( "PATH" ) ); } QString delimiters(QLatin1Char(KPATH_SEPARATOR)); delimiters += QLatin1Char('\b'); tokenize( tokens, p, delimiters ); QStringList exePaths; // split path using : or \b as delimiters for( int i = 0; i < tokens.count(); i++ ) { exePaths << /*KShell::tildeExpand(*/ tokens[ i ] /*)*/; // TODO } return exePaths; } #ifdef Q_OS_MAC static QString getBundle(const QString &path) { //kDebug(180) << "getBundle(" << path << ", " << ignore << ") called"; QFileInfo info; QString bundle = path; bundle += QLatin1String(".app/Contents/MacOS/") + bundle.section(QLatin1Char('/'), -1); info.setFile( bundle ); FILE *file; if ((file = fopen(info.absoluteFilePath().toUtf8().constData(), "r"))) { fclose(file); struct stat _stat; if ((stat(info.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( _stat.st_mode & S_IXUSR ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { //kDebug(180) << "getBundle(): returning " << bundle; return bundle; } } } return QString(); } #endif static QString checkExecutable( const QString& path ) { #ifdef Q_OS_MAC QString bundle = getBundle( path ); if ( !bundle.isEmpty() ) { //kDebug(180) << "findExe(): returning " << bundle; return bundle; } #endif QFileInfo info( path ); QFileInfo orig = info; #if defined(Q_OS_DARWIN) || defined(Q_OS_MAC) FILE *file; if ((file = fopen(orig.absoluteFilePath().toUtf8().constData(), "r"))) { fclose(file); struct stat _stat; if ((stat(orig.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( _stat.st_mode & S_IXUSR ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { orig.makeAbsolute(); return orig.filePath(); } } } return QString(); #else if( info.exists() && info.isSymLink() ) info = QFileInfo( info.canonicalFilePath() ); if( info.exists() && info.isExecutable() && info.isFile() ) { // return absolute path, but without symlinks resolved in order to prevent // problems with executables that work differently depending on name they are // run as (for example gunzip) orig.makeAbsolute(); return orig.filePath(); } //kDebug(180) << "checkExecutable(): failed, returning empty string"; return QString(); #endif } QString Utils::findExe(const QString &appname, const QString &pstr) { #ifdef Q_OS_WIN QStringList executable_extensions = executableExtensions(); if (!executable_extensions.contains(appname.section(QLatin1Char('.'), -1, -1, QString::SectionIncludeLeadingSep), Qt::CaseInsensitive)) { QString found_exe; foreach (const QString& extension, executable_extensions) { found_exe = findExe(appname + extension, pstr); if (!found_exe.isEmpty()) { return found_exe; } } return QString(); } #endif const QStringList exePaths = systemPaths( pstr ); for (QStringList::ConstIterator it = exePaths.begin(); it != exePaths.end(); ++it) { QString p = (*it) + QLatin1Char('/'); p += appname; QString result = checkExecutable(p); if (!result.isEmpty()) { return result; } } return QString(); } // Copied from KDE... END QString Utils::formatDuration(const quint32 totalseconds) { //Get the days,hours,minutes and seconds out of the total seconds quint32 days = totalseconds / 86400; quint32 rest = totalseconds - (days * 86400); quint32 hours = rest / 3600; rest = rest - (hours * 3600); quint32 minutes = rest / 60; quint32 seconds = rest - (minutes * 60); //Convert hour,minutes and seconds to a QTime for easier parsing QTime time(hours, minutes, seconds); return 0==days ? time.toString("h:mm:ss") : QString("%1:%2").arg(days).arg(time.toString("hh:mm:ss")); } QString Utils::formatTime(const quint32 seconds, bool zeroIsUnknown) { if (0==seconds && zeroIsUnknown) { return QObject::tr("Unknown"); } static const quint32 constHour=60*60; if (seconds>constHour) { return Utils::formatDuration(seconds); } QString result(QString::number(floor(seconds / 60.0))+QChar(':')); if (seconds % 60 < 10) { result += "0"; } return result+QString::number(seconds % 60); } QString Utils::cleanPath(const QString &p) { QString path(p); while (path.contains("//")) { path.replace("//", constDirSepStr); } return fixPath(path); } static QString userDir(const QString &mainDir, const QString &sub, bool create) { QString dir=mainDir; if (!sub.isEmpty()) { dir+=sub; } dir=Utils::cleanPath(dir); QDir d(dir); return d.exists() || (create && d.mkpath(dir)) ? dir : QString(); } QString Utils::dataDir(const QString &sub, bool create) { #if defined Q_OS_WIN || defined Q_OS_MAC return userDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)+constDirSep, sub, create); #else static QString location; if (location.isEmpty()) { location=QStandardPaths::writableLocation(QStandardPaths::DataLocation); if (QCoreApplication::organizationName()==QCoreApplication::applicationName()) { location=location.replace(QCoreApplication::organizationName()+Utils::constDirSep+QCoreApplication::applicationName(), QCoreApplication::applicationName()); } } return userDir(location+constDirSep, sub, create); #endif } QString Utils::cacheDir(const QString &sub, bool create) { #if defined Q_OS_WIN || defined Q_OS_MAC return userDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+constDirSep, sub, create); #else static QString location; if (location.isEmpty()) { location=QStandardPaths::writableLocation(QStandardPaths::CacheLocation); if (QCoreApplication::organizationName()==QCoreApplication::applicationName()) { location=location.replace(QCoreApplication::organizationName()+Utils::constDirSep+QCoreApplication::applicationName(), QCoreApplication::applicationName()); } } return userDir(location+constDirSep, sub, create); #endif } QString Utils::systemDir(const QString &sub) { #if defined Q_OS_WIN return fixPath(QCoreApplication::applicationDirPath())+(sub.isEmpty() ? QString() : (sub+constDirSep)); #elif defined Q_OS_MAC return fixPath(QCoreApplication::applicationDirPath())+QLatin1String("../Resources/")+(sub.isEmpty() ? QString() : (sub+constDirSep)); #else return fixPath(QString(SHARE_INSTALL_PREFIX"/")+QCoreApplication::applicationName()+constDirSep+(sub.isEmpty() ? QString() : sub)); #endif } QString Utils::helper(const QString &app) { #if defined Q_OS_WIN return fixPath(QCoreApplication::applicationDirPath())+app+QLatin1String(".exe"); #elif defined Q_OS_MAC return fixPath(QCoreApplication::applicationDirPath())+app; #else return QString(INSTALL_PREFIX "/" LINUX_LIB_DIR "/")+QCoreApplication::applicationName()+constDirSep+app; #endif } bool Utils::moveFile(const QString &from, const QString &to) { return !from.isEmpty() && !to.isEmpty() && from!=to && QFile::exists(from) && !QFile::exists(to) && QFile::rename(from, to); } void Utils::moveDir(const QString &from, const QString &to) { if (from.isEmpty() || to.isEmpty() || from==to) { return; } QDir f(from); if (!f.exists()) { return; } QDir t(to); if (!t.exists()) { return; } QFileInfoList files=f.entryInfoList(QStringList() << "*", QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); foreach (const QFileInfo &file, files) { if (file.isDir()) { QString dest=to+file.fileName()+constDirSep; if (!QDir(dest).exists()) { t.mkdir(file.fileName()); } moveDir(from+file.fileName()+constDirSep, dest); } else { QFile::rename(from+file.fileName(), to+file.fileName()); } } f.cdUp(); f.rmdir(from); } void Utils::clearOldCache(const QString &sub, int maxAge) { if (sub.isEmpty()) { return; } QString d=cacheDir(sub, false); if (d.isEmpty()) { return; } QDir dir(d); if (dir.exists()) { QFileInfoList files=dir.entryInfoList(QDir::Files|QDir::NoDotAndDotDot); if (files.count()) { QDateTime now=QDateTime::currentDateTime(); foreach (const QFileInfo &f, files) { if (f.lastModified().daysTo(now)>maxAge) { QFile::remove(f.absoluteFilePath()); } } } } } void Utils::touchFile(const QString &fileName) { ::utime(QFile::encodeName(fileName).constData(), 0); } double Utils::smallFontFactor(const QFont &f) { double sz=f.pointSizeF(); if (sz<=8.5) { return 1.0; } if (sz<=9.0) { return 0.9; } return 0.85; } QFont Utils::smallFont(QFont f) { f.setPointSizeF(f.pointSizeF()*smallFontFactor(f)); return f; } int Utils::layoutSpacing(QWidget *w) { int spacing=(w ? w->style() : qApp->style())->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical); if (spacing<0) { spacing=scaleForDpi(4); } return spacing; } double Utils::screenDpiScale() { static double scaleFactor=-1.0; if (scaleFactor<0) { QWidget *dw=QApplication::desktop(); if (!dw) { return 1.0; } scaleFactor=dw->logicalDpiX()>120 ? qMin(qMax(dw->logicalDpiX()/96.0, 1.0), 4.0) : 1.0; } return scaleFactor; } bool Utils::limitedHeight(QWidget *w) { static bool init=false; static bool limited=false; if (!init) { limited=!qgetenv("CANTATA_NETBOOK").isEmpty(); if (!limited) { QDesktopWidget *dw=QApplication::desktop(); if (dw) { limited=dw->availableGeometry(w).size().height()<=800; } } } return limited; } void Utils::resizeWindow(QWidget *w, bool preserveWidth, bool preserveHeight) { QWidget *window=w ? w->window() : 0; if (window) { QSize was=window->size(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); window->setMinimumSize(QSize(0, 0)); window->adjustSize(); QSize now=window->size(); window->setMinimumSize(now); if (preserveWidth && preserveHeight) { window->resize(qMax(was.width(), now.width()), qMax(was.height(), now.height())); } else if (preserveWidth) { window->resize(qMax(was.width(), now.width()), now.height()); } else if (preserveHeight) { window->resize(now.width(), qMax(was.height(), now.height())); } } } Utils::Desktop Utils::currentDe() { #if !defined Q_OS_WIN && !defined Q_OS_MAC static int de=-1; if (-1==de) { de=Other; QSet desktop=qgetenv("XDG_CURRENT_DESKTOP").toLower().split(':').toSet(); if (desktop.contains("unity")) { de=Unity; } else if (desktop.contains("kde")) { de=KDE; } else if (desktop.contains("gnome") || desktop.contains("pantheon")) { de=desktop.contains("ubuntu") ? Ubuntu_Gnome : Gnome; } else { QByteArray kde=qgetenv("KDE_FULL_SESSION"); if ("true"==kde) { de=KDE; } } } return (Utils::Desktop)de; #endif return Other; } bool Utils::useSystemTray() { #if defined Q_OS_MAC return false; #elif defined Q_OS_WIN return true; #elif QT_QTDBUS_FOUND return QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.StatusNotifierWatcher"); #else return false; #endif } QPainterPath Utils::buildPath(const QRectF &r, double radius) { QPainterPath path; double diameter(radius*2); path.moveTo(r.x()+r.width(), r.y()+r.height()-radius); path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90); path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90); path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90); path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90); return path; } QColor Utils::clampColor(const QColor &col) { static const int constMin=64; static const int constMax=240; if (col.value()constMax) { return QColor(constMax, constMax, constMax); } return col; } QColor Utils::monoIconColor() { return clampColor(QApplication::palette().color(QPalette::Active, QPalette::WindowText)); } #ifdef Q_OS_WIN // This is down here, because windows.h includes ALL windows stuff - and we get conflicts with MessageBox :-( #include #endif void Utils::raiseWindow(QWidget *w) { if (!w) { return; } #ifdef Q_OS_WIN ::SetWindowPos(reinterpret_cast(w->effectiveWinId()), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); ::SetWindowPos(reinterpret_cast(w->effectiveWinId()), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); #elif !defined Q_OS_WIN bool wasHidden=w->isHidden(); #endif w->raise(); w->showNormal(); w->activateWindow(); #ifdef Q_OS_MAC w->raise(); #endif #if !defined Q_OS_WIN && !defined Q_OS_MAC // This section seems to be required for compiz, so that MPRIS.Raise actually shows the window, and not just highlight launcher. QString wmctrl=Utils::findExe(QLatin1String("wmctrl")); if (!wmctrl.isEmpty()) { if (wasHidden) { QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } QProcess::execute(wmctrl, QStringList() << QLatin1String("-i") << QLatin1String("-a") << QString::number(w->effectiveWinId())); } #endif } cantata-2.2.0/support/utils.h000066400000000000000000000115161316350454000161600ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef UTILS_H #define UTILS_H #include "thread.h" #include #include #include #include #include #include #include #include #include #include class QString; class QWidget; class QUrl; class QRectF; namespace Utils { extern const QLatin1Char constDirSep; extern const QLatin1String constDirSepStr; extern const char * constDirSepCharStr; inline bool equal(double d1, double d2, double precision=0.0001) { return (fabs(d1 - d2) < precision); } inline int random(int max=0) { return max ? (qrand()%max) : qrand(); } inline void initRand() { QTime time = QTime::currentTime(); qsrand((time.second() * 1000) + (time.msec())); } extern QString fixPath(const QString &d, bool ensureEndsInSlash=true); #ifdef Q_OS_WIN inline QString homeToTilda(const QString &s) { return s; } inline QString tildaToHome(const QString &s) { return s; } #else extern QString homeToTilda(const QString &s); extern QString tildaToHome(const QString &s); #endif extern QString getDir(const QString &file); extern QString getFile(const QString &file); extern QString changeExtension(const QString &file, const QString &extension); extern bool isDirReadable(const QString &dir); inline void msleep(int msecs) { Thread::msleep(msecs); } inline void sleep() { msleep(100); } extern QString strippedText(QString s); extern QString stripAcceleratorMarkers(QString label); // Convert path to a format suitable fo rUI - e.g. use native separators, and remove any trailing separator extern QString convertPathForDisplay(const QString &dir, bool isFolder=true); // Convert path from a UI field - convert to / separators, and add a trailing separator (if isFolder) extern QString convertPathFromDisplay(const QString &dir, bool isFolder=true); #ifndef Q_OS_WIN extern gid_t getGroupId(const char *groupName="users"); // Return 0 if user is not in group, otherwise returns group ID #endif extern void setFilePerms(const QString &file, const char *groupName="users"); extern bool makeDir(const QString &dir, int mode); extern bool createWorldReadableDir(const QString &dir, const QString &base, const char *groupName="users"); extern QString findExe(const QString &appname, const QString &pathstr=QString()); extern QString formatByteSize(double size); inline QString formatNumber(double number, int precision) { return QString::number(number, 'f', precision); } extern QString formatDuration(const quint32 totalseconds); extern QString formatTime(const quint32 seconds, bool zeroIsUnknown=false); extern QString cleanPath(const QString &p); extern QString dataDir(const QString &sub=QString(), bool create=false); extern QString cacheDir(const QString &sub=QString(), bool create=true); extern QString systemDir(const QString &sub); extern QString helper(const QString &app); extern bool moveFile(const QString &from, const QString &to); extern void moveDir(const QString &from, const QString &to); extern void clearOldCache(const QString &sub, int maxAge); extern void touchFile(const QString &fileName); extern double smallFontFactor(const QFont &f); extern QFont smallFont(QFont f); extern int layoutSpacing(QWidget *w); extern double screenDpiScale(); inline bool isHighDpi() { return screenDpiScale()>1.35; } inline int scaleForDpi(int v) { return qRound(screenDpiScale()*v); } extern bool limitedHeight(QWidget *w); extern void resizeWindow(QWidget *w, bool preserveWidth=true, bool preserveHeight=true); extern void raiseWindow(QWidget *w); enum Desktop { KDE, Gnome, Ubuntu_Gnome, Unity, Other }; extern Desktop currentDe(); extern bool useSystemTray(); extern QPainterPath buildPath(const QRectF &r, double radius); extern QColor clampColor(const QColor &col); extern QColor monoIconColor(); } #endif cantata-2.2.0/support/windowmanager.cpp000066400000000000000000000433441316350454000202210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ // Copied from oxygenwindowmanager.cpp svnversion: 1139230 ////////////////////////////////////////////////////////////////////////////// // oxygenwindowmanager.cpp // pass some window mouse press/release/move event actions to window manager // ------------------- // // Copyright (c) 2010 Hugo Pereira Da Costa // // Largely inspired from BeSpin style // Copyright (C) 2007 Thomas Luebking // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// #include "windowmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline bool isToolBar(QWidget *w) { return qobject_cast(w) || 0==strcmp(w->metaObject()->className(), "ToolBar"); } static inline void addEventFilter(QObject *object, QObject *filter) { object->removeEventFilter(filter); object->installEventFilter(filter); } WindowManager::WindowManager(QObject *parent) : QObject(parent) , _useWMMoveResize(false) , _dragMode(WM_DRAG_NONE) , _dragDistance(QApplication::startDragDistance()) , _dragDelay(QApplication::startDragTime()) , _dragAboutToStart(false) , _dragInProgress(false) , _locked(false) #ifndef Q_OS_MAC , _cursorOverride(false) #endif { // install application wise event filter _appEventFilter = new AppEventFilter(this); qApp->installEventFilter(_appEventFilter); } void WindowManager::initialize(int windowDrag) { setDragMode(windowDrag); setDragDelay(QApplication::startDragTime()); } void WindowManager::registerWidgetAndChildren(QWidget *w) { QObjectList children=w->children(); foreach (QObject *o, children) { if (qobject_cast(o)) { registerWidgetAndChildren((QWidget *)o); } } registerWidget(w); } void WindowManager::registerWidget(QWidget *widget) { if (isBlackListed(widget)) { addEventFilter(widget, this); } else if (isDragable(widget)) { addEventFilter(widget, this); } } void WindowManager::unregisterWidget(QWidget *widget) { if (widget) { widget->removeEventFilter(this); } } bool WindowManager::eventFilter(QObject *object, QEvent *event) { if (!enabled()) { return false; } switch (event->type()) { case QEvent::MouseButtonPress: return mousePressEvent(object, event); break; case QEvent::MouseMove: if (object == _target.data()) { return mouseMoveEvent(object, event); } break; case QEvent::MouseButtonRelease: if (_target) { return mouseReleaseEvent(object, event); } break; default: break; } return false; } void WindowManager::timerEvent(QTimerEvent *event) { if (event->timerId() == _dragTimer.timerId()) { _dragTimer.stop(); if (_target) { startDrag(_target.data(), _globalDragPoint); } } else { return QObject::timerEvent(event); } } bool WindowManager::mousePressEvent(QObject *object, QEvent *event) { // cast event and check buttons/modifiers QMouseEvent *mouseEvent = static_cast(event); if (!(Qt::NoModifier==mouseEvent->modifiers() && Qt::LeftButton==mouseEvent->button())) { return false; } // check lock if (isLocked()) { return false; } else { setLocked(true); } // cast to widget QWidget *widget = static_cast(object); // check if widget can be dragged from current position if (isBlackListed(widget) || !canDrag(widget)) { return false; } // retrieve widget's child at event position QPoint position(mouseEvent->pos()); QWidget *child = widget->childAt(position); if (!canDrag(widget, child, position)) { return false; } // save target and drag point _target = widget; _dragPoint = position; _globalDragPoint = mouseEvent->globalPos(); _dragAboutToStart = true; // send a move event to the current child with same position // if received, it is caught to actually start the drag QPoint localPoint(_dragPoint); if (child) { localPoint = child->mapFrom(widget, localPoint); } else { child = widget; } QMouseEvent localMouseEvent(QEvent::MouseMove, localPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); qApp->sendEvent(child, &localMouseEvent); // never eat event return false; } bool WindowManager::mouseMoveEvent(QObject *object, QEvent *event) { Q_UNUSED(object) // stop timer if (_dragTimer.isActive()){ _dragTimer.stop(); } // cast event and check drag distance QMouseEvent *mouseEvent = static_cast(event); if (!_dragInProgress) { if (_dragAboutToStart) { if (mouseEvent->globalPos() == _globalDragPoint) { // start timer, _dragAboutToStart = false; if (_dragTimer.isActive()) { _dragTimer.stop(); } _dragTimer.start(_dragDelay, this); } else { resetDrag(); } } else if (QPoint(mouseEvent->globalPos() - _globalDragPoint).manhattanLength() >= _dragDistance) { _dragTimer.start(0, this); } return true; } else if (!useWMMoveResize()) { // use QWidget::move for the grabbing /* this works only if the sending object and the target are identical */ QWidget *window(_target.data()->window()); window->move(window->pos() + mouseEvent->pos() - _dragPoint); return true; } else { return false; } } bool WindowManager::mouseReleaseEvent(QObject *object, QEvent *event) { Q_UNUSED(object) Q_UNUSED(event) resetDrag(); return false; } bool WindowManager::isDragable(QWidget *widget) { // check widget if (!widget) { return false; } // accepted default types if ((qobject_cast(widget) && widget->isWindow()) || (qobject_cast(widget) && widget->isWindow()) || qobject_cast(widget)) { return true; } // more accepted types, provided they are not dock widget titles if ((qobject_cast(widget) || qobject_cast(widget) || qobject_cast(widget) || isToolBar(widget)) && !isDockWidgetTitle(widget)) { return true; } // flat toolbuttons if (QToolButton *toolButton = qobject_cast(widget)) { if (toolButton->autoRaise()) { return true; } } // viewports /* one needs to check that 1/ the widget parent is a scrollarea 2/ it matches its parent viewport 3/ the parent is not blacklisted */ if (QListView *listView = qobject_cast(widget->parentWidget())) { if (listView->viewport() == widget && !isBlackListed(listView)) { return true; } } if (QTreeView *treeView = qobject_cast(widget->parentWidget())) { if (treeView->viewport() == widget && !isBlackListed(treeView)) { return true; } } /* catch labels in status bars. this is because of kstatusbar who captures buttonPress/release events */ if (QLabel *label = qobject_cast(widget)) { if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse)) { return false; } QWidget *parent = label->parentWidget(); while (parent) { if (qobject_cast(parent)) { return true; } parent = parent->parentWidget(); } } return false; } bool WindowManager::isBlackListed(QWidget *widget) { QVariant propertyValue(widget->property("_kde_no_window_grab")); return propertyValue.isValid() && propertyValue.toBool(); } bool WindowManager::canDrag(QWidget *widget) { // check if enabled if (!enabled()) { return false; } // assume isDragable widget is already passed // check some special cases where drag should not be effective // check mouse grabber if (QWidget::mouseGrabber()) { return false; } /* check cursor shape. Assume that a changed cursor means that some action is in progress and should prevent the drag */ if (Qt::ArrowCursor!=widget->cursor().shape()) { return false; } // accept return true; } bool WindowManager::canDrag(QWidget *widget, QWidget *child, const QPoint &position) { // retrieve child at given position and check cursor again if (child && Qt::ArrowCursor!=child->cursor().shape()) { return false; } /* check against children from which drag should never be enabled, even if mousePress/Move has been passed to the parent */ if (child && (qobject_cast(child) || qobject_cast(child))) { return false; } // tool buttons if (QToolButton *toolButton = qobject_cast(widget)) { if (dragMode() < WM_DRAG_ALL && !isToolBar(widget->parentWidget())) { return false; } return toolButton->autoRaise() && !toolButton->isEnabled(); } // check menubar if (QMenuBar *menuBar = qobject_cast(widget)) { // check if there is an active action if (menuBar->activeAction() && menuBar->activeAction()->isEnabled()) { return false; } // check if action at position exists and is enabled if (QAction *action = menuBar->actionAt(position)) { if (action->isSeparator()) { return true; } if (action->isEnabled()) { return false; } } // return true in all other cases return true; } bool toolbar=isToolBar(widget); if (dragMode() < WM_DRAG_MENU_AND_TOOLBAR && toolbar) { return false; } /* in MINIMAL mode, anything that has not been already accepted and does not come from a toolbar is rejected */ if (dragMode() < WM_DRAG_ALL) { return toolbar; } /* following checks are relevant only for WD_FULL mode */ // tabbar. Make sure no tab is under the cursor if (QTabBar *tabBar = qobject_cast(widget)) { return -1==tabBar->tabAt(position); } /* check groupboxes prevent drag if unchecking grouboxes */ if (QGroupBox *groupBox = qobject_cast(widget)) { // non checkable group boxes are always ok if (!groupBox->isCheckable()) { return true; } // gather options to retrieve checkbox subcontrol rect QStyleOptionGroupBox opt; opt.initFrom(groupBox); if (groupBox->isFlat()) { opt.features |= QStyleOptionFrame::Flat; } opt.lineWidth = 1; opt.midLineWidth = 0; opt.text = groupBox->title(); opt.textAlignment = groupBox->alignment(); opt.subControls = (QStyle::SC_GroupBoxFrame | QStyle::SC_GroupBoxCheckBox); if (!groupBox->title().isEmpty()) { opt.subControls |= QStyle::SC_GroupBoxLabel; } opt.state |= (groupBox->isChecked() ? QStyle::State_On : QStyle::State_Off); // check against groupbox checkbox if (groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxCheckBox, groupBox).contains(position)) { return false; } // check against groupbox label if (!groupBox->title().isEmpty() && groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxLabel, groupBox).contains(position)) { return false; } return true; } // labels if (QLabel *label = qobject_cast(widget)) { if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse)) { return false; } } // abstract item views QAbstractItemView *itemView(NULL); if ((itemView = qobject_cast(widget->parentWidget())) || (itemView = qobject_cast(widget->parentWidget()))) { if (widget == itemView->viewport()) { // QListView if (QFrame::NoFrame!=itemView->frameShape()) { return false; } else if (QAbstractItemView::NoSelection!=itemView->selectionMode() && QAbstractItemView::SingleSelection!=itemView->selectionMode() && itemView->model() && itemView->model()->rowCount()) { return false; } else if (itemView->model() && itemView->indexAt(position).isValid()) { return false; } } } else if ((itemView = qobject_cast(widget->parentWidget()))) { if (widget == itemView->viewport()) { // QAbstractItemView if (QFrame::NoFrame!=itemView->frameShape()) { return false; } else if (itemView->indexAt(position).isValid()) { return false; } } } return true; } void WindowManager::resetDrag(void) { #ifndef Q_OS_MAC if ((!useWMMoveResize()) && _target && _cursorOverride) { qApp->restoreOverrideCursor(); _cursorOverride = false; } #endif _target.clear(); if (_dragTimer.isActive()) { _dragTimer.stop(); } _dragPoint = QPoint(); _globalDragPoint = QPoint(); _dragAboutToStart = false; _dragInProgress = false; } void WindowManager::startDrag(QWidget *widget, const QPoint& position) { if (!(enabled() && widget)) { return; } if (QWidget::mouseGrabber()) { return; } // ungrab pointer if (useWMMoveResize()) { Q_UNUSED(position) } #ifndef Q_OS_MAC if (!useWMMoveResize() && !_cursorOverride) { qApp->setOverrideCursor(Qt::DragMoveCursor); _cursorOverride = true; } #endif _dragInProgress = true; return; } bool WindowManager::supportWMMoveResize(void) const { return false; } bool WindowManager::isDockWidgetTitle(const QWidget *widget) const { if (!widget) { return false; } if (const QDockWidget *dockWidget = qobject_cast(widget->parent())) { return widget == dockWidget->titleBarWidget(); } else { return false; } } bool WindowManager::AppEventFilter::eventFilter(QObject *object, QEvent *event) { if (QEvent::MouseButtonRelease==event->type()) { // stop drag timer if (_parent->_dragTimer.isActive()) { _parent->resetDrag(); } // unlock if (_parent->isLocked()) { _parent->setLocked(false); } } if (!_parent->enabled()) { return false; } /* if a drag is in progress, the widget will not receive any event we trigger on the first MouseMove or MousePress events that are received by any widget in the application to detect that the drag is finished */ if (_parent->useWMMoveResize() && _parent->_dragInProgress && _parent->_target && (QEvent::MouseMove==event->type() || QEvent::MouseButtonPress==event->type())) { return appMouseEvent(object, event); } return false; } bool WindowManager::AppEventFilter::appMouseEvent(QObject *object, QEvent *event) { Q_UNUSED(object) // store target window (see later) QWidget *window(_parent->_target.data()->window()); /* post some mouseRelease event to the target, in order to counter balance the mouse press that triggered the drag. Note that it triggers a resetDrag */ QMouseEvent mouseEvent(QEvent::MouseButtonRelease, _parent->_dragPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); qApp->sendEvent(_parent->_target.data(), &mouseEvent); if (QEvent::MouseMove==event->type()) { /* HACK: quickly move the main cursor out of the window and back this is needed to get the focus right for the window children the origin of this issue is unknown at the moment */ const QPoint cursor = QCursor::pos(); QCursor::setPos(window->mapToGlobal(window->rect().topRight()) + QPoint(1, 0)); QCursor::setPos(cursor); } return true; } cantata-2.2.0/support/windowmanager.h000066400000000000000000000127111316350454000176600ustar00rootroot00000000000000#ifndef __WINDOW_MANAGER_H__ #define __WINDOW_MANAGER_H__ /* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ // Copied from oxygenwindowmanager.h svnversion: 1137195 ////////////////////////////////////////////////////////////////////////////// // oxygenwindowmanager.h // pass some window mouse press/release/move event actions to window manager // ------------------- // // Copyright (c) 2010 Hugo Pereira Da Costa // // Largely inspired from BeSpin style // Copyright (C) 2007 Thomas Luebking // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include class WindowManager: public QObject { Q_OBJECT public: enum DragMode { WM_DRAG_NONE = 0, WM_DRAG_MENUBAR = 1, WM_DRAG_MENU_AND_TOOLBAR = 2, WM_DRAG_ALL = 3 }; explicit WindowManager(QObject *); virtual ~WindowManager() { } void initialize(int windowDrag); void registerWidgetAndChildren(QWidget *w); void registerWidget(QWidget *); void unregisterWidget(QWidget *); virtual bool eventFilter(QObject *, QEvent *); protected: //! timer event, used to start drag if button is pressed for a long enough time */ void timerEvent(QTimerEvent *); bool mousePressEvent(QObject *, QEvent *); bool mouseMoveEvent(QObject *, QEvent *); bool mouseReleaseEvent(QObject *, QEvent *); bool enabled(void) const { return WM_DRAG_NONE!=_dragMode; } //! returns true if window manager is used for moving bool useWMMoveResize(void) const { return supportWMMoveResize() && _useWMMoveResize; } //! use window manager for moving, when available void setUseWMMoveResize(bool value) { _useWMMoveResize = value; } int dragMode(void) const { return _dragMode; } void setDragMode(int value) { _dragMode = value; } //! drag distance (pixels) void setDragDistance(int value) { _dragDistance = value; } //! drag delay (msec) void setDragDelay(int value) { _dragDelay = value; } //! returns true if widget is dragable bool isDragable(QWidget *); //! returns true if widget is dragable bool isBlackListed(QWidget *); //! returns true if drag can be started from current widget bool canDrag(QWidget *); //! returns true if drag can be started from current widget and position /*! child at given position is passed as second argument */ bool canDrag(QWidget *, QWidget *, const QPoint &); //! reset drag void resetDrag(void); //! start drag void startDrag(QWidget *, const QPoint &); //! returns true if window manager is used for moving /*! right now this is true only for X11 */ bool supportWMMoveResize(void) const; //! utility function bool isDockWidgetTitle(const QWidget *) const; void setLocked(bool value) { _locked = value; } bool isLocked(void) const { return _locked; } private: bool _useWMMoveResize; int _dragMode; int _dragDistance; int _dragDelay; //! drag point QPoint _dragPoint; QPoint _globalDragPoint; //! drag timer QBasicTimer _dragTimer; //! target being dragged /*! QWeakPointer is used in case the target gets deleted while drag is in progress */ QPointer _target; //! true if drag is about to start bool _dragAboutToStart; //! true if drag is in progress bool _dragInProgress; //! true if drag is locked bool _locked; #ifndef Q_OS_MAC //! cursor override /*! used to keep track of application cursor being overridden when dragging in non-WM mode */ bool _cursorOverride; #endif // provide application-wise event filter // it us used to unlock dragging and make sure event look is properly restored // after a drag has occurred class AppEventFilter: public QObject { public: AppEventFilter(WindowManager *parent) : QObject(parent), _parent(parent) { } virtual bool eventFilter(QObject *, QEvent *); protected: //! application-wise event. needed to catch end of XMoveResize events */ bool appMouseEvent(QObject *, QEvent *); private: WindowManager *_parent; }; AppEventFilter *_appEventFilter; friend class AppEventFilter; }; #endif cantata-2.2.0/tags/000077500000000000000000000000001316350454000140655ustar00rootroot00000000000000cantata-2.2.0/tags/CMakeLists.txt000066400000000000000000000022261316350454000166270ustar00rootroot00000000000000include_directories(${QTINCLUDES} ${TAGLIB_INCLUDES} ${CMAKE_SOURCE_DIR}) set(CANTATA_TAGS_SRCS main.cpp taghelper.cpp tags.cpp filetyperesolver.cpp ../mpd-interface/song.cpp) set(CANTATA_TAGS_MOC_HDRS taghelper.h) QT5_WRAP_CPP(CANTATA_TAGS_MOC_SRCS ${CANTATA_TAGS_MOC_HDRS}) if (WIN32) set(CMAKE_BUILD_TYPE "Release") ADD_EXECUTABLE(cantata-tags WIN32 ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}) install(TARGETS cantata-tags DESTINATION ${CMAKE_INSTALL_PREFIX}) elseif (APPLE) set(CMAKE_BUILD_TYPE "Release") ADD_EXECUTABLE(cantata-tags ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}) #install(TARGETS cantata-tags DESTINATION ${MACOSX_BUNDLE_APP_DIR}) else () ADD_EXECUTABLE(cantata-tags ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}) install(TARGETS cantata-tags RUNTIME DESTINATION ${LINUX_LIB_DIR}/cantata) endif () add_definitions(-DCANTATA_TAG_SERVER -DCANTATA_NO_UI_FUNCTIONS) target_link_libraries(cantata-tags ${TAGLIB_LIBRARIES} ${QTGUILIBS} ${QTCORELIBS} ${QTNETWORKLIBS}) if (TAGLIB-EXTRAS_FOUND) target_link_libraries(cantata-tags ${TAGLIB-EXTRAS_LIBRARIES}) include_directories(${TAGLIB-EXTRAS_INCLUDES}) endif () cantata-2.2.0/tags/filetyperesolver.cpp000066400000000000000000000123541316350454000202010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /**************************************************************************************** * Copyright (c) 2005 Martin Aumueller * * Copyright (c) 2011 Ralf Engels * * * * 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 . * ****************************************************************************************/ #include "filetyperesolver.h" #include #include #ifdef TAGLIB_EXTRAS_FOUND #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TAGLIB_OPUS_FOUND #include #endif TagLib::File *Meta::Tag::FileTypeResolver::createFile(TagLib::FileName fileName, bool readProperties, TagLib::AudioProperties::ReadStyle propertiesStyle) const { TagLib::File* result = 0; QString fn = QFile::decodeName(fileName); QString suffix = QFileInfo(fn).suffix(); if (suffix == QLatin1String("mp3")) { result = new TagLib::MPEG::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("flac")) { result = new TagLib::FLAC::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("ogg")) { result = new TagLib::Ogg::Vorbis::File(fileName, readProperties, propertiesStyle); if (!result->isValid()) { delete result; result = new TagLib::Ogg::FLAC::File(fileName, readProperties, propertiesStyle); } if (!result->isValid()) { delete result; result = new TagLib::TrueAudio::File(fileName, readProperties, propertiesStyle); } #ifdef TAGLIB_OPUS_FOUND if (!result->isValid()) { delete result; result = new TagLib::Ogg::Opus::File(fileName, readProperties, propertiesStyle); } #endif } else if (suffix == QLatin1String("m4a") || suffix == QLatin1String("m4b") || suffix == QLatin1String("m4p") || suffix == QLatin1String("mp4") /*|| suffix == QLatin1String("m4v") || suffix == QLatin1String("mp4v") */) { result = new TagLib::MP4::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("wav")) { result = new TagLib::RIFF::WAV::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("wma") /*|| suffix == QLatin1String("asf")*/) { result = new TagLib::ASF::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("wvp") || suffix == QLatin1String("wv")) { result = new TagLib::WavPack::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("ape")) { result = new TagLib::APE::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("spx")) { result = new TagLib::TrueAudio::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("tta")) { result = new TagLib::TrueAudio::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("aiff") || suffix == QLatin1String("aif") || suffix == QLatin1String("aifc")) { result = new TagLib::RIFF::AIFF::File(fileName, readProperties, propertiesStyle); } else if (suffix == QLatin1String("mpc") || suffix == QLatin1String("mpp") || suffix == QLatin1String("mp+")) { result = new TagLib::MPC::File(fileName, readProperties, propertiesStyle); } #ifdef TAGLIB_OPUS_FOUND else if (suffix == QLatin1String("opus")) { result = new TagLib::Ogg::Opus::File(fileName, readProperties, propertiesStyle); } #endif if (result && !result->isValid()) { delete result; result = 0; } return result; } cantata-2.2.0/tags/filetyperesolver.h000066400000000000000000000037171316350454000176510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * */ /**************************************************************************************** * Copyright (c) 2005 Martin Aumueller * * Copyright (c) 2011 Ralf Engels * * * * 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 . * ****************************************************************************************/ #ifndef AMAROK_FILETYPERESOLVER_H #define AMAROK_FILETYPERESOLVER_H #include namespace Meta { namespace Tag { class FileTypeResolver : public TagLib::FileRef::FileTypeResolver { TagLib::File *createFile(TagLib::FileName fileName, bool readAudioProperties, TagLib::AudioProperties::ReadStyle audioPropertiesStyle) const; public: virtual ~FileTypeResolver() {} }; } } #endif cantata-2.2.0/tags/main.cpp000066400000000000000000000045441316350454000155240ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "tags.h" #include "taghelper.h" #ifdef Q_OS_WIN #include static long __stdcall exceptionHandler(EXCEPTION_POINTERS *p) { Q_UNUSED(p) ::exit(0); } #endif static QString logFileName; static bool firstMsg=true; static void cantataQtMsgHandler(QtMsgType, const QMessageLogContext &, const QString &msg) { QFile f(logFileName); if (f.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text)) { QTextStream stream(&f); if (firstMsg) { stream << "------------START------------" << endl; firstMsg=false; } stream << QDateTime::currentDateTime().toString(Qt::ISODate).replace("T", " ") << " - " << msg << endl; } } int main(int argc, char *argv[]) { #ifdef Q_OS_WIN // Prevent windows crash dialog from appearing... SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)exceptionHandler); #endif QCoreApplication app(argc, argv); if (3==app.arguments().length() || 4==app.arguments().length()) { if (4==app.arguments().length()) { logFileName=app.arguments().at(3); if (!logFileName.isEmpty()) { qInstallMessageHandler(cantataQtMsgHandler); TagHelper::enableDebug(); Tags::enableDebug(); } } new TagHelper(app.arguments().at(1), app.arguments().at(2).toInt()); return app.exec(); } return 0; } cantata-2.2.0/tags/tag_fixes.xml000066400000000000000000000001341316350454000165560ustar00rootroot00000000000000 cantata-2.2.0/tags/tageditor.cpp000066400000000000000000001272551316350454000165670ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tageditor.h" #include "tags.h" #include "widgets/tagspinbox.h" #include "mpd-interface/mpdconnection.h" #include "gui/settings.h" #include "support/messagebox.h" #include "support/inputdialog.h" #include "trackorganiser.h" #include "mpd-interface/cuefile.h" #include "support/utils.h" #include "support/monoicon.h" #ifdef ENABLE_DEVICES_SUPPORT #include "models/devicesmodel.h" #include "devices/device.h" #endif #include #include #include #include #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; // // NOTE: Cantata does NOT read, and store, comments from MPD. For comment support, Cantata will read these from the // files when the tag editor is opened. // static bool equalTags(const Song &a, const Song &b, bool compareCommon, bool composerSupport, bool commentSupport) { return (compareCommon || a.track==b.track) && a.year==b.year && a.disc==b.disc && a.artist==b.artist && a.genres[0]==b.genres[0] && a.album==b.album && a.albumartist==b.albumartist && (!composerSupport || a.composer()==b.composer()) && (!commentSupport || a.comment()==b.comment()) && (compareCommon || a.title==b.title); } static bool setString(QString &str, const QString &v, bool skipEmpty) { if (!skipEmpty || !v.isEmpty()) { str=v; return true; } return false; } static QString trim(QString str) { str=str.trimmed(); str=str.simplified(); str=str.replace(QLatin1String(" ;"), QLatin1String(";")); str=str.replace(QLatin1String("; "), QLatin1String(";")); return str; } // Split genres back out static void splitGenres(Song &song) { QStringList genres=song.genres[0].split(",", QString::SkipEmptyParts); for (int i=0; iSong::Rating_Max; } static inline bool nullRating(const Song &s) { return nullRating(s.rating); } TagEditor::TagEditor(QWidget *parent, const QList &songs, const QSet &existingArtists, const QSet &existingAlbumArtists, const QSet &existingComposers, const QSet &existingAlbums, const QSet &existingGenres, const QString &udi) : SongDialog(parent) #ifdef ENABLE_DEVICES_SUPPORT , deviceUdi(udi) #endif , currentSongIndex(-1) , updating(false) , haveArtists(false) , haveAlbumArtists(false) , haveComposers(false) , haveComments(false) , haveAlbums(false) , haveGenres(false) , haveDiscs(false) , haveYears(false) , haveRatings(false) , saving(false) , composerSupport(false) , commentSupport(false) , readRatingsAct(0) , writeRatingsAct(0) { iCount++; bool ratingsSupport=false; #ifdef ENABLE_DEVICES_SUPPORT if (deviceUdi.isEmpty()) { baseDir=MPDConnection::self()->getDetails().dir; composerSupport=MPDConnection::self()->composerTagSupported(); commentSupport=MPDConnection::self()->commentTagSupported(); ratingsSupport=MPDConnection::self()->stickersSupported(); } else { Device *dev=getDevice(udi, parentWidget()); if (!dev) { deleteLater(); return; } baseDir=dev->path(); } #else baseDir=MPDConnection::self()->getDetails().dir; composerSupport=MPDConnection::self()->composerTagSupported(); commentSupport=MPDConnection::self()->commentTagSupported(); ratingsSupport=MPDConnection::self()->stickersSupported(); #endif foreach (const Song &s, songs) { if (CueFile::isCue(s.file)) { continue; } Song song(s); song.rating=Song::Rating_Null; if (s.guessed) { song.revertGuessedTags(); } // Store all Genres's in 1st genre song.genres[0]=song.displayGenre(); song.genres[1]=QString(); original.append(song); } if (original.isEmpty()) { deleteLater(); return; } qSort(original); if (!songsOk(original, baseDir, udi.isEmpty())) { return; } QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); if (!ratingsSupport) { REMOVE(ratingWidget); REMOVE(ratingLabel); REMOVE(ratingVarious); REMOVE(ratingNoteLabel); } else { connect(this, SIGNAL(getRating(QString)), MPDConnection::self(), SLOT(getRating(QString))); connect(this, SIGNAL(setRating(QString,quint8)), MPDConnection::self(), SLOT(setRating(QString,quint8))); connect(MPDConnection::self(), SIGNAL(rating(QString,quint8)), this, SLOT(rating(QString,quint8))); ratingWidget->setShowZeroForNull(true); QColor col=palette().color(QPalette::WindowText); ratingVarious->setStyleSheet(QString("QLabel{color:rgba(%1,%2,%3,128);}").arg(col.red()).arg(col.green()).arg(col.blue())); } setMainWidget(mainWidet); ButtonCodes buttons=Ok|Cancel|Reset|User3; if (songs.count()>1) { buttons|=User2|User1; } setButtons(buttons); setCaption(tr("Tags")); if (songs.count()>1) { setButtonGuiItem(User2, StdGuiItem::back(true)); setButtonGuiItem(User1,StdGuiItem::forward(true)); enableButton(User1, false); enableButton(User2, false); } setButtonGuiItem(Ok, StdGuiItem::save()); setButtonGuiItem(User3, GuiItem(tr("Tools"), FontAwesome::magic)); QMenu *toolsMenu=new QMenu(this); toolsMenu->addAction(tr("Apply \"Various Artists\" Workaround"), this, SLOT(applyVa())); toolsMenu->addAction(tr("Revert \"Various Artists\" Workaround"), this, SLOT(revertVa())); toolsMenu->addAction(tr("Set 'Album Artist' from 'Artist'"), this, SLOT(setAlbumArtistFromArtist())); toolsMenu->addAction(tr("Capitalize"), this, SLOT(capitalise())); toolsMenu->addAction(tr("Adjust Track Numbers"), this, SLOT(adjustTrackNumbers())); if (ratingsSupport) { readRatingsAct=toolsMenu->addAction(tr("Read Ratings from File"), this, SLOT(readRatings())); writeRatingsAct=toolsMenu->addAction(tr("Write Ratings to File"), this, SLOT(writeRatings())); readRatingsAct->setEnabled(false); writeRatingsAct->setEnabled(false); } setButtonMenu(User3, toolsMenu, InstantPopup); enableButton(Ok, false); enableButton(Reset, false); setAttribute(Qt::WA_DeleteOnClose); QStringList strings=existingArtists.toList(); strings.sort(); artist->clear(); artist->insertItems(0, strings); strings=existingAlbumArtists.toList(); strings.sort(); albumArtist->clear(); albumArtist->insertItems(0, strings); if (composerSupport) { strings=existingComposers.toList(); strings.sort(); composer->clear(); composer->insertItems(0, strings); } else { REMOVE(composer) REMOVE(composerLabel) } if (commentSupport) { comment->clear(); composer->insertItems(0, strings); } else { REMOVE(comment) REMOVE(commentLabel) } strings=existingAlbums.toList(); strings.sort(); album->clear(); album->insertItems(0, strings); strings=existingGenres.toList(); strings.sort(); genre->clear(); genre->insertItems(0, strings); trackName->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); trackName->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); trackName->view()->setTextElideMode(Qt::ElideLeft); if (original.count()>1) { QSet songArtists; QSet songAlbumArtists; QSet songAlbums; QSet songGenres; QSet songComposers; QSet songComments; QSet songYears; QSet songDiscs; foreach (const Song &s, original) { songArtists.insert(s.artist); songAlbumArtists.insert(s.albumartist); songAlbums.insert(s.album); for (int i=0; i1 && songAlbumArtists.count()>1 && songAlbums.count()>1 && songGenres.count()>1 && songYears.count()>1 && songDiscs.count()>1 && (!composerSupport || songComposers.count()>1) && (!commentSupport || songComments.count()>1)) { break; } } Song all; all.file.clear(); all.title.clear(); all.track=0; all.artist=1==songArtists.count() ? *(songArtists.begin()) : QString(); if (1==songComposers.count()) { all.setComposer(*(songComposers.begin())); } all.setComment(1==songComments.count() ? *(songComments.begin()) : QString()); all.albumartist=1==songAlbumArtists.count() ? *(songAlbumArtists.begin()) : QString(); all.album=1==songAlbums.count() ? *(songAlbums.begin()) : QString(); all.genres[0]=1==songGenres.count() ? *(songGenres.begin()) : QString(); all.year=1==songYears.count() ? *(songYears.begin()) : 0; all.disc=1==songDiscs.count() ? *(songDiscs.begin()) : 0; all.rating=Song::Rating_Null; original.prepend(all); artist->setFocus(); haveArtists=songArtists.count()>1; haveAlbumArtists=songAlbumArtists.count()>1; haveAlbums=songAlbums.count()>1; haveGenres=songGenres.count()>1; haveComposers=songComposers.count()>1; haveDiscs=songDiscs.count()>1; haveYears=songYears.count()>1; } else { title->setFocus(); } edited=original; setIndex(0); if (ratingsSupport || commentSupport) { progress->setVisible(true); progress->setRange(0, (original.count()>1 ? (original.count()-1) : 1)*(commentSupport && ratingsSupport ? 2 : 1)); progress->setValue(0); } else { progress->setVisible(false); } bool first=original.count()>1; foreach (const Song &s, original) { if (first) { trackName->insertItem(trackName->count(), tr("All tracks")); first=false; } else { trackName->insertItem(trackName->count(), s.filePath()); if (ratingsSupport) { emit getRating(s.file); } } } connect(title, SIGNAL(textChanged(const QString &)), SLOT(checkChanged())); connect(artist, SIGNAL(activated(int)), SLOT(checkChanged())); connect(artist, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged())); connect(albumArtist, SIGNAL(activated(int)), SLOT(checkChanged())); connect(albumArtist, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged())); if (composerSupport) { connect(composer, SIGNAL(activated(int)), SLOT(checkChanged())); connect(composer, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged())); } if (commentSupport) { connect(comment, SIGNAL(textChanged(const QString &)), SLOT(checkChanged())); } connect(album, SIGNAL(activated(int)), SLOT(checkChanged())); connect(album, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged())); connect(genre, SIGNAL(activated(int)), SLOT(checkChanged())); connect(track, SIGNAL(valueChanged(int)), SLOT(checkChanged())); connect(disc, SIGNAL(valueChanged(int)), SLOT(checkChanged())); connect(genre, SIGNAL(editTextChanged(const QString &)), SLOT(checkChanged())); connect(year, SIGNAL(valueChanged(int)), SLOT(checkChanged())); connect(trackName, SIGNAL(activated(int)), SLOT(setIndex(int))); connect(this, SIGNAL(update()), MPDConnection::self(), SLOT(update())); if (ratingWidget) { connect(ratingWidget, SIGNAL(valueChanged(int)), SLOT(checkRating())); } adjustSize(); int w=Utils::scaleForDpi(600); if (width()lineEdit()->setReadOnly(true); } TagEditor::~TagEditor() { iCount--; } void TagEditor::fillSong(Song &s, bool isAll, bool skipEmpty) const { Song all=original.at(0); bool haveAll=original.count()>1; if (!isAll) { setString(s.title, title->text().trimmed(), skipEmpty); } setString(s.artist, artist->text().trimmed(), skipEmpty && (!haveAll || all.artist.isEmpty())); setString(s.album, album->text().trimmed(), skipEmpty && (!haveAll || all.album.isEmpty())); setString(s.albumartist, albumArtist->text().trimmed(), skipEmpty && (!haveAll || all.albumartist.isEmpty())); if (composerSupport) { QString str; if (setString(str, composer->text().trimmed(), skipEmpty && (!haveAll || all.composer().isEmpty()))) { s.setComposer(str); } } if (commentSupport) { QString str; if (setString(str, comment->text().trimmed(), skipEmpty && (!haveAll || all.comment().isEmpty()))) { s.setComment(str); } } if (!isAll) { s.track=track->value(); } if (!isAll || 0!=disc->value()) { s.disc=disc->value(); } setString(s.genres[0], trim(genre->text()), skipEmpty && (!haveAll || all.genres[0].isEmpty())); if (!isAll || 0!=year->value()) { s.year=year->value(); } if (ratingWidget) { s.rating=ratingWidget->value(); } } void TagEditor::setVariousHint() { if (0==currentSongIndex && original.count()>1) { Song all=original.at(0); artist->setPlaceholderText(all.artist.isEmpty() && haveArtists ? TagSpinBox::variousStr() : QString()); album->setPlaceholderText(all.album.isEmpty() && haveAlbums ? TagSpinBox::variousStr() : QString()); albumArtist->setPlaceholderText(all.albumartist.isEmpty() && haveAlbumArtists ? TagSpinBox::variousStr() : QString()); if (composerSupport) { composer->setPlaceholderText(all.composer().isEmpty() && haveComposers ? TagSpinBox::variousStr() : QString()); } if (commentSupport) { comment->setPlaceholderText(all.comment().isEmpty() && haveComments ? TagSpinBox::variousStr() : QString()); } genre->setPlaceholderText(all.genres[0].isEmpty() && haveGenres ? TagSpinBox::variousStr() : QString()); disc->setVarious(0==all.disc && haveDiscs); year->setVarious(0==all.year && haveYears); if (ratingVarious) { ratingVarious->setVisible(nullRating(all) && haveRatings); } } else { artist->setPlaceholderText(QString()); album->setPlaceholderText(QString()); albumArtist->setPlaceholderText(QString()); if (composerSupport) { composer->setPlaceholderText(QString()); } if (commentSupport) { comment->setPlaceholderText(QString()); } genre->setPlaceholderText(QString()); disc->setVarious(false); year->setVarious(false); if (ratingVarious) { ratingVarious->setVisible(false); } } } void TagEditor::enableOkButton() { enableButton(Ok, (editedIndexes.count()>1) || (1==original.count() && 1==editedIndexes.count()) || (1==editedIndexes.count() && !editedIndexes.contains(0)) ); enableButton(Reset, isButtonEnabled(Ok)); } void TagEditor::setLabelStates() { Song o=original.at(currentSongIndex); Song e=edited.at(currentSongIndex); bool isAll=0==currentSongIndex && original.count()>1; titleLabel->setOn(!isAll && o.title!=e.title); artistLabel->setOn(o.artist!=e.artist); if (composerSupport) { composerLabel->setOn(o.composer()!=e.composer()); } if (commentSupport) { commentLabel->setOn(o.comment()!=e.comment()); } albumArtistLabel->setOn(o.albumartist!=e.albumartist); albumLabel->setOn(o.album!=e.album); trackLabel->setOn(!isAll && o.track!=e.track); discLabel->setOn(o.disc!=e.disc); genreLabel->setOn(o.genres[0]!=e.genres[0]); yearLabel->setOn(o.year!=e.year); if (ratingLabel) { ratingLabel->setOn(o.rating<=Song::Rating_Max && e.rating<=Song::Rating_Max && o.rating!=e.rating); } } void TagEditor::readComments() { bool haveMultiple=original.count()>1; bool updated=false; bool multipleComments=false; QString allComment; for (int i=0; isetValue(progress->value()+1); } if (i && 0==i%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } if (0==i && haveMultiple) { continue; } Song song=original.at(i); QString comment=Tags::readComment(baseDir+song.file); if (!comment.isEmpty()) { song.setComment(comment); original.replace(i, song); haveComments=true; updated=true; if (haveMultiple) { if (allComment.isEmpty()) { allComment=comment; } else if (comment!=allComment) { multipleComments=true; } } } } if (updated) { edited=original; if (haveMultiple && !allComment.isEmpty() && !multipleComments) { edited[0].setComment(allComment); original[0].setComment(allComment); if (0==currentSongIndex) { comment->setText(allComment); } } } controlInitialActionsState(); if (haveMultiple) { setVariousHint(); } else { setSong(original.at(0)); } } void TagEditor::applyVa() { bool isAll=0==currentSongIndex && original.count()>1; if (MessageBox::No==MessageBox::questionYesNo(this, (isAll ? tr("Apply \"Various Artists\" workaround to all tracks?") : tr("Apply \"Various Artists\" workaround?"))+ QLatin1String("

    ")+ tr("This will set 'Album artist' and 'Artist' to " "\"Various Artists\", and set 'Title' to " "\"TrackArtist - TrackTitle\""), tr("Apply \"Various Artists\" Workaround"), StdGuiItem::apply(), StdGuiItem::cancel())) { return; } if (isAll) { updating=true; for (int i=0; i1; if (MessageBox::No==MessageBox::questionYesNo(this, (isAll ? tr("Revert \"Various Artists\" workaround on all tracks?") : tr("Revert \"Various Artists\" workaround"))+ QLatin1String("

    ")+ tr("Where the 'Album artist' is the same as 'Artist' " "and the 'Title' is of the format \"TrackArtist - TrackTitle\", " "'Artist' will be taken from 'Title' and 'Title' itself will be " "set to just the title. e.g.

    " "If 'Title' is \"Wibble - Wobble\", then 'Artist' will be set to " "\"Wibble\" and 'Title' will be set to \"Wobble\"
    "), tr("Revert \"Various Artists\" Workaround"), GuiItem(tr("Revert")), StdGuiItem::cancel())) { return; } if (isAll) { updating=true; QSet artists; for (int i=1; i1; if (MessageBox::No==MessageBox::questionYesNo(this, isAll ? tr("Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for all tracks?") : tr("Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)?"), tr("Album Artist from Artist"), StdGuiItem::apply(), StdGuiItem::cancel())) { return; } if (isAll) { updating=true; for (int i=0; i1; if (MessageBox::No==MessageBox::questionYesNo(this, isAll ? tr("Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) " "of all tracks?") : tr("Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)?"), tr("Capitalize"), GuiItem(tr("Capitalize")), StdGuiItem::cancel())) { return; } if (isAll) { for (int i=0; i1; bool ok=false; int adj=InputDialog::getInteger(tr("Adjust Track Numbers"), isAll ? tr("Adjust the value of each track number by:") : tr("Adjust track number by:"), 0, -500, 500, 1, 10, &ok, this); if (!ok || 0==adj) { return; } if (isAll) { for (int i=1; i1; if (MessageBox::No==MessageBox::questionYesNo(this, isAll ? tr("Read ratings for all tracks from the music files?") : tr("Read rating from music file?"), tr("Ratings"), isAll ? GuiItem(tr("Read Ratings")) : GuiItem(tr("Read Rating")), StdGuiItem::cancel())) { return; } if (isAll) { progress->setVisible(true); progress->setRange(0, original.count()); QStringList updated; for (int i=1; isetValue(i+1); if (i && 0==i%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } Song s=edited.at(i); int r=Tags::readRating(baseDir+s.file); if (r>=0 && r<=Song::Rating_Max && s.rating!=r) { s.rating=r; edited.replace(i, s); updated.append(s.file); editedIndexes.insert(i); updateTrackName(i, true); if (i==currentSongIndex) { setSong(s); } } } progress->setVisible(false); if (!updated.isEmpty()) { MessageBox::informationList(this, tr("Read, and updated, ratings from the following tracks:"), updated); } } else { Song s=edited.at(currentSongIndex); int r=Tags::readRating(baseDir+s.file); if (r>=0 && r<=Song::Rating_Max && s.rating!=r) { s.rating=r; edited.replace(currentSongIndex, s); setSong(s); } } enableOkButton(); } void TagEditor::writeRatings() { bool isAll=0==currentSongIndex && original.count()>1; if (isAll) { for (int i=1; isetVisible(true); progress->setRange(0, edited.count()); QStringList failed; for (int i=1; isetValue(i+1); if (i && 0==i%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } Song s=edited.at(i); if (s.rating<=Song::Rating_Max) { Tags::Update status=Tags::updateRating(baseDir+s.file, s.rating); if (Tags::Update_Failed==status || Tags::Update_BadFile==status){ failed.append(s.file); } } } progress->setVisible(false); if (!failed.isEmpty()) { MessageBox::errorList(this, tr("Failed to write ratings of the following tracks:"), failed); } } else { Song s=edited.at(currentSongIndex); if (s.rating<=Song::Rating_Max) { Tags::Update status=Tags::updateRating(baseDir+s.file, s.rating); if (Tags::Update_Failed==status || Tags::Update_BadFile==status){ MessageBox::error(this, tr("Failed to write rating to music file!")); } } } } void TagEditor::checkChanged() { if (updating) { return; } bool allWasEdited=editedIndexes.contains(0); updateEdited(); bool allEdited=editedIndexes.contains(0); bool isAll=0==currentSongIndex && original.count()>1; if (isAll && (allEdited || allWasEdited)) { int save=currentSongIndex; for (int i=1; i1; if (edited) { if (isAll) { trackName->setItemText(index, tr("All tracks [modified]")); } else { trackName->setItemText(index, tr("%1 [modified]").arg(original.at(index).filePath())); } } else { if (isAll) { trackName->setItemText(index, tr("All tracks")); } else { trackName->setItemText(index, original.at(index).filePath()); } } } void TagEditor::updateEditedStatus(int index) { bool isAll=0==index && original.count()>1; Song s=edited.at(index); if (equalTags(s, original.at(index), isAll, composerSupport, commentSupport) && s.rating==original.at(index).rating) { if (editedIndexes.contains(index)) { editedIndexes.remove(index); updateTrackName(index, false); edited.replace(index, s); } } else { if (!editedIndexes.contains(index)) { editedIndexes.insert(index); updateTrackName(index, true); } edited.replace(index, s); } } void TagEditor::updateEdited(bool isFromAll) { Song s=edited.at(currentSongIndex); bool isAll=0==currentSongIndex && original.count()>1; fillSong(s, isFromAll || isAll, /*isFromAll*/false); if (!isAll && isFromAll && original.count()>1) { Song all=original.at(0); Song o=original.at(currentSongIndex); if (all.artist.isEmpty() && s.artist.isEmpty() && !o.artist.isEmpty()) { s.artist=o.artist; } if (all.albumartist.isEmpty() && s.albumartist.isEmpty() && !o.albumartist.isEmpty()) { s.albumartist=o.albumartist; } if (composerSupport && all.composer().isEmpty() && s.composer().isEmpty() && !o.composer().isEmpty()) { s.setComposer(o.composer()); } if (commentSupport && all.comment().isEmpty() && s.comment().isEmpty() && !o.comment().isEmpty()) { s.setComment(o.comment()); } if (all.album.isEmpty() && s.album.isEmpty() && !o.album.isEmpty()) { s.album=o.album; } if (all.genres[0].isEmpty() && s.genres[0].isEmpty() && !o.genres[0].isEmpty()) { s.genres[0]=o.genres[0]; } } if (equalTags(s, original.at(currentSongIndex), isFromAll || isAll, composerSupport, commentSupport) && s.rating==original.at(currentSongIndex).rating) { if (editedIndexes.contains(currentSongIndex)) { editedIndexes.remove(currentSongIndex); updateTrackName(currentSongIndex, false); edited.replace(currentSongIndex, s); } } else { if (!editedIndexes.contains(currentSongIndex)) { editedIndexes.insert(currentSongIndex); updateTrackName(currentSongIndex, true); } edited.replace(currentSongIndex, s); } } void TagEditor::setSong(const Song &s) { blockSignals(true); title->setText(s.title); artist->setText(s.artist); albumArtist->setText(s.albumartist); if (composerSupport) { composer->setText(s.composer()); } if (commentSupport) { comment->setText(s.comment()); } album->setText(s.album); track->setValue(s.track); disc->setValue(s.disc); genre->setText(s.genres[0]); year->setValue(s.year); if (ratingWidget) { ratingWidget->setValue(s.rating); } blockSignals(false); checkChanged(); } void TagEditor::setIndex(int idx) { if (currentSongIndex==idx || idx>(original.count()-1)) { return; } updating=true; bool haveMultiple=original.count()>1; if (haveMultiple && currentSongIndex>=0) { updateEdited(); } Song s=edited.at(!haveMultiple || idx==0 ? 0 : idx); setSong(s); currentSongIndex=idx; bool isMultiple=haveMultiple && 0==idx; title->setEnabled(!isMultiple); titleLabel->setEnabled(!isMultiple); track->setEnabled(!isMultiple); trackLabel->setEnabled(!isMultiple); if (isMultiple) { title->setText(QString()); track->setValue(0); } if (original.count()>1) { enableButton(User1, !isMultiple && idx<(original.count()-1)); // Next enableButton(User2, !isMultiple && idx>1); // Prev } setVariousHint(); enableOkButton(); trackName->setCurrentIndex(idx); setLabelStates(); updating=false; } void TagEditor::rating(const QString &f, quint8 r) { if (!ratingWidget) { return; } for (int i=original.count()>1 ? 1 : 0; isetValue(progress->value()+1); controlInitialActionsState(); s.rating=r; original.replace(i, s); s=edited.at(i); if (nullRating(s)) { s.rating=r; edited.replace(i, s); } if (i==currentSongIndex && ratingWidget->value()>Song::Rating_Max) { ratingWidget->setValue(r); } } } if (original.count()>1 && !haveRatings) { quint8 rating=Song::Rating_Null; bool first=true; for (int i=1; isetValue(rating); } setVariousHint(); } } void TagEditor::checkRating() { if (!ratingWidget) { return; } checkChanged(); if (original.count()>1 && 0==currentSongIndex) { quint8 rating=Song::Rating_Null; bool first=true; haveRatings=false; for (int i=1; isetValue(rating); } } setVariousHint(); } } bool TagEditor::applyUpdates() { bool skipFirst=original.count()>1; QStringList failed; DeviceOptions opts; QString udi; #ifdef ENABLE_DEVICES_SUPPORT Device * dev=0; if (!deviceUdi.isEmpty()) { dev=getDevice(deviceUdi, this); if (!dev) { return true; } opts=dev->options(); udi=dev->id(); } else #endif opts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); int toSave=editedIndexes.count(); bool renameFiles=false; QList updatedSongs; saving=true; enableButton(Ok, false); enableButton(Cancel, false); enableButton(Reset, false); enableButton(User1, false); enableButton(User2, false); enableButton(User3, false); progress->setVisible(true); progress->setRange(1, toSave); int count=0; bool someTimedout=false; foreach (int idx, editedIndexes) { progress->setValue(progress->value()+1); if (0==count++%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } if (skipFirst && 0==idx) { continue; } Song orig=original.at(idx); Song edit=edited.at(idx); if (ratingWidget && orig.rating!=edit.rating && edit.rating<=Song::Rating_Max) { emit setRating(orig.file, edit.rating); } if (equalTags(orig, edit, false, composerSupport, commentSupport)) { continue; } QString file=orig.filePath(); splitGenres(orig); splitGenres(edit); switch(Tags::update(baseDir+file, orig, edit, -1, commentSupport)) { case Tags::Update_Modified: edit.setComment(QString()); #ifdef ENABLE_DEVICES_SUPPORT if (!deviceUdi.isEmpty()) { if (!dev->updateSong(orig, edit)) { dev->removeSongFromList(orig); dev->addSongToList(edit); } } else #endif { // if (!MusicLibraryModel::self()->updateSong(orig, edit)) { // MusicLibraryModel::self()->removeSongFromList(orig); // MusicLibraryModel::self()->addSongToList(edit); // } } updatedSongs.append(edit); if (!renameFiles && file!=opts.createFilename(edit)) { renameFiles=true; } break; case Tags::Update_Failed: failed.append(file); break; case Tags::Update_BadFile: failed.append(tr("%1 (Corrupt tags?)", "filename (Corrupt tags?)").arg(file)); break; default: break; } } saving=false; if (failed.count()) { MessageBox::errorListEx(this, tr("Failed to update the tags of the following tracks:"), failed); } if (updatedSongs.count()) { // If we call tag-editor, no need to do MPD update - as this will be done from that dialog... if (renameFiles && MessageBox::Yes==MessageBox::questionYesNo(this, tr("Would you also like to rename your song files, so as to match your tags?"), tr("Rename Files"), GuiItem(tr("Rename")), StdGuiItem::cancel())) { TrackOrganiser *dlg=new TrackOrganiser(parentWidget()); dlg->show(updatedSongs, udi, true); } else { #ifdef ENABLE_DEVICES_SUPPORT if (!deviceUdi.isEmpty()) { dev->saveCache(); } else #endif { // MusicLibraryModel::self()->removeCache(); emit update(); } } } return !someTimedout; } void TagEditor::slotButtonClicked(int button) { switch (button) { case Ok: { if (applyUpdates()) { accept(); } break; } case Reset: // Reset if (0==currentSongIndex && original.count()>1) { for (int i=0; idevice(udi); if (!dev) { MessageBox::error(p ? p : this, tr("Device has been removed!")); reject(); return 0; } if (!dev->isConnected()) { MessageBox::error(p ? p : this, tr("Device is not connected.")); reject(); return 0; } if (!dev->isIdle()) { MessageBox::error(p ? p : this, tr("Device is busy?")); reject(); return 0; } return dev; } #endif void TagEditor::closeEvent(QCloseEvent *event) { if (saving) { event->ignore(); } else { Dialog::closeEvent(event); } } void TagEditor::controlInitialActionsState() { if (progress->value()>=progress->maximum()) { progress->setVisible(false); if (readRatingsAct) { readRatingsAct->setEnabled(true); } if (writeRatingsAct) { writeRatingsAct->setEnabled(true); } } } cantata-2.2.0/tags/tageditor.h000066400000000000000000000061761316350454000162320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAG_EDITOR_H #define TAG_EDITOR_H #include "config.h" #include "widgets/songdialog.h" #include "ui_tageditor.h" #include #include #ifdef ENABLE_DEVICES_SUPPORT class Device; #endif class TagEditor : public SongDialog, Ui::TagEditor { Q_OBJECT public: static int instanceCount(); TagEditor(QWidget *parent, const QList &songs, const QSet &existingArtists, const QSet &existingAlbumArtists, const QSet &existingComposers, const QSet &existingAlbums, const QSet &existingGenres, const QString &udi); virtual ~TagEditor(); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void update(); void getRating(const QString &f); void setRating(const QString &f, quint8 r); private: void enableOkButton(); void setLabelStates(); void setVariousHint(); void fillSong(Song &s, bool isAll, bool skipEmpty) const; void slotButtonClicked(int button); void updateTrackName(int index, bool edited); void updateEditedStatus(int index); bool applyUpdates(); #ifdef ENABLE_DEVICES_SUPPORT Device * getDevice(const QString &udi, QWidget *p); #endif void closeEvent(QCloseEvent *event); void controlInitialActionsState(); private Q_SLOTS: void readComments(); void applyVa(); void revertVa(); void setAlbumArtistFromArtist(); void capitalise(); void checkChanged(); void adjustTrackNumbers(); void readRatings(); void writeRatings(); void updateEdited(bool isFromAll=false); void setSong(const Song &s); void setIndex(int idx); void rating(const QString &f, quint8 r); void checkRating(); private: QString baseDir; #ifdef ENABLE_DEVICES_SUPPORT QString deviceUdi; #endif QList original; QList edited; int currentSongIndex; QSet editedIndexes; bool updating; bool haveArtists; bool haveAlbumArtists; bool haveComposers; bool haveComments; bool haveAlbums; bool haveGenres; bool haveDiscs; bool haveYears; bool haveRatings; bool saving; bool composerSupport; bool commentSupport; QAction *readRatingsAct; QAction *writeRatingsAct; }; #endif cantata-2.2.0/tags/tageditor.ui000066400000000000000000000173121316350454000164120ustar00rootroot00000000000000 TagEditor 0 0 517 490 0 0 0 0 QFormLayout::ExpandingFieldsGrow Track: trackName Qt::NoFocus true Title: title Artist: artist Album artist: albumArtist Composer: composer Album: album Track number: track Disc number: disc Genre: genre Year: year Rating: ratingWidget <i>(Various)</i> Comment: comment Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Qt::Vertical QSizePolicy::Expanding 20 2 LineEdit QLineEdit
    support/lineedit.h
    CompletionCombo QComboBox
    widgets/completioncombo.h
    TagSpinBox QSpinBox
    widgets/tagspinbox.h
    StateLabel QLabel
    widgets/statelabel.h
    PlainNoteLabel QLabel
    widgets/notelabel.h
    PlainUrlNoteLabel QLabel
    widgets/notelabel.h
    RatingWidget QWidget
    widgets/ratingwidget.h
    trackName
    cantata-2.2.0/tags/taghelper.cpp000066400000000000000000000123121316350454000165430ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "taghelper.h" #include "tags.h" #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #include #include #endif #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void TagHelper::enableDebug() { debugEnabled=true; } static QString socketName; static void deleteSocket() { // Just in case Cantata has crashed, ensure we delete the socket... if (!socketName.isEmpty()) { QLocalServer::removeServer(socketName); } } TagHelper::TagHelper(const QString &sockName, int parent) : parentPid(parent) , dataSize(0) { socket=new QLocalSocket(this); socket->connectToServer(sockName); connect(socket, SIGNAL(readyRead()), SLOT(dataReady())); connect(socket, SIGNAL(disconnected()), qApp, SLOT(quit())); QTimer *timer=new QTimer(this); timer->setSingleShot(false); timer->start(5000); connect(timer, SIGNAL(timeout()), SLOT(checkParent())); socketName=sockName; atexit(deleteSocket); } TagHelper::~TagHelper() { } void TagHelper::dataReady() { DBUG << dataSize; while (socket->bytesAvailable()) { if (0==dataSize) { QDataStream stream(socket); stream >> dataSize; if (dataSize<=0) { qApp->exit(); return; } } data+=socket->read(dataSize-data.length()); DBUG << data.length() << "/" << dataSize; if (data.length() == dataSize) { process(); } } } void TagHelper::checkParent() { // If parent process (Cantata) has terminated, then we need to exit... #ifdef Q_OS_WIN if (0==OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, parentPid)) { qApp->exit(); } #else if (0!=::kill(parentPid, 0)) { qApp->exit(); } #endif } void TagHelper::process() { QByteArray response; QDataStream inStream(data); QDataStream outStream(&response, QIODevice::WriteOnly); QString request; QString fileName; inStream >> request >> fileName; DBUG << "REQ" << request << fileName; if (QLatin1String("read")==request) { outStream << Tags::read(fileName); } else if (QLatin1String("readImage")==request) { outStream << Tags::readImage(fileName); } else if (QLatin1String("readLyrics")==request) { outStream << Tags::readLyrics(fileName); } else if (QLatin1String("readComment")==request) { outStream << Tags::readComment(fileName); } else if (QLatin1String("updateArtistAndTitle")==request) { Song song; outStream << (int)Tags::updateArtistAndTitle(fileName, song); } else if (QLatin1String("update")==request) { Song from; Song to; int id3Ver; bool saveComment; inStream >> from >> to >> id3Ver >> saveComment; outStream << (int)Tags::update(fileName, from, to, id3Ver, saveComment); } else if (QLatin1String("readReplaygain")==request) { Tags::ReplayGain rg=Tags::readReplaygain(fileName); outStream << rg; } else if (QLatin1String("updateReplaygain")==request) { Tags::ReplayGain rg; inStream >> rg; outStream << (int)Tags::updateReplaygain(fileName, rg); } else if (QLatin1String("embedImage")==request) { QByteArray cover; inStream >> cover; outStream << (int)Tags::embedImage(fileName, cover); } else if (QLatin1String("oggMimeType")==request) { outStream << Tags::oggMimeType(fileName); } else if (QLatin1String("readRating")==request) { outStream << Tags::readRating(fileName); } else if (QLatin1String("updateRating")==request) { int rating=-1; inStream >> rating; outStream << (int)Tags::updateRating(fileName, rating); } else if (QLatin1String("readAll")==request) { outStream << Tags::readAll(fileName); } else { qApp->exit(); } DBUG << "RESP" << response.size(); QDataStream writeStream(socket); writeStream << qint32(response.length()); if (!response.isEmpty()) { writeStream.writeRawData(response.data(), response.length()); } socket->flush(); data.clear(); dataSize=0; } cantata-2.2.0/tags/taghelper.h000066400000000000000000000024461316350454000162170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAG_HELPER_H #define TAG_HELPER_H #include #include class QLocalSocket; class TagHelper : public QObject { Q_OBJECT public: static void enableDebug(); TagHelper(const QString &sockName, int parent); ~TagHelper(); private Q_SLOTS: void dataReady(); void checkParent(); private: void process(); private: int parentPid; QLocalSocket *socket; qint32 dataSize; QByteArray data; }; #endif cantata-2.2.0/tags/taghelperiface.cpp000066400000000000000000000305701316350454000175410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, readStatusite to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "taghelperiface.h" #include "tags.h" #include "config.h" #include "support/globalstatic.h" #include "support/thread.h" #include #include #include #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << QThread::currentThread()->objectName() << __FUNCTION__ void TagHelperIface::enableDebug() { debugEnabled=true; } GLOBAL_STATIC(TagHelperIface, instance) TagHelperIface::TagHelperIface() : msgStatus(true) , dataSize(0) , awaitingResponse(false) , thread(0) , proc(0) , server(0) , sock(0) { qRegisterMetaType("QAbstractSocket::SocketError"); thread=new Thread(metaObject()->className()); moveToThread(thread); thread->start(); } void TagHelperIface::stop() { if (thread) { DBUG; QMutexLocker locker(&mutex); metaObject()->invokeMethod(this, "close", Qt::QueuedConnection); sema.acquire(); DBUG << "Stop thread"; thread->stop(); thread=0; } } Song TagHelperIface::read(const QString &fileName) { DBUG << fileName; Song resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } QImage TagHelperIface::readImage(const QString &fileName) { DBUG << fileName; QImage resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } QString TagHelperIface::readLyrics(const QString &fileName) { DBUG << fileName; QString resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } QString TagHelperIface::readComment(const QString &fileName) { DBUG << fileName; QString resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } int TagHelperIface::updateArtistAndTitle(const QString &fileName, const Song &song) { DBUG << fileName; int resp=Tags::Update_Failed; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName << song; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } else { resp=Tags::Update_BadFile; } return resp; } int TagHelperIface::update(const QString &fileName, const Song &from, const Song &to, int id3Ver, bool saveComment) { DBUG << fileName; int resp=Tags::Update_Failed; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName << from << to << id3Ver << saveComment; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } else { resp=Tags::Update_BadFile; } return resp; } Tags::ReplayGain TagHelperIface::readReplaygain(const QString &fileName) { DBUG << fileName; Tags::ReplayGain resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } int TagHelperIface::updateReplaygain(const QString &fileName, const Tags::ReplayGain &rg) { DBUG << fileName; int resp=Tags::Update_Failed; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName << rg; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } else { resp=Tags::Update_BadFile; } return resp; } int TagHelperIface::embedImage(const QString &fileName, const QByteArray &cover) { DBUG << fileName; int resp=Tags::Update_Failed; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName << cover; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } else { resp=Tags::Update_BadFile; } return resp; } QString TagHelperIface::oggMimeType(const QString &fileName) { DBUG << fileName; QString resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } int TagHelperIface::readRating(const QString &fileName) { DBUG << fileName; int resp=0; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } int TagHelperIface::updateRating(const QString &fileName, int rating) { DBUG << fileName; int resp=Tags::Update_Failed; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName << rating; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } else { resp=Tags::Update_BadFile; } return resp; } QMap TagHelperIface::readAll(const QString &fileName) { DBUG << fileName; QMap resp; QByteArray message; QDataStream outStream(&message, QIODevice::WriteOnly); outStream << QString(__FUNCTION__) << fileName; Reply reply=sendMessage(message); if (reply.status) { QDataStream inStream(reply.data); inStream >> resp; } return resp; } TagHelperIface::Reply TagHelperIface::sendMessage(const QByteArray &msg) { QMutexLocker locker(&mutex); data=msg; metaObject()->invokeMethod(this, "sendMsg", Qt::QueuedConnection); sema.acquire(); TagHelperIface::Reply reply; reply.status=msgStatus; reply.data=data; DBUG << "Message response - " << reply.status << reply.data.length(); return reply; } static const int constMaxWait=5000; bool TagHelperIface::startHelper() { DBUG << (void *)proc; if (!helperIsRunning()) { stopHelper(); qint64 currentPid=QCoreApplication::applicationPid(); DBUG << "Create server"; server=new QLocalServer(this); forever { QString name="cantata-tags-"+QString::number(currentPid)+QLatin1Char('-')+QString::number(Utils::random()); QLocalServer::removeServer(name); if (server->listen(name)) { DBUG << "Listening on" << server->fullServerName(); break; } } DBUG << "Start process"; proc=new QProcess(this); if (debugEnabled) { proc->start(Utils::helper(QLatin1String("cantata-tags")), QStringList() << server->fullServerName() << QString::number(currentPid) << Utils::cacheDir()+"/cantata-tags.log"); } else { proc->start(Utils::helper(QLatin1String("cantata-tags")), QStringList() << server->fullServerName() << QString::number(currentPid)); } connect(proc, SIGNAL(finished(int)), this, SLOT(helperClosed())); if (proc->waitForStarted(constMaxWait)) { DBUG << "Process started, on pid" << proc->pid() << "- wait for helper to connect"; if (server->waitForNewConnection(constMaxWait)) { sock=server->nextPendingConnection(); } if (sock) { DBUG << "Helper connected"; connect(sock, SIGNAL(readyRead()), this, SLOT(dataReady())); connect(sock, SIGNAL(disconnected()), this, SLOT(helperClosed())); return true; } else { DBUG << "Helper did not connect"; } } else { DBUG << "Failed to start process"; } DBUG << "Failed to start"; stopHelper(); return false; } return true; } void TagHelperIface::close() { DBUG; awaitingResponse=true; stopHelper(); } void TagHelperIface::stopHelper() { if (sock) { DBUG << "Socket" << (void *)sock; disconnect(sock, SIGNAL(readyRead()), this, SLOT(dataReady())); disconnect(sock, SIGNAL(disconnected()), this, SLOT(helperClosed())); sock->flush(); sock->close(); sock->deleteLater(); sock=0; } if (server) { DBUG << "Server" << (void *)server; server->close(); server->deleteLater(); server=0; } if (proc) { disconnect(proc, SIGNAL(finished(int)), this, SLOT(helperClosed())); DBUG << "Process" << (void *)proc; if (QProcess::NotRunning!=proc->state()) { proc->kill(); proc->waitForFinished(10); } proc->deleteLater(); proc=0; } setStatus(false); } bool TagHelperIface::helperIsRunning() { return proc && QProcess::Running==proc->state() && sock && QLocalSocket::ConnectedState==sock->state(); } void TagHelperIface::sendMsg() { DBUG; if (startHelper()) { awaitingResponse=true; // In here because startHelper might call stopHelper, which calls setStatus! QDataStream stream(sock); stream << qint32(data.length()); stream.writeRawData(data.data(), data.length()); sock->flush(); DBUG << "Message sent"; data.clear(); dataSize=0; } else { awaitingResponse=true; setStatus(false); } } void TagHelperIface::dataReady() { DBUG; if (!awaitingResponse) { DBUG << "Socket is ready to read, but not expecting any message!!!"; stopHelper(); return; } while (sock->bytesAvailable()) { if (0==dataSize) { QDataStream stream(sock); stream >> dataSize; DBUG << "Expecting" << dataSize << "bytes in response"; if (dataSize<=0) { setStatus(false); return; } } data+=sock->read(dataSize-data.length()); if (data.length() == dataSize) { DBUG << "Response fully received"; setStatus(true); break; } } } void TagHelperIface::helperClosed() { DBUG; setStatus(false); } void TagHelperIface::setStatus(bool st) { DBUG << st << awaitingResponse; if (awaitingResponse) { awaitingResponse=false; msgStatus=st; sema.release(); } } cantata-2.2.0/tags/taghelperiface.h000066400000000000000000000051741316350454000172100ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAG_HELPER_IFACE_H #define TAG_HELPER_IFACE_H #include "mpd-interface/song.h" #include #include #include #include #include class QLocalServer; class QLocalSocket; class QProcess; class Thread; namespace Tags { struct ReplayGain; } class TagHelperIface : public QObject { Q_OBJECT public: static void enableDebug(); static TagHelperIface * self(); struct Reply { bool status; QByteArray data; }; TagHelperIface(); void stop(); Song read(const QString &fileName); QImage readImage(const QString &fileName); QString readLyrics(const QString &fileName); QString readComment(const QString &fileName); int updateArtistAndTitle(const QString &fileName, const Song &song); int update(const QString &fileName, const Song &from, const Song &to, int id3Ver, bool saveComment); Tags::ReplayGain readReplaygain(const QString &fileName); int updateReplaygain(const QString &fileName, const Tags::ReplayGain &rg); int embedImage(const QString &fileName, const QByteArray &cover); QString oggMimeType(const QString &fileName); int readRating(const QString &fileName); int updateRating(const QString &fileName, int rating); QMap readAll(const QString &fileName); private: bool helperIsRunning(); Reply sendMessage(const QByteArray &msg); bool startHelper(); void setStatus(bool st); private Q_SLOTS: void close(); void stopHelper(); void sendMsg(); void dataReady(); void helperClosed(); private: QMutex mutex; QByteArray data; bool msgStatus; qint32 dataSize; bool awaitingResponse; Thread *thread; QSemaphore sema; QProcess *proc; QLocalServer *server; QLocalSocket *sock; }; #endif cantata-2.2.0/tags/tags.cpp000066400000000000000000001544241316350454000155410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tags.h" #include "config.h" #include "filetyperesolver.h" #include "support/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define TAGLIB_VERSION CANTATA_MAKE_VERSION(TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION) #include #if TAGLIB_VERSION >= CANTATA_MAKE_VERSION(1,8,0) #include #endif #include #include #ifdef TAGLIB_ASF_FOUND #include #endif #include #ifdef TAGLIB_MP4_FOUND #include #endif #ifdef TAGLIB_OPUS_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef TAGLIB_ASF_FOUND #include #endif #include #include #include #ifdef TAGLIB_MP4_FOUND #include #endif #include #include #include #include #include #include #ifdef TAGLIB_EXTRAS_FOUND #include #include #endif namespace Tags { static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << "Tags" << __FUNCTION__ void enableDebug() { debugEnabled=true; } static QString tString2QString(const TagLib::String &str) { static QTextCodec *codec = QTextCodec::codecForName("UTF-8"); return codec->toUnicode(str.toCString(true)).trimmed(); } TagLib::String qString2TString(const QString &str) { QString val = str.trimmed(); return val.isEmpty() ? TagLib::String::null : TagLib::String(val.toUtf8().data(), TagLib::String::UTF8); } static inline int convertToCantataRating(double r) { return qRound(r*10.0); //return qRound((r*10.0)/2.0); } static std::string convertFromCantataRating(int rating) { std::stringstream ss; ss.precision(2); ss << std::fixed << (rating/10.0); return ss.str(); } static double parseDoubleString(const TagLib::String &str) { if (str.isEmpty()) { return 0.0; } QString s=tString2QString(str); bool ok=false; double v=s.toDouble(&ok); return ok ? v : 0.0; } static double parseRgString(const TagLib::String &str) { if (str.isEmpty()) { return 0.0; } QString s=tString2QString(str); s.remove(QLatin1String(" dB"), Qt::CaseInsensitive); if (str.isEmpty()) { return 0.0; } bool ok=false; double v=s.toDouble(&ok); return ok ? v : 0.0; } struct RgTags : public ReplayGain { RgTags(const ReplayGain &r) : ReplayGain(r) , albumMode(true) , null(false) { } RgTags() : albumMode(false) , null(true) { } bool albumMode; bool null; }; struct RgTagsStrings { RgTagsStrings(const RgTags &rg) { std::stringstream ss; ss.precision(2); ss << std::fixed; ss << rg.albumGain << " dB"; albumGain = ss.str(); ss.str(std::string()); ss.clear(); ss << rg.trackGain << " dB"; trackGain = ss.str(); ss.str(std::string()); ss.clear(); ss.precision(6); ss << rg.albumPeak; ss >> albumPeak; ss.str(std::string()); ss.clear(); ss << rg.trackPeak; ss >> trackPeak; ss.str(std::string()); ss.clear(); } std::string trackGain, trackPeak, albumGain, albumPeak; }; static void ensureFileTypeResolvers() { static bool alreadyAdded = false; if (!alreadyAdded) { alreadyAdded = true; #ifdef TAGLIB_EXTRAS_FOUND TagLib::FileRef::addFileTypeResolver(new AudibleFileTypeResolver); TagLib::FileRef::addFileTypeResolver(new RealMediaFileTypeResolver); #endif TagLib::FileRef::addFileTypeResolver(new Meta::Tag::FileTypeResolver()); } } static TagLib::FileRef getFileRef(const QString &path) { ensureFileTypeResolvers(); #ifdef Q_OS_WIN return TagLib::FileRef(reinterpret_cast(path.utf16()), true, TagLib::AudioProperties::Fast); #else return TagLib::FileRef(QFile::encodeName(path).constData(), true, TagLib::AudioProperties::Fast); #endif } static QPair splitDiscNumber(const QString &value) { int disc; int count = 0; if (-1!=value.indexOf('/')) { QStringList list = value.split('/', QString::SkipEmptyParts); disc = list.value(0).toInt(); count = list.value(1).toInt(); } else if (-1!=value.indexOf(':')) { QStringList list = value.split(':', QString::SkipEmptyParts); disc = list.value(0).toInt(); count = list.value(1).toInt(); } else { disc = value.toInt(); } return qMakePair(disc, count); } // -- taken from rgtag.cpp from libebur128 -- START static bool clearTxxxTag(TagLib::ID3v2::Tag *tag, TagLib::String tagName) { TagLib::ID3v2::FrameList l = tag->frameList("TXXX"); for (TagLib::ID3v2::FrameList::Iterator it = l.begin(); it != l.end(); ++it) { TagLib::ID3v2::UserTextIdentificationFrame *fr=dynamic_cast(*it); if (fr && fr->description().upper() == tagName) { tag->removeFrame(fr); return true; } } return false; } static bool clearRva2Tag(TagLib::ID3v2::Tag *tag, TagLib::String tagName) { TagLib::ID3v2::FrameList l = tag->frameList("RVA2"); for (TagLib::ID3v2::FrameList::Iterator it = l.begin(); it != l.end(); ++it) { TagLib::ID3v2::RelativeVolumeFrame *fr=dynamic_cast(*it); if (fr && fr->identification().upper() == tagName) { tag->removeFrame(fr); return true; } } return false; } static void setTxxxTag(TagLib::ID3v2::Tag *tag, const std::string &tagName, const std::string &value) { TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, tagName); if (!frame) { frame = new TagLib::ID3v2::UserTextIdentificationFrame; frame->setDescription(tagName); tag->addFrame(frame); } frame->setText(value); } static void setRva2Tag(TagLib::ID3v2::Tag *tag, const std::string &tagName, double gain, double peak) { TagLib::ID3v2::RelativeVolumeFrame *frame = NULL; TagLib::ID3v2::FrameList frameList = tag->frameList("RVA2"); TagLib::ID3v2::FrameList::ConstIterator it = frameList.begin(); for (; it != frameList.end(); ++it) { TagLib::ID3v2::RelativeVolumeFrame *fr=dynamic_cast(*it); if (fr && fr->identification() == tagName) { frame = fr; break; } } if (!frame) { frame = new TagLib::ID3v2::RelativeVolumeFrame; frame->setIdentification(tagName); tag->addFrame(frame); } frame->setVolumeAdjustment(float(gain), TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peakVolume; peakVolume.bitsRepresentingPeak = 16; double ampPeak = peak * 32768.0 > 65535.0 ? 65535.0 : peak * 32768.0; unsigned int ampPeakInt = static_cast(std::ceil(ampPeak)); TagLib::ByteVector bv_uint = TagLib::ByteVector::fromUInt(ampPeakInt); peakVolume.peakVolume = TagLib::ByteVector(&(bv_uint.data()[2]), 2); frame->setPeakVolume(peakVolume); } // -- taken from rgtag.cpp from libebur128 -- END static void readID3v2Tags(TagLib::ID3v2::Tag *tag, Song *song, ReplayGain *rg, QImage *img, QString *lyrics, int *rating) { if (song) { const TagLib::ID3v2::FrameList &albumArtist = tag->frameListMap()["TPE2"]; if (!albumArtist.isEmpty()) { song->albumartist=tString2QString(albumArtist.front()->toString()); } const TagLib::ID3v2::FrameList &composer = tag->frameListMap()["TCOM"]; if (!composer.isEmpty()) { song->setComposer(tString2QString(composer.front()->toString())); } const TagLib::ID3v2::FrameList &disc = tag->frameListMap()["TPOS"]; if (!disc.isEmpty()) { song->disc=splitDiscNumber(tString2QString(disc.front()->toString())).first; } const TagLib::ID3v2::FrameList &tcon = tag->frameListMap()["TCON"]; if (!tcon.isEmpty()) { TagLib::ID3v2::FrameList::ConstIterator it = tcon.begin(); TagLib::ID3v2::FrameList::ConstIterator end = tcon.end(); for (; it!=end; ++it) { TagLib::ID3v2::TextIdentificationFrame *f = static_cast(*it); TagLib::StringList fields = f->fieldList(); TagLib::StringList::ConstIterator fIt = fields.begin(); TagLib::StringList::ConstIterator fEnd = fields.end(); for (; fIt!=fEnd; ++fIt) { if ((*fIt).isEmpty()) { continue; } bool ok; int number = (*fIt).toInt(&ok); QString genre; if (ok && number >= 0 && number <= 255) { genre = tString2QString(TagLib::ID3v1::genre(number)); } else { genre = tString2QString(*fIt); } song->addGenre(genre); } } } } if (rg) { const TagLib::ID3v2::FrameList &frames = tag->frameList("TXXX"); TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); TagLib::ID3v2::FrameList::ConstIterator end = frames.end(); int found=0; for (; it != end && found!=0x0F; ++it) { TagLib::ID3v2::UserTextIdentificationFrame *fr=dynamic_cast(*it); if (fr) { if (fr->description().upper() == "REPLAYGAIN_TRACK_GAIN") { found|=1; rg->trackGain=parseRgString(fr->fieldList().back()); } else if (fr->description().upper() == "REPLAYGAIN_TRACK_PEAK") { rg->trackPeak=parseRgString(fr->fieldList().back()); found|=2; } else if (fr->description().upper() == "REPLAYGAIN_ALBUM_GAIN") { rg->albumGain=parseRgString(fr->fieldList().back()); found|=4; } else if (fr->description().upper() == "REPLAYGAIN_ALBUM_PEAK") { rg->albumPeak=parseRgString(fr->fieldList().back()); found|=8; } } } } if (img) { const TagLib::ID3v2::FrameList &frames = tag->frameList("APIC"); if (!frames.isEmpty()) { TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); TagLib::ID3v2::FrameList::ConstIterator end = frames.end(); bool found = false; for (; it != end && !found; ++it) { TagLib::ID3v2::AttachedPictureFrame *pic=dynamic_cast(*it); if (pic && TagLib::ID3v2::AttachedPictureFrame::FrontCover==pic->type()) { img->loadFromData((const uchar *) pic->picture().data(), pic->picture().size()); if (!img->isNull()) { found=true; } } } if (!found) { // Just use first image! TagLib::ID3v2::AttachedPictureFrame *pic=static_cast(frames.front()); img->loadFromData((const uchar *) pic->picture().data(), pic->picture().size()); } } } if (lyrics) { const TagLib::ID3v2::FrameList &frames = tag->frameList("USLT"); if (!frames.isEmpty()) { TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); TagLib::ID3v2::FrameList::ConstIterator end = frames.end(); bool found = false; for (; it != end && !found; ++it) { TagLib::ID3v2::UnsynchronizedLyricsFrame *l=dynamic_cast(*it); if (l /*&& !l->language().isEmpty() && 0==strcmp(l->language().data(), lyricsLang)*/) { *lyrics=tString2QString(l->toString()); found=true; } } } } if (rating) { // First check for FMPS rating const TagLib::ID3v2::FrameList &frames = tag->frameList("TXXX"); TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); TagLib::ID3v2::FrameList::ConstIterator end = frames.end(); for (; it != end; ++it) { TagLib::ID3v2::UserTextIdentificationFrame *fr=dynamic_cast(*it); if (fr) { if (fr->description().upper() == "FMPS_RATING") { *rating=convertToCantataRating(parseDoubleString(fr->fieldList().back())); return; } } } const TagLib::ID3v2::FrameList &popm = tag->frameListMap()["POPM"]; it = popm.begin(); end = popm.end(); for (; it != end; ++it) { TagLib::ID3v2::PopularimeterFrame *p=dynamic_cast< TagLib::ID3v2::PopularimeterFrame * >(*it); if (p) { if (p->email().isEmpty()) { *rating=convertToCantataRating(p->rating() / 256.0); return; } } } } } static bool updateID3v2Tag(TagLib::ID3v2::Tag *tag, const char *tagName, const QString &value) { const TagLib::ID3v2::FrameList &frameList = tag->frameListMap()[tagName]; if (value.isEmpty()) { if (!frameList.isEmpty()) { tag->removeFrames(tagName); return true; } } else { TagLib::ID3v2::TextIdentificationFrame *frame=frameList.isEmpty() ? 0 : dynamic_cast(frameList.front()); if (!frame) { frame = new TagLib::ID3v2::TextIdentificationFrame(tagName); tag->addFrame(frame); } frame->setText(qString2TString(value)); return true; } return false; } static bool isId3V24(TagLib::ID3v2::Header *header) { return header && header->majorVersion()>3; } static bool writeID3v2Tags(TagLib::ID3v2::Tag *tag, const Song &from, const Song &to, const RgTags &rg, const QByteArray &img, int rating) { bool changed=false; if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.albumartist!=to.albumartist && updateID3v2Tag(tag, "TPE2", to.albumartist)) { changed=true; } if (from.composer()!=to.composer() && updateID3v2Tag(tag, "TCOM", to.composer())) { changed=true; } if (from.disc!=to.disc&& updateID3v2Tag(tag, "TPOS", 0==to.disc ? QString() : QString::number(to.disc))) { changed=true; } DBUG << "genres" << from.genres << to.genres; if (0!=from.compareGenres(to)) { tag->removeFrames("TCON"); if (to.genres[1].isEmpty()) { DBUG << "set genre" << (to.firstGenre().trimmed()); tag->setGenre(qString2TString(to.firstGenre().trimmed())); } else { for(int i=0; iaddFrame(frame); DBUG << "add genre" << to.genres[i].trimmed(); frame->setText(qString2TString(to.genres[i].trimmed())); } } changed=true; } } if (!rg.null) { RgTagsStrings rgs(rg); while (clearTxxxTag(tag, TagLib::String("replaygain_album_gain").upper())); while (clearTxxxTag(tag, TagLib::String("replaygain_album_peak").upper())); while (clearRva2Tag(tag, TagLib::String("album").upper())); while (clearTxxxTag(tag, TagLib::String("replaygain_track_gain").upper())); while (clearTxxxTag(tag, TagLib::String("replaygain_track_peak").upper())); while (clearRva2Tag(tag, TagLib::String("track").upper())); setTxxxTag(tag, "replaygain_track_gain", rgs.trackGain); setTxxxTag(tag, "replaygain_track_peak", rgs.trackPeak); if (isId3V24(tag->header())) { setRva2Tag(tag, "track", rg.trackGain, rg.trackPeak); } if (rg.albumMode) { setTxxxTag(tag, "replaygain_album_gain", rgs.albumGain); setTxxxTag(tag, "replaygain_album_peak", rgs.albumPeak); if (isId3V24(tag->header())) { setRva2Tag(tag, "album", rg.albumGain, rg.albumPeak); } } changed=true; } if (!img.isEmpty()) { TagLib::ByteVector imgData(img.constData(), img.length()); TagLib::ID3v2::AttachedPictureFrame *pic=new TagLib::ID3v2::AttachedPictureFrame; pic->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover); pic->setMimeType("image/jpeg"); pic->setPicture(imgData); tag->addFrame(pic); changed=true; } if (rating>-1) { int old=-1; readID3v2Tags(tag, 0, 0, 0, 0, &old); if (old!=rating) { while (clearTxxxTag(tag, "FMPS_RATING")); setTxxxTag(tag, "FMPS_RATING", convertFromCantataRating(rating)); changed=true; } } return changed; } static void readAPETags(TagLib::APE::Tag *tag, Song *song, ReplayGain *rg, QImage *img, int *rating) { const TagLib::APE::ItemListMap &map = tag->itemListMap(); if (song) { if (map.contains("Album Artist")) { song->albumartist=tString2QString(map["Album Artist"].toString()); } if (map.contains("Composer")) { song->setComposer(tString2QString(map["Composer"].toString())); } if (map.contains("Disc")) { song->disc=splitDiscNumber(tString2QString(map["Disc"].toString())).first; } if (map.contains("GENRE")) { TagLib::StringList genres=map["GENRE"].values(); TagLib::StringList::ConstIterator it=genres.begin(); TagLib::StringList::ConstIterator end=genres.end(); for (; it!=end; ++it) { song->addGenre(tString2QString(*it)); } } } if (rg) { if (map.contains("replaygain_track_gain")) { rg->trackGain=parseRgString(map["replaygain_track_gain"].toString()); } if (map.contains("replaygain_track_peak")) { rg->trackPeak=parseRgString(map["replaygain_track_peak"].toString()); } if (map.contains("replaygain_album_gain")) { rg->albumGain=parseRgString(map["replaygain_album_gain"].toString()); } if (map.contains("replaygain_album_peak")) { rg->albumPeak=parseRgString(map["replaygain_album_peak"].toString()); } } if (img) { if (map.contains("COVER ART (FRONT)")) { const TagLib::ByteVector nullStringTerminator(1, 0); TagLib::ByteVector item = map["COVER ART (FRONT)"].value(); int pos = item.find(nullStringTerminator); // Skip the filename if (++pos > 0) { const TagLib::ByteVector &bytes=item.mid(pos); QByteArray data(bytes.data(), bytes.size()); img->loadFromData(data); } } } if (rating) { *rating=convertToCantataRating(parseDoubleString(map["FMPS_RATING"].toString())); } } static bool updateAPETag(TagLib::APE::Tag *tag, const char *tagName, const QString &value) { const TagLib::APE::ItemListMap &map = tag->itemListMap(); if (value.isEmpty()) { if (map.contains(tagName)) { tag->removeItem(tagName); return true; } } else { tag->addValue(tagName, qString2TString(value), true); return true; } return false; } static bool writeAPETags(TagLib::APE::Tag *tag, const Song &from, const Song &to, const RgTags &rg, int rating) { bool changed=false; if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.albumartist!=to.albumartist && updateAPETag(tag, "Album Artist", to.albumartist)) { changed=true; } if (from.composer()!=to.composer() && updateAPETag(tag, "Composer", to.composer())) { changed=true; } if (from.disc!=to.disc && updateAPETag(tag, "Disc", 0==to.disc ? QString() : QString::number(to.disc))) { changed=true; } if (0!=from.compareGenres(to)) { tag->removeItem("GENRE"); if (to.genres[1].isEmpty()) { tag->setGenre(qString2TString(to.firstGenre().trimmed())); } else { for(int i=0; iaddValue("GENRE", qString2TString(to.genres[i].trimmed()), false); } } changed=true; } } if (!rg.null) { RgTagsStrings rgs(rg); tag->addValue("replaygain_track_gain", rgs.trackGain); tag->addValue("replaygain_track_peak", rgs.trackPeak); if (rg.albumMode) { tag->addValue("replaygain_album_gain", rgs.albumGain); tag->addValue("replaygain_album_peak", rgs.albumPeak); } else { tag->removeItem("replaygain_album_gain"); tag->removeItem("replaygain_album_peak"); } changed=true; } if (rating>-1) { int old=-1; readAPETags(tag, 0, 0, 0, &old); if (old!=rating) { tag->addValue("FMPS_RATING", convertFromCantataRating(rating)); changed=true; } } return changed; } static TagLib::String readVorbisTag(TagLib::Ogg::XiphComment *tag, const char *field) { if (tag->contains(field)) { const TagLib::StringList &list = tag->fieldListMap()[field]; if (!list.isEmpty()) { return list.front(); } } return TagLib::String(); } static void readVorbisCommentTags(TagLib::Ogg::XiphComment *tag, Song *song, ReplayGain *rg, QImage *img, QString *lyrics, int *rating) { if (song) { TagLib::String str=readVorbisTag(tag, "ALBUMARTIST"); if (str.isEmpty()) { song->albumartist=tString2QString(str); } str=readVorbisTag(tag, "COMPOSER"); if (str.isEmpty()) { song->setComposer(tString2QString(str)); } str=readVorbisTag(tag, "DISCNUMBER"); if (str.isEmpty()) { song->disc=splitDiscNumber(tString2QString(str)).first; } if (tag->contains("GENRE")) { const TagLib::StringList &genres = tag->fieldListMap()["GENRE"]; if (!genres.isEmpty()) { TagLib::StringList::ConstIterator it=genres.begin(); TagLib::StringList::ConstIterator end=genres.end(); for (; it!=end; ++it) { song->addGenre(tString2QString(*it)); } } } } if (rg) { rg->trackGain=parseRgString(readVorbisTag(tag, "REPLAYGAIN_TRACK_GAIN")); rg->trackPeak=parseRgString(readVorbisTag(tag, "REPLAYGAIN_TRACK_PEAK")); rg->albumGain=parseRgString(readVorbisTag(tag, "REPLAYGAIN_ALBUM_GAIN")); rg->albumPeak=parseRgString(readVorbisTag(tag, "REPLAYGAIN_ALBUM_PEAK")); } if (img) { TagLib::Ogg::FieldListMap map = tag->fieldListMap(); #if TAGLIB_VERSION >= CANTATA_MAKE_VERSION(1,7,0) // METADATA_BLOCK_PICTURE is the Ogg standard way of encoding a covers. // https://wiki.xiph.org/index.php/VorbisComment#Cover_art if (map.contains("METADATA_BLOCK_PICTURE")) { TagLib::StringList block=map["METADATA_BLOCK_PICTURE"]; if (!block.isEmpty()) { TagLib::StringList::ConstIterator i = block.begin(); TagLib::StringList::ConstIterator end = block.end(); for (; i != end; ++i ) { QByteArray data(QByteArray::fromBase64(i->to8Bit().c_str())); TagLib::ByteVector bytes(data.data(), data.size()); TagLib::FLAC::Picture pic; if (pic.parse(bytes)) { img->loadFromData(QByteArray(pic.data().data(), pic.data().size())); } } } } #endif // COVERART is an older (now deprecated) way of storing covers... if (img->isNull() && map.contains("COVERART")) { QByteArray data=map["COVERART"].toString().toCString(); img->loadFromData(QByteArray::fromBase64(data)); if (img->isNull()) { img->loadFromData(data); // not base64?? } } } if (lyrics) { TagLib::String str=readVorbisTag(tag, "LYRICS"); if (!str.isEmpty()) { *lyrics=tString2QString(str); } } if (rating) { *rating=convertToCantataRating(parseDoubleString(readVorbisTag(tag, "FMPS_RATING"))); } } #if TAGLIB_VERSION >= CANTATA_MAKE_VERSION(1,7,0) static void readFlacPicture(const TagLib::List &pics, QImage *img) { if (!pics.isEmpty() && 1==pics.size()) { TagLib::FLAC::Picture *picture = *(pics.begin()); QByteArray data(picture->data().data(), picture->data().size()); img->loadFromData(data); } } #endif static bool updateVorbisCommentTag(TagLib::Ogg::XiphComment *tag, const char *tagName, const QString &value) { if (value.isEmpty()) { const TagLib::StringList &list = tag->fieldListMap()[tagName]; if (!list.isEmpty()) { tag->removeField(tagName); return true; } } else { tag->addField(tagName, qString2TString(value), true); return true; } return false; } static bool writeVorbisCommentTags(TagLib::Ogg::XiphComment *tag, const Song &from, const Song &to, const RgTags &rg, const QByteArray &img, int rating) { bool changed=false; if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.albumartist!=to.albumartist && updateVorbisCommentTag(tag, "ALBUMARTIST", to.albumartist)) { changed=true; } if (from.composer()!=to.composer() && updateVorbisCommentTag(tag, "COMPOSER", to.composer())) { changed=true; } if (from.disc!=to.disc && updateVorbisCommentTag(tag, "DISCNUMBER", 0==to.disc ? QString() : QString::number(to.disc))) { changed=true; } if (0!=from.compareGenres(to)) { tag->removeField("GENRE"); if (to.genres[1].isEmpty()) { tag->setGenre(qString2TString(to.firstGenre().trimmed())); } else { for(int i=0; iaddField("GENRE", qString2TString(to.genres[i].trimmed()), false); } } changed=true; } } if (!rg.null) { RgTagsStrings rgs(rg); tag->addField("REPLAYGAIN_TRACK_GAIN", rgs.trackGain); tag->addField("REPLAYGAIN_TRACK_PEAK", rgs.trackPeak); if (rg.albumMode) { tag->addField("REPLAYGAIN_ALBUM_GAIN", rgs.albumGain); tag->addField("REPLAYGAIN_ALBUM_PEAK", rgs.albumPeak); } else { tag->removeField("REPLAYGAIN_ALBUM_GAIN"); tag->removeField("REPLAYGAIN_ALBUM_PEAK"); } changed=true; } if (!img.isEmpty()) { tag->addField("COVERART", qString2TString(QString::fromLatin1(img.toBase64()))); changed=true; } if (rating>-1) { int old=-1; readVorbisCommentTags(tag, 0, 0, 0, 0, &old); if (old!=rating) { tag->addField("FMPS_RATING", convertFromCantataRating(rating)); changed=true; } } return changed; } #ifdef TAGLIB_MP4_FOUND static void readMP4Tags(TagLib::MP4::Tag *tag, Song *song, ReplayGain *rg, QImage *img, QString *lyrics, int *rating) { TagLib::MP4::ItemListMap &map = tag->itemListMap(); if (song) { if (map.contains("aART") && !map["aART"].toStringList().isEmpty()) { song->albumartist=tString2QString(map["aART"].toStringList().front()); } if (map.contains("\xA9wrt") && !map["\xA9wrt"].toStringList().isEmpty()) { song->setComposer(tString2QString(map["\xA9wrt"].toStringList().front())); } if (map.contains("disk")) { song->disc=map["disk"].toIntPair().first; } if (map.contains("\251gen")) { TagLib::StringList genres=map["\251gen"].toStringList(); TagLib::StringList::ConstIterator it=genres.begin(); TagLib::StringList::ConstIterator end=genres.end(); for (; it!=end; ++it) { song->addGenre(tString2QString(*it)); } } } if (rg) { if (map.contains("----:com.apple.iTunes:replaygain_track_gain")) { rg->trackGain=parseRgString(map["----:com.apple.iTunes:replaygain_track_gain"].toStringList().front()); } if (map.contains("----:com.apple.iTunes:replaygain_track_peak")) { rg->trackPeak=parseRgString(map["----:com.apple.iTunes:replaygain_track_peak"].toStringList().front()); } if (map.contains("----:com.apple.iTunes:replaygain_album_gain")) { rg->albumGain=parseRgString(map["----:com.apple.iTunes:replaygain_album_gain"].toStringList().front()); } if (map.contains("----:com.apple.iTunes:replaygain_album_peak")) { rg->albumPeak=parseRgString(map["----:com.apple.iTunes:replaygain_album_peak"].toStringList().front()); } } if (img) { if (map.contains("covr")) { TagLib::MP4::Item coverItem = map["covr"]; TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList(); if (!coverArtList.isEmpty()) { TagLib::MP4::CoverArt coverArt = coverArtList.front(); img->loadFromData((const uchar *) coverArt.data().data(), coverArt.data().size()); } } } if (lyrics) { if (map.contains("\251lyr") && !map["\251lyr"].toStringList().isEmpty()) { *lyrics=tString2QString(map["\251lyr"].toStringList().front()); } } if (rating) { if (map.contains("----:com.apple.iTunes:FMPS_Rating")) { *rating=convertToCantataRating(parseDoubleString(map["----:com.apple.iTunes:FMPS_Rating"].toStringList().front())); } } } static bool updateMP4Tag(TagLib::MP4::Tag *tag, const char *tagName, const QString &value) { TagLib::MP4::ItemListMap &map = tag->itemListMap(); if (value.isEmpty()) { if (map.contains(tagName)) { map.erase(tagName); return true; } } else { map.insert(tagName, TagLib::StringList(qString2TString(value))); return true; } return false; } static bool writeMP4Tags(TagLib::MP4::Tag *tag, const Song &from, const Song &to, const RgTags &rg, const QByteArray &img, int rating) { bool changed=false; if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.albumartist!=to.albumartist && updateMP4Tag(tag, "aART", to.albumartist)) { changed=true; } if (from.composer()!=to.composer() && updateMP4Tag(tag, "\xA9wrt", to.composer())) { changed=true; } if (from.disc!=to.disc && updateMP4Tag(tag, "disk", 0==to.disc ? QString() : QString::number(to.disc))) { changed=true; } if (0!=from.compareGenres(to)) { if (to.genres[1].isEmpty()) { tag->setGenre(qString2TString(to.firstGenre().trimmed())); } else { TagLib::StringList tagGenres; for(int i=0; iitemListMap()["\251gen"]=tagGenres; } changed=true; } } if (!rg.null) { RgTagsStrings rgs(rg); TagLib::MP4::ItemListMap &map = tag->itemListMap(); map["----:com.apple.iTunes:replaygain_track_gain"] = TagLib::MP4::Item(TagLib::StringList(rgs.trackGain)); map["----:com.apple.iTunes:replaygain_track_peak"] = TagLib::MP4::Item(TagLib::StringList(rgs.trackPeak)); if (rg.albumMode) { map["----:com.apple.iTunes:replaygain_album_gain"] = TagLib::MP4::Item(TagLib::StringList(rgs.albumGain)); map["----:com.apple.iTunes:replaygain_album_peak"] = TagLib::MP4::Item(TagLib::StringList(rgs.albumPeak)); } else { map.erase("----:com.apple.iTunes:replaygain_album_gain"); map.erase("----:com.apple.iTunes:replaygain_album_peak"); } changed=true; } if (!img.isEmpty()) { TagLib::ByteVector imgData(img.constData(), img.length()); TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::JPEG, imgData); TagLib::MP4::CoverArtList coverArtList; coverArtList.append(coverArt); TagLib::MP4::Item coverItem(coverArtList); TagLib::MP4::ItemListMap &map = tag->itemListMap(); map.insert("covr", coverItem); } if (rating>-1) { int old=-1; readMP4Tags(tag, 0, 0, 0, 0, &old); if (old!=rating) { TagLib::MP4::ItemListMap &map = tag->itemListMap(); map["----:com.apple.iTunes:FMPS_Rating"] = TagLib::MP4::Item(TagLib::StringList(convertFromCantataRating(rating))); changed=true; } } return changed; } #endif #ifdef TAGLIB_ASF_FOUND static void readASFTags(TagLib::ASF::Tag *tag, Song *song, int *rating) { if (song) { const TagLib::ASF::AttributeListMap &map = tag->attributeListMap(); if (map.contains("WM/AlbumTitle") && !map["WM/AlbumTitle"].isEmpty()) { song->albumartist=tString2QString(map["WM/AlbumTitle"].front().toString()); } if (map.contains("WM/Composer") && !map["WM/Composer"].isEmpty()) { song->setComposer(tString2QString(map["WM/Composer"].front().toString())); } if (map.contains("WM/PartOfSet") && !map["WM/PartOfSet"].isEmpty()) { song->albumartist=map["WM/PartOfSet"].front().toUInt(); } if (map.contains("WM/Genre") && !map["WM/Genre"].isEmpty()) { const TagLib::ASF::AttributeList &genres=map["WM/Genre"]; TagLib::ASF::AttributeList::ConstIterator it=genres.begin(); TagLib::ASF::AttributeList::ConstIterator end=genres.end(); for (; it!=end; ++it) { song->addGenre(tString2QString((*it).toString())); } } } if (rating) { const TagLib::ASF::AttributeListMap &map = tag->attributeListMap(); if (map.contains("FMPS/Rating") && !map["FMPS/Rating"].isEmpty()) { *rating=convertToCantataRating(parseDoubleString(map["FMPS/Rating"].front().toString())); } } } static bool updateASFTag(TagLib::ASF::Tag *tag, const char *tagName, const QString &value) { const TagLib::ASF::AttributeListMap &map = tag->attributeListMap(); if (value.isEmpty()) { if (map.contains(tagName)) { tag->removeItem(tagName); return true; } } else { tag->setAttribute(tagName, qString2TString(value)); return true; } return false; } static bool writeASFTags(TagLib::ASF::Tag *tag, const Song &from, const Song &to, const RgTags &rg, int rating) { Q_UNUSED(rg) bool changed=false; if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.albumartist!=to.albumartist && updateASFTag(tag, "WM/AlbumTitle", to.albumartist)) { changed=true; } if (from.composer()!=to.composer() && updateASFTag(tag, "WM/Composer", to.composer())) { changed=true; } if (from.disc!=to.disc && updateASFTag(tag, "WM/PartOfSet", 0==to.disc ? QString() : QString::number(to.disc))) { changed=true; } if (0!=from.compareGenres(to)) { tag->removeItem("WM/Genre"); if (to.genres[1].isEmpty()) { tag->setGenre(qString2TString(to.firstGenre().trimmed())); } else { for(int i=0; iaddAttribute("WM/Genre", qString2TString(to.genres[i].trimmed())); } } changed=true; } } if (rating>-1) { int old=-1; readASFTags(tag, 0, &old); if (old!=rating) { tag->addAttribute("FMPS/Rating", TagLib::String(convertFromCantataRating(rating))); changed=true; } } return changed; } #endif static void readTags(const TagLib::FileRef fileref, Song *song, ReplayGain *rg, QImage *img, QString *lyrics, int *rating) { TagLib::Tag *tag=fileref.tag(); if (song) { song->title=tString2QString(tag->title()); song->artist=tString2QString(tag->artist()); song->album=tString2QString(tag->album()); // song->genre=tString2QString(tag->genre()); song->track=tag->track(); song->year=tag->year(); } if (TagLib::MPEG::File *file = dynamic_cast< TagLib::MPEG::File * >(fileref.file())) { if (file->ID3v2Tag() && !file->ID3v2Tag()->isEmpty()) { readID3v2Tags(file->ID3v2Tag(), song, rg, img, lyrics, rating); } else if (file->APETag()) { readAPETags(file->APETag(), song, rg, img, rating); // } else if (file->ID3v1Tag()) { // readID3v1Tags(fileref, song, rg); } } else if (TagLib::Ogg::Vorbis::File *file = dynamic_cast< TagLib::Ogg::Vorbis::File * >(fileref.file())) { if (file->tag()) { readVorbisCommentTags(file->tag(), song, rg, img, lyrics, rating); } } else if (TagLib::Ogg::FLAC::File *file = dynamic_cast< TagLib::Ogg::FLAC::File * >(fileref.file())) { if (file->tag()) { readVorbisCommentTags(file->tag(), song, rg, img, lyrics, rating); } } else if (TagLib::Ogg::Speex::File *file = dynamic_cast< TagLib::Ogg::Speex::File * >(fileref.file())) { if (file->tag()) { readVorbisCommentTags(file->tag(), song, rg, img, lyrics, rating); } #ifdef TAGLIB_OPUS_FOUND } else if (TagLib::Ogg::Opus::File *file = dynamic_cast< TagLib::Ogg::Opus::File * >(fileref.file())) { if (file->tag()) { readVorbisCommentTags(file->tag(), song, rg, img, lyrics, rating); } #endif } else if (TagLib::FLAC::File *file = dynamic_cast< TagLib::FLAC::File * >(fileref.file())) { if (file->xiphComment()) { readVorbisCommentTags(file->xiphComment(), song, rg, img, lyrics, rating); #if TAGLIB_VERSION >= CANTATA_MAKE_VERSION(1,7,0) if (img && img->isNull()) { readFlacPicture(file->pictureList(), img); } #endif } else if (file->ID3v2Tag() && !file->ID3v2Tag()->isEmpty()) { readID3v2Tags(file->ID3v2Tag(), song, rg, img, lyrics, rating); // } else if (file->ID3v1Tag()) { // readID3v1Tags(fileref, song, rg); } #ifdef TAGLIB_MP4_FOUND } else if (TagLib::MP4::File *file = dynamic_cast< TagLib::MP4::File * >(fileref.file())) { TagLib::MP4::Tag *tag = dynamic_cast< TagLib::MP4::Tag * >(file->tag()); if (tag) { readMP4Tags(tag, song, rg, img, lyrics, rating); } #endif } else if (TagLib::MPC::File *file = dynamic_cast< TagLib::MPC::File * >(fileref.file())) { if (file->APETag()) { readAPETags(file->APETag(), song, rg, img, rating); // } else if (file->ID3v1Tag()) { // readID3v1Tags(fileref, song, rg); } } else if (TagLib::RIFF::AIFF::File *file = dynamic_cast< TagLib::RIFF::AIFF::File * >(fileref.file())) { if (file->tag()) { readID3v2Tags(file->tag(), song, rg, img, lyrics, rating); } } else if (TagLib::RIFF::WAV::File *file = dynamic_cast< TagLib::RIFF::WAV::File * >(fileref.file())) { if (file->tag()) { readID3v2Tags(file->tag(), song, rg, img, lyrics, rating); } #ifdef TAGLIB_ASF_FOUND } else if (TagLib::ASF::File *file = dynamic_cast< TagLib::ASF::File * >(fileref.file())) { TagLib::ASF::Tag *tag = dynamic_cast< TagLib::ASF::Tag * >(file->tag()); if (tag) { readASFTags(tag, song, rating); } #endif } else if (TagLib::TrueAudio::File *file = dynamic_cast< TagLib::TrueAudio::File * >(fileref.file())) { if (file->ID3v2Tag(false)) { readID3v2Tags(file->ID3v2Tag(false), song, rg, img, lyrics, rating); // } else if (file->ID3v1Tag()) { // readID3v1Tags(fileref, song, rg); } } else if (TagLib::WavPack::File *file = dynamic_cast< TagLib::WavPack::File * >(fileref.file())) { if (file->APETag()) { readAPETags(file->APETag(), song, rg, img, rating); // } else if (file->ID3v1Tag()) { // readID3v1Tags(fileref, song, rg); } } if (song && song->genres[0].isEmpty()) { song->addGenre(tString2QString(tag->genre())); } } static bool writeTags(const TagLib::FileRef fileref, const Song &from, const Song &to, const RgTags &rg, const QByteArray &img, int rating, bool saveComment) { bool changed=false; TagLib::Tag *tag=fileref.tag(); if (/*!from.isEmpty() &&*/ !to.isEmpty()) { if (from.title!=to.title) { tag->setTitle(qString2TString(to.title)); changed=true; } if (from.artist!=to.artist) { tag->setArtist(qString2TString(to.artist)); changed=true; } if (from.album!=to.album) { tag->setAlbum(qString2TString(to.album)); changed=true; } // if (from.genre!=to.genre) { // tag->setGenre(qString2TString(to.genre)); // changed=true; // } if (from.track!=to.track) { tag->setTrack(to.track); changed=true; } if (from.year!=to.year) { tag->setYear(to.year); changed=true; } if (saveComment && from.comment()!=to.comment()) { tag->setComment(qString2TString(to.comment())); changed=true; } } if (TagLib::MPEG::File *file = dynamic_cast< TagLib::MPEG::File * >(fileref.file())) { if (file->ID3v2Tag() && !file->ID3v2Tag()->isEmpty()) { changed=writeID3v2Tags(file->ID3v2Tag(), from, to, rg, img, rating) || changed; } else if (file->APETag()) { changed=writeAPETags(file->APETag(), from, to, rg, rating) || changed; // } else if (file->ID3v1Tag()) { // changed=writeID3v1Tags(fileref, from, to, rg, img, rating) || changed; } else if (file->ID3v2Tag(true)) { changed=writeID3v2Tags(file->ID3v2Tag(), from, to, rg, img, rating) || changed; } } else if (TagLib::Ogg::Vorbis::File *file = dynamic_cast< TagLib::Ogg::Vorbis::File * >(fileref.file())) { if (file->tag()) { changed=writeVorbisCommentTags(file->tag(), from, to, rg, img, rating) || changed; } } else if (TagLib::Ogg::FLAC::File *file = dynamic_cast< TagLib::Ogg::FLAC::File * >(fileref.file())) { if (file->tag()) { changed=writeVorbisCommentTags(file->tag(), from, to, rg, img, rating) || changed; } } else if (TagLib::Ogg::Speex::File *file = dynamic_cast< TagLib::Ogg::Speex::File * >(fileref.file())) { if (file->tag()) { changed=writeVorbisCommentTags(file->tag(), from, to, rg, img, rating) || changed; } #ifdef TAGLIB_OPUS_FOUND } else if (TagLib::Ogg::Opus::File *file = dynamic_cast< TagLib::Ogg::Opus::File * >(fileref.file())) { if (file->tag()) { changed=writeVorbisCommentTags(file->tag(), from, to, rg, img, rating) || changed; } #endif } else if (TagLib::FLAC::File *file = dynamic_cast< TagLib::FLAC::File * >(fileref.file())) { if (file->xiphComment()) { changed=writeVorbisCommentTags(file->xiphComment(), from, to, rg, img, rating) || changed; } else if (file->ID3v2Tag() && !file->ID3v2Tag()->isEmpty()) { changed=writeID3v2Tags(file->ID3v2Tag(), from, to, rg, img, rating) || changed; // } else if (file->ID3v1Tag()) { // changed=writeID3v1Tags(fileref, from, to, rg, img, rating) || changed; } else if (file->ID3v2Tag(true)) { changed=writeID3v2Tags(file->ID3v2Tag(), from, to, rg, img, rating) || changed; } #ifdef TAGLIB_MP4_FOUND } else if (TagLib::MP4::File *file = dynamic_cast< TagLib::MP4::File * >(fileref.file())) { TagLib::MP4::Tag *tag = dynamic_cast(file->tag()); if (tag) { changed=writeMP4Tags(tag, from, to, rg, img, rating) || changed; } #endif } else if (TagLib::MPC::File *file = dynamic_cast< TagLib::MPC::File * >(fileref.file())) { if (file->APETag(true)) { changed=writeAPETags(file->APETag(), from, to, rg, rating) || changed; // } else if (file->ID3v1Tag()) { // changed=writeID3v1Tags(fileref, from, to, rg, img, rating) || changed; } } else if (TagLib::RIFF::AIFF::File *file = dynamic_cast< TagLib::RIFF::AIFF::File * >(fileref.file())) { if (file->tag()) { changed=writeID3v2Tags(file->tag(), from, to, rg, img, rating) || changed; } } else if (TagLib::RIFF::WAV::File *file = dynamic_cast< TagLib::RIFF::WAV::File * >(fileref.file())) { if (file->tag()) { changed=writeID3v2Tags(file->tag(), from, to, rg, img, rating) || changed; } #ifdef TAGLIB_ASF_FOUND } else if (TagLib::ASF::File *file = dynamic_cast< TagLib::ASF::File * >(fileref.file())) { TagLib::ASF::Tag *tag = dynamic_cast< TagLib::ASF::Tag * >(file->tag()); if (tag) { changed=writeASFTags(tag, from, to, rg, rating) || changed; } #endif } else if (TagLib::TrueAudio::File *file = dynamic_cast< TagLib::TrueAudio::File * >(fileref.file())) { if (file->ID3v2Tag(true)) { changed=writeID3v2Tags(file->ID3v2Tag(), from, to, rg, img, rating) || changed; // } else if (file->ID3v1Tag()) { // changed=writeID3v1Tags(fileref, from, to, rg, img, rating) || changed; } } else if (TagLib::WavPack::File *file = dynamic_cast< TagLib::WavPack::File * >(fileref.file())) { if (file->APETag(true)) { changed=writeAPETags(file->APETag(), from, to, rg, rating) || changed; // } else if (file->ID3v1Tag()) { // changed=writeID3v1Tags(fileref, from, to, rg, img, rating) || changed; } } return changed; } Song read(const QString &fileName) { Song song; TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return song; } readTags(fileref, &song, 0, 0, 0, 0); song.file=fileName; song.time=fileref.audioProperties() ? fileref.audioProperties()->length() : 0; return song; } QImage readImage(const QString &fileName) { QImage img; TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return img; } readTags(fileref, 0, 0, &img, 0, 0); return img; } QString readLyrics(const QString &fileName) { QString lyrics; TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return lyrics; } readTags(fileref, 0, 0, 0, &lyrics, 0); return lyrics; } QString readComment(const QString &fileName) { TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? QString() : tString2QString(fileref.tag()->comment()); } static Update update(const TagLib::FileRef fileref, const Song &from, const Song &to, const RgTags &rg, const QByteArray &img, int id3Ver=-1, bool saveComment=false, int rating=-1) { if (writeTags(fileref, from, to, rg, img, rating, saveComment)) { TagLib::MPEG::File *mpeg=dynamic_cast(fileref.file()); if (mpeg) { #ifdef TAGLIB_CAN_SAVE_ID3VER TagLib::ID3v2::Tag *v2=mpeg->ID3v2Tag(false); bool isID3v24=v2 && isId3V24(v2->header()); int ver=id3Ver==3 ? 3 : (id3Ver==4 ? 4 : (isID3v24 ? 4 : 3)); DBUG << "isID3v24" << isID3v24 << "reqVer:" << id3Ver << "use:" << ver; return mpeg->save(TagLib::MPEG::File::ID3v2, true, ver) ? Update_Modified : Update_Failed; #else Q_UNUSED(id3Ver) return mpeg->save(TagLib::MPEG::File::ID3v2, true) ? Update_Modified : Update_Failed; #endif } return fileref.file()->save() ? Update_Modified : Update_Failed; } return Update_None; } Update updateArtistAndTitle(const QString &fileName, const Song &song) { TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return Update_Failed; } TagLib::MPEG::File *mpeg=dynamic_cast(fileref.file()); TagLib::Tag *tag=fileref.tag(); tag->setTitle(qString2TString(song.title)); tag->setArtist(qString2TString(song.artist)); if (mpeg) { #ifdef TAGLIB_CAN_SAVE_ID3VER TagLib::ID3v2::Tag *v2=mpeg->ID3v2Tag(false); int ver=v2 && isId3V24(v2->header()) ? 4 : 3; DBUG << "useId3ver:" << ver; return mpeg->save(TagLib::MPEG::File::ID3v2, true, ver) ? Update_Modified : Update_Failed; #else return mpeg->save(TagLib::MPEG::File::ID3v2, true) ? Update_Modified : Update_Failed; #endif } return fileref.file()->save() ? Update_Modified : Update_Failed; } Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver, bool saveComment) { TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, from, to, RgTags(), QByteArray(), id3Ver, saveComment); } ReplayGain readReplaygain(const QString &fileName) { TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return false; } ReplayGain rg; readTags(fileref, 0, &rg, 0, 0, 0); return rg; } Update updateReplaygain(const QString &fileName, const ReplayGain &rg) { TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, Song(), Song(), RgTags(rg), QByteArray()); } Update embedImage(const QString &fileName, const QByteArray &cover) { TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, Song(), Song(), RgTags(), cover); } QString oggMimeType(const QString &fileName) { #ifdef Q_OS_WIN const wchar_t*encodedName=reinterpret_cast(fileName.constData()); #else const char *encodedName=QFile::encodeName(fileName).constData(); #endif TagLib::Ogg::Vorbis::File vorbis(encodedName, false, TagLib::AudioProperties::Fast); if (vorbis.isValid()) { return QLatin1String("audio/x-vorbis+ogg"); } TagLib::Ogg::FLAC::File flac(encodedName, false, TagLib::AudioProperties::Fast); if (flac.isValid()) { return QLatin1String("audio/x-flac+ogg"); } TagLib::Ogg::Speex::File speex(encodedName, false, TagLib::AudioProperties::Fast); if (speex.isValid()) { return QLatin1String("audio/x-speex+ogg"); } #ifdef TAGLIB_OPUS_FOUND TagLib::Ogg::Opus::File opus(encodedName, false, TagLib::AudioProperties::Fast); if (opus.isValid()) { return QLatin1String("audio/x-opus+ogg"); } #endif return QLatin1String("audio/ogg"); } int readRating(const QString &fileName) { int rating=-1; TagLib::FileRef fileref = getFileRef(fileName); if (!fileref.isNull()) { readTags(fileref, 0, 0, 0, 0, &rating); } return rating; } Update updateRating(const QString &fileName, int rating) { TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, Song(), Song(), RgTags(), QByteArray(), -1, false, rating); } QMap readAll(const QString &fileName) { QMap allTags; TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { return allTags; } #if TAGLIB_VERSION >= CANTATA_MAKE_VERSION(1,8,0) TagLib::PropertyMap properties=fileref.file()->properties(); TagLib::PropertyMap::ConstIterator it = properties.begin(); TagLib::PropertyMap::ConstIterator end = properties.end(); for (; it!=end; ++it) { allTags.insert(tString2QString(it->first.upper()), tString2QString(it->second.toString(", "))); } if (fileref.audioProperties()) { TagLib::AudioProperties *properties = fileref.audioProperties(); allTags.insert(QLatin1String("X-AUDIO:BITRATE"), QString("%1 kb/s").arg(properties->bitrate())); allTags.insert(QLatin1String("X-AUDIO:SAMPLERATE"), QString("%1 Hz").arg(properties->sampleRate())); allTags.insert(QLatin1String("X-AUDIO:CHANNELS"), QString::number(properties->channels())); } #endif return allTags; } QString id3Genre(int id) { // Clementine: In theory, genre 0 is "blues"; in practice it's invalid. return 0==id ? QString() : tString2QString(TagLib::ID3v1::genre(id)); } } cantata-2.2.0/tags/tags.h000066400000000000000000000117031316350454000151760ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAGS_H #define TAGS_H #include "mpd-interface/song.h" #include "support/utils.h" #include "config.h" #include #include #include #ifndef CANTATA_TAG_SERVER #include "taghelperiface.h" #endif namespace Tags { struct ReplayGain { ReplayGain(double tg=0.0, double ag=0.0, double tp=0.0, double ap=0.0) : trackGain(tg) , albumGain(ag) , trackPeak(tp) , albumPeak(ap) { } bool isEmpty() const { return Utils::equal(trackGain, 0.0) && Utils::equal(trackPeak, 0.0) && Utils::equal(albumGain, 0.0) && Utils::equal(albumPeak, 0.0); } double trackGain; double albumGain; double trackPeak; double albumPeak; }; enum Update { Update_Failed, Update_None, Update_Modified, Update_BadFile }; void enableDebug(); #ifndef CANTATA_TAG_SERVER inline void init() { TagHelperIface::self(); } inline void stop() { TagHelperIface::self()->stop(); } inline Song read(const QString &fileName) { return TagHelperIface::self()->read(fileName); } inline QImage readImage(const QString &fileName) { return TagHelperIface::self()->readImage(fileName); } inline QString readLyrics(const QString &fileName) { return TagHelperIface::self()->readLyrics(fileName); } inline QString readComment(const QString &fileName) { return TagHelperIface::self()->readComment(fileName); } inline Update updateArtistAndTitle(const QString &fileName, const Song &song) { return (Update)TagHelperIface::self()->updateArtistAndTitle(fileName, song); } inline Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver=-1, bool saveComment=false) { return (Update)TagHelperIface::self()->update(fileName, from, to, id3Ver, saveComment); } inline ReplayGain readReplaygain(const QString &fileName) { return TagHelperIface::self()->readReplaygain(fileName); } inline Update updateReplaygain(const QString &fileName, const ReplayGain &rg) { return (Update)TagHelperIface::self()->updateReplaygain(fileName, rg); } inline Update embedImage(const QString &fileName, const QByteArray &cover) { return (Update)TagHelperIface::self()->embedImage(fileName, cover); } inline QString oggMimeType(const QString &fileName) { return TagHelperIface::self()->oggMimeType(fileName); } inline int readRating(const QString &fileName) { return TagHelperIface::self()->readRating(fileName); } inline Update updateRating(const QString &fileName, int rating) { return (Update)TagHelperIface::self()->updateRating(fileName, rating); } inline QMap readAll(const QString &fileName) { return TagHelperIface::self()->readAll(fileName); } #else inline void init() { } inline void stop() { } extern Song read(const QString &fileName); extern QImage readImage(const QString &fileName); extern QString readLyrics(const QString &fileName); extern QString readComment(const QString &fileName); extern Update updateArtistAndTitle(const QString &fileName, const Song &song); extern Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver=-1, bool saveComment=false); extern ReplayGain readReplaygain(const QString &fileName); extern Update updateReplaygain(const QString &fileName, const ReplayGain &rg); extern Update embedImage(const QString &fileName, const QByteArray &cover); extern QString oggMimeType(const QString &fileName); extern int readRating(const QString &fileName); extern Update updateRating(const QString &fileName, int rating); extern QMap readAll(const QString &fileName); #endif extern QString id3Genre(int id); } Q_DECLARE_METATYPE(Tags::ReplayGain) inline QDataStream & operator<<(QDataStream &stream, const Tags::ReplayGain &rg) { stream << rg.trackGain << rg.albumGain << rg.trackPeak << rg.albumPeak; return stream; } inline QDataStream & operator>>(QDataStream &stream, Tags::ReplayGain &rg) { stream >> rg.trackGain >> rg.albumGain >> rg.trackPeak >> rg.albumPeak; return stream; } #endif cantata-2.2.0/tags/trackorganiser.cpp000066400000000000000000000435531316350454000176210ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "trackorganiser.h" #include "devices/filenameschemedialog.h" #ifdef ENABLE_DEVICES_SUPPORT #include "models/devicesmodel.h" #endif #include "devices/device.h" #include "gui/settings.h" #include "mpd-interface/mpdconnection.h" #include "support/utils.h" #include "context/songview.h" #include "support/messagebox.h" #include "support/action.h" #include "widgets/icons.h" #include "widgets/basicitemdelegate.h" #include "mpd-interface/cuefile.h" #include "gui/covers.h" #include "context/contextwidget.h" #include #include #include #define REMOVE(w) \ w->setVisible(false); \ w->deleteLater(); \ w=0; static int iCount=0; int TrackOrganiser::instanceCount() { return iCount; } TrackOrganiser::TrackOrganiser(QWidget *parent) : SongDialog(parent, "TrackOrganiser", QSize(800, 500)) , schemeDlg(0) , autoSkip(false) , paused(false) , updated(false) , alwaysUpdate(false) { iCount++; setButtons(Ok|Cancel); setCaption(tr("Organize Files")); setAttribute(Qt::WA_DeleteOnClose); QWidget *mainWidet = new QWidget(this); setupUi(mainWidet); setMainWidget(mainWidet); configFilename->setIcon(Icons::self()->configureIcon); setButtonGuiItem(Ok, GuiItem(tr("Rename"))); connect(this, SIGNAL(update()), MPDConnection::self(), SLOT(update())); progress->setVisible(false); files->setItemDelegate(new BasicItemDelegate(files)); files->setAlternatingRowColors(false); files->setContextMenuPolicy(Qt::ActionsContextMenu); files->setSelectionMode(QAbstractItemView::ExtendedSelection); removeAct=new Action(tr("Remove From List"), files); removeAct->setEnabled(false); files->addAction(removeAct); connect(files, SIGNAL(itemSelectionChanged()), SLOT(controlRemoveAct())); connect(removeAct, SIGNAL(triggered()), SLOT(removeItems())); } TrackOrganiser::~TrackOrganiser() { iCount--; } void TrackOrganiser::show(const QList &songs, const QString &udi, bool forceUpdate) { // If we are called from the TagEditor dialog, then forceUpdate will be true. This is so that we dont do 2 // MPD updates (one from TagEditor, and one from here!) alwaysUpdate=forceUpdate; foreach (const Song &s, songs) { if (!CueFile::isCue(s.file)) { origSongs.append(s); } } if (origSongs.isEmpty()) { deleteLater(); if (alwaysUpdate) { doUpdate(); } return; } QString musicFolder; #ifdef ENABLE_DEVICES_SUPPORT if (udi.isEmpty()) { musicFolder=MPDConnection::self()->getDetails().dir; opts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); } else { deviceUdi=udi; Device *dev=getDevice(parentWidget()); if (!dev) { deleteLater(); return; } opts=dev->options(); musicFolder=dev->path(); } #else opts.load(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); musicFolder=MPDConnection::self()->getDetails().dir; #endif qSort(origSongs); filenameScheme->setText(opts.scheme); vfatSafe->setChecked(opts.vfatSafe); asciiOnly->setChecked(opts.asciiOnly); ignoreThe->setChecked(opts.ignoreThe); replaceSpaces->setChecked(opts.replaceSpaces); connect(configFilename, SIGNAL(clicked()), SLOT(configureFilenameScheme())); connect(filenameScheme, SIGNAL(textChanged(const QString &)), this, SLOT(updateView())); connect(vfatSafe, SIGNAL(toggled(bool)), this, SLOT(updateView())); connect(asciiOnly, SIGNAL(toggled(bool)), this, SLOT(updateView())); connect(ignoreThe, SIGNAL(toggled(bool)), this, SLOT(updateView())); connect(replaceSpaces, SIGNAL(toggled(bool)), this, SLOT(updateView())); if (!songsOk(origSongs, musicFolder, udi.isEmpty())) { return; } connect(ratingsNote, SIGNAL(leftClickedUrl()), SLOT(showRatingsMessage())); Dialog::show(); enableButtonOk(false); updateView(); } void TrackOrganiser::slotButtonClicked(int button) { switch (button) { case Ok: startRename(); break; case Cancel: if (!optionsBox->isEnabled()) { paused=true; if (MessageBox::No==MessageBox::questionYesNo(this, tr("Abort renaming of files?"), tr("Abort"), GuiItem(tr("Abort")), StdGuiItem::cancel())) { paused=false; QTimer::singleShot(0, this, SLOT(renameFile())); return; } } finish(false); // Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!! Dialog::slotButtonClicked(button); break; default: break; } } void TrackOrganiser::configureFilenameScheme() { if (!schemeDlg) { schemeDlg=new FilenameSchemeDialog(this); connect(schemeDlg, SIGNAL(scheme(const QString &)), this, SLOT(setFilenameScheme(const QString &))); } readOptions(); schemeDlg->show(opts); } void TrackOrganiser::readOptions() { opts.scheme=filenameScheme->text().trimmed(); opts.vfatSafe=vfatSafe->isChecked(); opts.asciiOnly=asciiOnly->isChecked(); opts.ignoreThe=ignoreThe->isChecked(); opts.replaceSpaces=replaceSpaces->isChecked(); } void TrackOrganiser::updateView() { QFont f(font()); f.setItalic(true); files->clear(); bool different=false; readOptions(); foreach (const Song &s, origSongs) { QString modified=opts.createFilename(s); //different=different||(modified!=s.file); QString orig=s.filePath(); bool diff=modified!=orig; different|=diff; QTreeWidgetItem *item=new QTreeWidgetItem(files, QStringList() << orig << modified); if (diff) { item->setFont(0, f); item->setFont(1, f); } } files->resizeColumnToContents(0); files->resizeColumnToContents(1); enableButtonOk(different); } void TrackOrganiser::startRename() { optionsBox->setEnabled(false); progress->setVisible(true); progress->setRange(1, origSongs.count()); enableButtonOk(false); index=0; paused=autoSkip=false; saveOptions(); QTimer::singleShot(100, this, SLOT(renameFile())); } void TrackOrganiser::renameFile() { if (paused) { return; } progress->setValue(progress->value()+1); QTreeWidgetItem *item=files->topLevelItem(index); files->scrollToItem(item); Song s=origSongs.at(index); QString modified=opts.createFilename(s); QString musicFolder; #ifdef ENABLE_DEVICES_SUPPORT if (!deviceUdi.isEmpty()) { Device *dev=getDevice(); if (!dev) { return; } musicFolder=dev->path(); } else #endif musicFolder=MPDConnection::self()->getDetails().dir; QString orig=s.filePath(); if (modified!=orig) { QString source=musicFolder+orig; QString dest=musicFolder+modified; bool skip=false; if (!QFile::exists(source)) { if (autoSkip) { skip=true; } else { switch(MessageBox::questionYesNoCancel(this, tr("Source file does not exist!")+QLatin1String("\n\n")+dest, QString(), GuiItem(tr("Skip")), GuiItem(tr("Auto Skip")))) { case MessageBox::Yes: skip=true; break; case MessageBox::No: autoSkip=skip=true; break; case MessageBox::Cancel: finish(false); return; } } } // Check if dest exists... if (!skip && QFile::exists(dest)) { if (autoSkip) { skip=true; } else { switch(MessageBox::questionYesNoCancel(this, tr("Destination file already exists!")+QLatin1String("\n\n")+dest, QString(), GuiItem(tr("Skip")), GuiItem(tr("Auto Skip")))) { case MessageBox::Yes: skip=true; break; case MessageBox::No: autoSkip=skip=true; break; case MessageBox::Cancel: finish(false); return; } } } // Create dest folder... if (!skip) { QDir dir(Utils::getDir(dest)); if(!dir.exists() && !Utils::createWorldReadableDir(dir.absolutePath(), musicFolder)) { if (autoSkip) { skip=true; } else { switch(MessageBox::questionYesNoCancel(this, tr("Failed to create destination folder!")+QLatin1String("\n\n")+dir.absolutePath(), QString(), GuiItem(tr("Skip")), GuiItem(tr("Auto Skip")))) { case MessageBox::Yes: skip=true; break; case MessageBox::No: autoSkip=skip=true; break; case MessageBox::Cancel: finish(false); return; } } } } bool renamed=false; if (!skip && !(renamed=QFile::rename(source, dest))) { if (autoSkip) { skip=true; } else { switch(MessageBox::questionYesNoCancel(this, tr("Failed to rename '%1' to '%2'").arg(source, dest), QString(), GuiItem(tr("Skip")), GuiItem(tr("Auto Skip")))) { case MessageBox::Yes: skip=true; break; case MessageBox::No: autoSkip=skip=true; break; case MessageBox::Cancel: finish(false); return; } } } // If file was renamed, then also rename any lyrics file... if (renamed) { QString lyricsSource=Utils::changeExtension(source, SongView::constExtension); QString lyricsDest=Utils::changeExtension(dest, SongView::constExtension); if (QFile::exists(lyricsSource) && !QFile::exists(lyricsDest)) { QFile::rename(lyricsSource, lyricsDest); } } if (!skip) { QDir sDir(Utils::getDir(source)); QDir sArtistDir(sDir); sArtistDir.cdUp(); QDir dDir(Utils::getDir(dest)); #ifdef ENABLE_DEVICES_SUPPORT Device *dev=deviceUdi.isEmpty() ? 0 : getDevice(); if (sDir.absolutePath()!=dDir.absolutePath()) { Device::moveDir(sDir.absolutePath(), dDir.absolutePath(), musicFolder, dev ? dev->coverFile() : QString(Covers::albumFileName(s)+QLatin1String(".jpg"))); } #else if (sDir.absolutePath()!=dDir.absolutePath()) { Device::moveDir(sDir.absolutePath(), dDir.absolutePath(), musicFolder, QString(Covers::albumFileName(s)+QLatin1String(".jpg"))); } #endif QDir dArtistDir(dDir); dArtistDir.cdUp(); // Move any artist, or backdrop, image... if (sArtistDir.exists() && dArtistDir.exists() && sArtistDir.absolutePath()!=sDir.absolutePath() && sArtistDir.absolutePath()!=dArtistDir.absolutePath()) { QStringList artistImages; QFileInfoList entries=sArtistDir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); QString artistImage=Covers::artistFileName(s); QSet acceptable=QSet() << artistImage+QLatin1String(".jpg") << artistImage+QLatin1String(".png") << ContextWidget::constBackdropFileName+QLatin1String(".jpg") << ContextWidget::constBackdropFileName+QLatin1String(".png"); foreach (const QFileInfo &entry, entries) { if (entry.isDir() || !acceptable.contains(entry.fileName())) { artistImages.clear(); break; } else { artistImages.append(entry.fileName()); } } if (!artistImages.isEmpty()) { bool delDir=true; foreach (const QString &f, artistImages) { if (!QFile::rename(sArtistDir.absolutePath()+Utils::constDirSep+f, dArtistDir.absolutePath()+Utils::constDirSep+f)) { delDir=false; break; } } if (delDir) { QString dirName=sArtistDir.dirName(); if (!dirName.isEmpty()) { sArtistDir.cdUp(); sArtistDir.rmdir(dirName); } } } } item->setText(0, modified); item->setFont(0, font()); item->setFont(1, font()); Song to=s; QString origPath; if (s.file.startsWith(Song::constMopidyLocal)) { origPath=to.file; to.file=Song::encodePath(to.file); } else { to.file=modified; } origSongs.replace(index, to); updated=true; if (deviceUdi.isEmpty()) { // MusicLibraryModel::self()->updateSongFile(s, to); // DirViewModel::self()->removeFileFromList(s.file); // DirViewModel::self()->addFileToList(origPath.isEmpty() ? to.file : origPath, // origPath.isEmpty() ? QString() : to.file); } #ifdef ENABLE_DEVICES_SUPPORT else { if (!dev) { return; } dev->updateSongFile(s, to); } #endif } } index++; if (index>=origSongs.count()) { finish(true); } else { QTimer::singleShot(100, this, SLOT(renameFile())); } } void TrackOrganiser::controlRemoveAct() { removeAct->setEnabled(files->topLevelItemCount()>1 && !files->selectedItems().isEmpty()); } void TrackOrganiser::removeItems() { if (files->topLevelItemCount()<1) { return; } if (MessageBox::Yes==MessageBox::questionYesNo(this, tr("Remove the selected tracks from the list?"), tr("Remove Tracks"), StdGuiItem::remove(), StdGuiItem::cancel())) { QList selection=files->selectedItems(); foreach (QTreeWidgetItem *item, selection) { int idx=files->indexOfTopLevelItem(item); if (idx>-1 && idxtakeTopLevelItem(idx); } } } } void TrackOrganiser::showRatingsMessage() { MessageBox::information(this, tr("Song ratings are not stored in the song files, but within MPD's 'sticker' database.\n\n" "If you rename a file (or the folder it is within), then the rating associated with the song will be lost."), QLatin1String("Ratings")); } void TrackOrganiser::setFilenameScheme(const QString &text) { if (filenameScheme->text()!=text) { filenameScheme->setText(text); saveOptions(); } } void TrackOrganiser::saveOptions() { readOptions(); #ifdef ENABLE_DEVICES_SUPPORT if (!deviceUdi.isEmpty()) { Device *dev=getDevice(); if (!dev) { return; } dev->setOptions(opts); } else #endif opts.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true); } void TrackOrganiser::doUpdate() { if (deviceUdi.isEmpty()) { emit update(); } #ifdef ENABLE_DEVICES_SUPPORT else { Device *dev=getDevice(); if (dev) { dev->saveCache(); } } #endif } void TrackOrganiser::finish(bool ok) { if (updated || alwaysUpdate) { doUpdate(); } if (ok) { accept(); } else { reject(); } } #ifdef ENABLE_DEVICES_SUPPORT Device * TrackOrganiser::getDevice(QWidget *p) { Device *dev=DevicesModel::self()->device(deviceUdi); if (!dev) { MessageBox::error(p ? p : this, tr("Device has been removed!")); reject(); return 0; } if (!dev->isConnected()) { MessageBox::error(p ? p : this, tr("Device is not connected.")); reject(); return 0; } if (!dev->isIdle()) { MessageBox::error(p ? p : this, tr("Device is busy?")); reject(); return 0; } return dev; } #endif cantata-2.2.0/tags/trackorganiser.h000066400000000000000000000043541316350454000172620ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TRACKORGANISER_H #define TRACKORGANISER_H #include "ui_trackorganiser.h" #include "widgets/songdialog.h" #include "config.h" #ifdef ENABLE_DEVICES_SUPPORT #include "devices/device.h" #else #include "devices/deviceoptions.h" #endif class FilenameSchemeDialog; class Action; class TrackOrganiser : public SongDialog, Ui::TrackOrganiser { Q_OBJECT public: static int instanceCount(); TrackOrganiser(QWidget *parent); virtual ~TrackOrganiser(); void show(const QList &songs, const QString &udi, bool forceUpdate=false); Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void update(); private Q_SLOTS: void configureFilenameScheme(); void updateView(); void startRename(); void renameFile(); void controlRemoveAct(); void removeItems(); void showRatingsMessage(); void setFilenameScheme(const QString &text); private: void saveOptions(); void slotButtonClicked(int button); void readOptions(); #ifdef ENABLE_DEVICES_SUPPORT Device * getDevice(QWidget *p=0); #endif void doUpdate(); void finish(bool ok); private: FilenameSchemeDialog *schemeDlg; QList origSongs; QString deviceUdi; Action *removeAct; int index; bool autoSkip; bool paused; bool updated; bool alwaysUpdate; DeviceOptions opts; }; #endif cantata-2.2.0/tags/trackorganiser.ui000066400000000000000000000112131316350454000174400ustar00rootroot00000000000000 TrackOrganiser 0 0 551 337 0 0 Filenames QFormLayout::ExpandingFieldsGrow 0 288 0 true true Filename scheme: filenameScheme VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names true false true true Original Name New Name Ratings will be lost if a file is renamed. LineEdit QLineEdit
    support/lineedit.h
    BuddyLabel QLabel
    support/buddylabel.h
    UrlNoteLabel QLabel
    widgets/notelabel.h
    vfatSafe asciiOnly replaceSpaces ignoreThe filenameScheme configFilename files
    cantata-2.2.0/translations/000077500000000000000000000000001316350454000156505ustar00rootroot00000000000000cantata-2.2.0/translations/CMakeLists.txt000066400000000000000000000024021316350454000204060ustar00rootroot00000000000000file(GLOB TS_FILES *.ts) foreach (TS_FILE ${TS_FILES}) get_filename_component(TRANS ${TS_FILE} NAME_WE) list(APPEND CANTATA_TRANS "${TRANS}") endforeach () find_program(LRELEASE_EXECUTABLE lrelease) if (LRELEASE_EXECUTABLE) set(catalogname cantata) add_custom_target(translations ALL) foreach(TRANS ${CANTATA_TRANS}) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${TRANS}.ts) set(CUR_TS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${TRANS}.ts) set(CUR_QM_FILE ${CMAKE_CURRENT_BINARY_DIR}/${TRANS}.qm) add_custom_command(TARGET translations COMMAND ${LRELEASE_EXECUTABLE} ARGS ${CUR_TS_FILE} -qm ${CUR_QM_FILE} DEPENDS ${CUR_TS_FILE}) if (WIN32) install(FILES ${CUR_QM_FILE} DESTINATION ${CMAKE_INSTALL_PREFIX}/translations/) elseif (APPLE) install(FILES ${CUR_QM_FILE} DESTINATION ${MACOSX_BUNDLE_RESOURCES}/translations/) else () install(FILES ${CUR_QM_FILE} DESTINATION ${SHARE_INSTALL_PREFIX}/cantata/translations/) endif () endif () endforeach() else () message("------\n" "NOTE: lrelease not found. Translations will *not* be installed\n" "------\n") endif () cantata-2.2.0/translations/blank.ts000066400000000000000000012743541316350454000173270ustar00rootroot00000000000000 ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Configure (Needs configuring) Copy songs to: Destination format: Overwrite songs To copy: <b>INVALID</b> <i>(When different)</i> Artists:%1, Albums:%2, Songs:%3 %1 free Local Music Library Audio CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Copy Songs To Library Copy Songs To Device Copy Songs Delete Songs You have not configured the destination device. Continue with the default settings? Not Configured Use Defaults You have not configured the source device. Continue with the default settings? Are you sure you wish to stop? Stop Device has been removed! Device is not connected! Device is busy? Device has been changed? Clearing unused folders Calculate ReplayGain for ripped tracks? ReplayGain Calculate The destination filename already exists! Song already exists! Song does not exist! Failed to create destination folder!<br/>Please check you have sufficient permissions. Source file no longer exists? Failed to copy. Failed to delete. Not connected to device. Selected codec is not available. Transcoding failed. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Failed to read source file. Failed to write to destination file. No space left on device. Failed to update metadata. Failed to download track. Failed to lock device. Local Music Library Properties Error Skip Auto Skip Retry Artist: Album: Track: Source file: Destination file: File: Saving cache AlbumDetails Album Details Artist: Composer: Title: Genre: Year: Disc: Single artist Tracks Track Artist Title AlbumDetailsDialog Audio CD Apply "Various Artists" Workaround Revert "Various Artists" Workaround Capitalize Adjust Track Numbers Tools Apply "Various Artists" workaround? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Revert Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Adjust track number by: AlbumView Refresh Album Information Album Tracks ArtistView Refresh Artist Information Artist Albums Web Links Similar Artists AudioCdDevice Reading disc %n Tracks (%1) AudioCdSettings Album and Track Information Retrieval Initially look up via: CDDB Host: CDDB Port: Lookup information as soon as CD is inserted Audio Extraction Full paranoia mode (best quality) Never skip on read error CDDB MusicBrainz BrowseModel Cue Sheet Playlist CacheItem Deleting... Calculating... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Scaled Covers Backdrops Lyrics Artist Information Album Information Track Information Stream Listings Podcast Directories Wikipedia Languages Scrobble Tracks Delete All Delete all '%1' items? Delete Cache Items Delete items from all selected categories? CacheTree Name Item Count Space Used CddbInterface Data Track Failed to open CD device Track %1 Failed to create CDDB connection Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB CDDB error: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Artist Title Disc Selection %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 (%3) artist - album (year) ContextSettings Lyrics Providers Wikipedia Languages Other ContextWidget &Artist Al&bum &Track CoverDialog Search Add a local file Configure This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive An image already exists for this artist, and the file is not writeable. A cover already exists for this album, and the file is not writeable. '%1' Artist Image '%1 - %2' Album Cover 'Artist - Album' Album Cover Failed to set cover! Could not download to temporary file! Failed to download image! Load Local Cover Images (*.png *.jpg) File is already in list! Failed to read image! Display Remove Failed to set cover! Could not make copy! Failed to set cover! Could not backup original! Failed to set cover! Could not copy file to '%1'! Searching... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> CoverPreview Image Downloading... Image (%1 x %2 %3%) Image (width x height zoom%) CustomActionDialog Name: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Edit Remove Name Command Remove the selected commands? Device Updating (%1)... Updating (%1%)... DevicePropertiesDialog Device Properties DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Name: Music folder: Copy album covers as: Maximum cover size: Default volume: 'Various Artists' workaround Automatically scan music when attached Use cache Filenames Filename scheme: VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Only transcode if source file is of a different format Only transcode if source is FLAC/WAV Don't copy covers Embed cover within each file No maximum size 400 pixels 300 pixels 200 pixels 100 pixels <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> Do not transcode Encoder Transcode to %1 %1 (%2 free) name (size free) DevicesModel Configure Device Refresh Device Connect Device Disconnect Device Edit CD Details Not Connected No Devices Attached DevicesPage Copy To Library Synchronise Forget Device Add Device Lookup album and track details? Refresh Via CDDB Via MusicBrainz Which type of refresh do you wish to perform? Partial - Only new songs are scanned (quick) Full - All songs are rescanned (slow) Partial Full Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs Are you sure you wish to forget '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Eject Are you sure you wish to disconnect '%1'? Disconnect Please close other dialogs first. DigitallyImported Not logged in Logged in Unknown error No subscriptions You do not have an active subscription Logged in (expiry:%1) Session expired DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Premium Account Username: Password: Stream type: Status: Login Session expiry: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Digitally Imported Settings MP3 256k AAC 64k AAC 128k Not Authenticated Authenticating... Authenticated Logout DockMenu Play Pause DynamicPlaylists Start Dynamic Playlist Stop Dynamic Mode Dynamic Playlists Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Failed to locate rules file - %1 Failed to remove previous rules file - %1 Failed to install rules file - %1 -> %2 Dynamizer has been terminated. Awaiting response for previous command. (%1) Saving rule Deleting rule Failed to save %1. (%2) Failed to delete rules file. (%1) Failed to control dynamizer state. (%1) Failed to set the current dynamic rules. (%1) DynamicPlaylistsPage Add Edit Remove Remote dynamizer is not running. Are you sure you wish to remove the selected rules? This cannot be undone. Remove Dynamic Rules FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: About filename schemes The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Album Artist The name of the album. Album Title The composer. Composer The artist of each track. Track Artist The track title (without <i>Track Artist</i>). Track Title The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Track Title (+Artist) The track number. Track # The album number of a multi-album album. Often compilations consist of several albums. CD # The year of the album's release. Year The genre of the album. Genre Filename Scheme Various Artists Example album artist Wibble Example artist Vivaldi Example composer Now 5001 Example album Wobble Example song name Dance Example genre The following variables will be replaced with their corresponding meaning for each track name. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> FolderPage Open In File Manager Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs FsDevice Updating... Reading cache Saving cache %1 %2% Message percent GenreCombo Filter On Genre All Genres GroupedViewDelegate Audio CD Streams %n Track(s) InitialSettingsWizard Cantata First Run Welcome to Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Host: Password: Music folder: Connect The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Music folder Please choose the folder containing your music collection. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Finished! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. Not Connected Connection Established Connection Failed Cantata will now terminate InputDialog Password Please enter password: InterfaceSettings Sidebar Views Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Style: Position: Only show icons, no text Auto-hide Play Queue Initially collapse albums Automatically expand current album Scroll to current track Prompt before clearing Separate action (and shortcut) for play queue search Background Image None Current album cover Custom image: Blur: 10px Opacity: 40% Toolbar Show stop button Show cover of current track Show track rating External Enable MPRIS D-BUS interface Show popup messages when changing tracks Show icon in notification area Minimize to notification area when closed On Start-up Show main window Hide main window Restore previous state Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General Fetch missing covers from Last.fm Show delete action in context menus Enforce single-click activation of items Show song information tooltips Language: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Changing the style setting will require a re-start of Cantata. Library Folders Playlists Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Search (via MPD) Info - Current song information (artist, album, and lyrics) Large Small Tab-bar Left Right Top Bottom Images (*.png *.jpg) 10px pixels Notifications English (en) System default %1% value% %1 px pixels ItemView Go Back Updating... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings MP3 Ogg Streaming format: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Year Album, Artist, Year Album, Year, Artist Artist, Album, Year Artist, Year, Album Year, Album, Artist Year, Artist, Album Modified Date Group By Genre Artist Album Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs LyricSettings Choose the websites you want to use when searching for lyrics. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Title: Artist: Search For Lyrics MPDConnection Unknown Connection to %1 failed Connection to %1 failed - please check your proxy settings Connection to %1 failed - incorrect password Connecting to %1 Failed to send command to %1 - not connected Failed to load. Please check user "mpd" has read permission. Failed to load. MPD can only play local files if connected via a local socket. MPD reported the following error: %1 Failed to send command. Disconnected from %1 Failed to rename <b>%1</b> to <b>%2</b> Failed to save <b>%1</b> You cannot add parts of a cue sheet to a playlist! You cannot add a playlist to another playlist! Failed to send '%1' to %2. Please check %2 is registered with MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Streaming MP3 128k MP3 VBR Ogg Vorbis FLAC WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Username: Password: Membership: Downloads: MainWindow [Dynamic] Exit Full Screen Configure Cantata... Preferences Quit About Cantata... Show Window Server information... Refresh Database Refresh Connect Collection Outputs Stop After Track Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist Crop Others Add Stream URL Clear Center On Current Track Expanded Interface Show Current Song Information Full Screen Random Repeat Single When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Consume When consume is activated, a song is removed from the play queue after it has been played. Find in Play Queue Play Stream Locate In Library Play next Edit Track Information (Play Queue) Expand All Collapse All Cancel Play Queue Library Folders Playlists Internet Devices Search Info &Music &Edit &View &Queue &Help Set Rating No Rating Failed to locate any songs matching the dynamic playlist rules. Connecting to %1 Refresh MPD Database? About Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Abort download and quit Please close other dialogs first. Enabled: %1 Disabled: %1 Server Information <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> Cantata (%1) MPD reported the following error: %1 Cantata Playback stopped Remove all songs from play queue? Priority Enter priority (0..255): Decrease priority for each subsequent track Playlist Name Enter a name for the playlist: '%1' is used to store favorite streams, please choose another name. A playlist named '%1' already exists! Add to that playlist? Existing Playlist %n Track(s) %n Tracks (%1) MenuButton Menu MessageOverlay Cancel Mpris (Stream) MtpConnection Connecting to device... No devices found Connected to device Disconnected from device Updating folders... Updating tracks... MtpDevice Not Connected %1 free MusicBrainz Failed to open CD device Track %1 %1 (Disc %2) No matches found in MusicBrainz MusicLibraryModel Cue Sheet Playlist %n Track(s) %n Artist(s) %n Album(s) %n Tracks (%1) %1 by %2 Album by Artist NoteLabel <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) OSXStyle &Window Close Minimize Zoom OnlineDbService Downloading...%1% Parsing music list.... Failed to download %n Artist(s) OnlineDbWidget Group By Genre Artist Configure The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Re-download music listing? OnlineSearchService Searching... OnlineSearchWidget No tracks found. %n Tracks (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Configure Service OnlineView Song Information OnlineXmlParser Failed to parse OpmlBrowsePage Reload Failed to download directory listing Failed to parse directory listing OtherSettings Background Image None Artist image Custom image: Blur: 10px Opacity: 40% Automatically switch to view after: Do not auto-switch ms Dark background Darken background, and use white text, regardless of current color palette. Always collapse into a single pane Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Images (*.png *.jpg) 10px pixels %1% value% %1 px pixels PathRequester Select Folder Select File PlayQueueModel Title Artist Album # Track number Length Disc Year Original Year Genre Priority Composer Performer Rating Remove Duplicates Undo Redo Shuffle Tracks Albums Sort By Album Artist Track Title Track Number # (Track Number) PlayQueueView Remove PlaybackSettings Playback Fa&deout on stop: None ms Stop playback on exit Inhibit suspend whilst playing If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Output <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> &Crossfade between tracks: s Replay &gain: About replay gain Use the checkboxes below to control the active outputs. Track Album Auto <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> PlaylistRule Type: Include songs that match the following: Exclude songs that match the following: Artist: Artists similar to: Album Artist: Composer: Album: Title: Genre From Year: Any To Year: Comment: Filename / path: Exact match Only enter values for the tags you wish to be search on. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Smart Rule Add <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules Add Edit Remove Songs with ratings between: - Songs with duration between: seconds Number of songs in play queue: Order songs: About Rules PlaylistRulesDialog Dynamic Rules Smart Rules No Limit Ascending Descending Name of Smart Rules Number of songs About dynamic rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 A set of rules named '%1' already exists! Overwrite? Overwrite Rules Saving %1 PlaylistsModel New Playlist... Stored Playlists Standard playlists %n Tracks (%1) Smart Playlist PodcastPage RSS: Website: Podcast details Select a podcast to display its details PodcastSearchDialog Subscribe Enter URL Manual podcast URL Search %1 Search for podcasts on %1 Add Podcast Subscription Browse %1 Browse %1 podcasts You are already subscribed to this podcast! Subscription added PodcastSearchPage Enter search term... Search Failed to fetch podcasts from %1 There was a problem parsing the response from %1 PodcastService Subscribe to RSS feeds %n Podcast(s) %1 (%2) podcast name (num unplayed episodes) %n Episode(s) (Downloading: %1%) Failed to parse %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Failed to download %1 PodcastSettingsDialog Check for new episodes: Download episodes to: Download automatically: Podcast Settings Manually Every 15 minutes Every 30 minutes Every hour Every 2 hours Every 6 hours Every 12 hours Every day Every week Don't automatically download episodes Latest episode Latest %1 episodes All episodes PodcastUrlPage URL Enter podcast URL... Load Enter podcast URL below, and press 'Load' Invalid URL! Failed to fetch podcast! Failed to parse podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. PodcastWidget Add Subscription Remove Subscription Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Do you wish to download the selected podcast episodes? Cancel podcast episode downloads (both current and any that are queued)? Do you wish to the delete downloaded files of the selected podcast episodes? Do you wish to mark the selected podcast episodes as new? Do you wish to mark the selected podcast episodes as listened? Refresh all subscriptions? Refresh Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track PreferencesDialog Collection Collection Settings Playback Playback Settings Downloaded Files Downloaded Files Settings Interface Interface Settings Info Info View Settings Scrobbling Scrobbling Settings Audio CD Audio CD Settings Proxy Proxy Settings Shortcuts Keyboard Shortcut Settings Cache Cached Items Custom Actions Cantata Preferences Configure ProxySettings Mode: Type: HTTP Proxy SOCKS Proxy Host: Port: Username: Password: No proxy Use the system proxy settings Manual proxy configuration QObject Track listing Read more on wikipedia Open in browser <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Expected average bitrate for variable bitrate encoding Smaller file Better sound quality <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Quality rating Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Bitrate Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Compression level Faster compression Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Empty filename. Invalid filename. (%1) Failed to save %1. Failed to delete rules file. (%1) Invalid command. (%1) Could not remove active rules link. Active rules is not a link. Could not create active rules link. Rules file, %1, does not exist. Incorrect arguments supplied. Unknown method called. Unknown error Artist SimilarArtists AlbumArtist Composer Comment Album Title Genre Date File Include Exclude (Exact) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 name width x height Current Cover CoverArt Archive Grouped Albums Table Parse in Library view, and show in Folders view Only show in Folders view Do not list Previous Track Next Track Play/Pause Stop Stop After Current Track Stop After Track Increase Volume Decrease Volume Save As Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Set Priority Highest Priority (255) High Priority (200) Medium Priority (125) Low Priority (50) Default Priority (0) Custom Priority... Add To Playlist Organize Files Edit Track Information ReplayGain Copy Songs To Device Delete Songs Set Image Remove Find Add To Play Queue Parse error loading cache file, please check your songs tags. Other Default "%1" (%2:%3) name (host:port) Single Tracks Personal Unknown Various Artists Album artist Performer Track number Disc number Year Orignal Year Length <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album Invalid service Invalid method Authentication failed Invalid format Invalid parameters Invalid resource specified Operation failed Invalid session key Invalid API key Service offline Last.fm is currently busy, please try again in a few minutes Rate-limit exceeded General Digitally Imported Local and National Radio (ListenLive) &OK &Cancel &Yes &No &Discard &Save &Apply &Close &Help &Overwrite &Reset &Continue &Delete &Stop &Remove &Previous &Next Close Error Information Warning Question %1 B %1 kB %1 MB %1 GB %1 KiB %1 MiB %1 GiB Basic Tree (No Icons) Simple Tree Detailed Tree List Grid RemoteDevicePropertiesDialog Device Properties Connection Music Library Add Device A remote device named '%1' already exists! Please choose a different name. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Type: Name: Options Host: Port: User: Domain: Password: Share: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Service name: Folder: Extra Options: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Samba Share Samba Share (Auto-discover host and port) Secure Shell (sshfs) Locally Mounted Folder RemoteFsDevice Available Not Available Failed to resolve connection details for %1 Connecting... Password prompting does not work when cantata is started from the commandline. No suitable ssh-askpass application installed! This is required for entering passwords. Mount point ("%1") is not empty! "sshfs" is not installed! Disconnecting... "fusermount" is not installed! Failed to connect to "%1" Failed to disconnect from "%1" Updating tracks... Not Connected Capacity Unknown %1 free RgDialog ReplayGain Show All Tracks Show Untagged Tracks Remove From List Artist Album Title Album Gain Track Gain Album Peak Track Peak Scan Update ReplayGain tags in tracks? Update Tags Abort scanning of tracks? Abort Abort reading of existing tags? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Do you wish to scan all tracks, or only tracks without existing tags? Untagged Tracks All Tracks Scanning tracks... Reading existing tags... %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: Device has been removed! Device is not connected. Device is busy? %1 dB Failed Original: %1 dB Original: %1 Remove the selected tracks from the list? Remove Tracks RulesPlaylists Album Artist Artist Album Composer Date Genre Rating File Age Random %n Rule(s) , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 ScrobblingLove %1: Loved Current Track %1: Love Current Track ScrobblingSettings Scrobble using: Username: Password: Status: Login Scrobble tracks Show 'Love' button %1 (via MPD) scrobbler name (via MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Authenticating... Authenticated Not Authenticated ScrobblingStatus %1: Scrobble Tracks SearchModel # (Track Number) SearchPage Locate In Library Artist: Composer: Performer: Album: Title: Genre: Comment: Date: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Any: No tracks found. %n Tracks (%1) SearchWidget Search... Close Search Bar ServerSettings Collection: Name: Host: Password: Music folder: Cover filename: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> HTTP stream URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Standard Basic Delete '%1'? Delete New Collection %1 Default ServiceStatusLabel Logged into %1 <b>NOT</b> logged into %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Shortcut for Selected Action Default: None Custom: SinglePageWidget Refresh View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Edit Remove Are you sure you wish to remove the selected rules? This cannot be undone. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cannot access song files! Please check that the device is still attached. SongView Lyrics Information Metadata Scroll Lyrics Refresh Lyrics Edit Lyrics Delete Lyrics File Refresh Track Information Cancel Track Reload lyrics? Reload from disk, or delete disk copy and download? Reload Reload From Disk Download Current playing song has changed, still perform search? Song Changed Perform Search Delete lyrics file? Delete File Artist Album artist Composer Lyricist Conductor Remixer Album Subtitle Track number Disc number Genre Date Original date Comment Copyright Label Catalogue number Title sort Artist sort Album artist sort Album sort Encoded by Encoder Mood Media Bitrate Sample rate Channels Tagging time Performer (%1) %1 kb/s %1 Hz Bits Performer Year Filename Fetching lyrics via %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Total space used: %1 SqlLibraryModel %n Artist(s) %n Album(s) %n Tracks (%1) Cue Sheet Playlist StoredPlaylistsPage Rename Remove Duplicates Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Remove Playlists Playlist Name Enter a name for the playlist: A playlist named '%1' already exists! Overwrite? Overwrite Playlist Rename Playlist Enter new name for playlist: Cannot add songs from '%1' to '%2' StreamDialog Add stream to favourites Name: URL: Add Stream Edit Stream <i><b>ERROR:</b> Invalid protocol</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Update available Check the providers you wish to install/update. Install/Update Stream Providers Downloading list... Failed to download list of stream providers! Installing/updating %1 Failed to install '%1' Failed to download '%1' Install/update the selected stream providers? Install the selected stream providers? Update the selected stream providers? Install/Update Abort installation/update? Abort %n Update(s) available Downloading %1 Update all updateable providers StreamSearchModel TuneIn ShoutCast Dirble Stream Search Search for radio streams Enter string to search Not Loaded Loading... %n Entry(s) StreamSearchPage Added '%1'' to favorites StreamsBrowsePage Import Streams Into Favorites Export Favorite Streams Add New Stream To Favorites Edit Seatch For Streams Configure Digitally Imported Service name Import Streams XML Streams (*.xml *.xml.gz *.cantata) Export Streams XML Streams (*.xml.gz) Failed to create '%1'! Stream '%1' already exists! A stream named '%1' already exists! Bookmark added Already bookmarked Already in favorites Reload '%1' streams? Are you sure you wish to remove bookmark to '%1'? Are you sure you wish to remove all '%1' bookmarks? Are you sure you wish to remove the %1 selected streams? Are you sure you wish to remove '%1'? Added '%1'' to favorites StreamsModel Bookmarks TuneIn IceCast ShoutCast Dirble Favorites Bookmark Category Add Stream To Favorites Configure Digitally Imported Reload Streams Radio stations Not Loaded Loading... %n Entry(s) StreamsSettings Use the checkboxes below to configure the list of active providers. Built-in categories are shown in italic, and these cannot be removed. Configure Streams From File... Download... Configure Provider Install Remove Install Streams Cantata Streams (*.streams) A category named '%1' already exists! Overwrite? Failed top open package file. Invalid file format! Failed to create stream category folder! Failed to save stream list! Are you sure you wish to remove '%1'? Failed to remove streams folder! SyncCollectionWidget Search Check Items Uncheck Items SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Device and library are in sync. Loading all songs from library, please wait...%1%... Local Music Library Properties Device has been removed! Device has been changed? Device is busy? TableView Stretch Columns To Fit Window Left Center Right Alignment TagEditor Track: Title: Artist: Album artist: Composer: Album: Track number: Disc number: Genre: Year: Rating: <i>(Various)</i> Comment: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Tags Tools Apply "Various Artists" Workaround Revert "Various Artists" Workaround Set 'Album Artist' from 'Artist' Capitalize Adjust Track Numbers Read Ratings from File Write Ratings to File All tracks Apply "Various Artists" workaround to <b>all</b> tracks? Apply "Various Artists" workaround? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Revert "Various Artists" workaround <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> Revert Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Album Artist from Artist Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Adjust the value of each track number by: Adjust track number by: Read ratings for all tracks from the music files? Read rating from music file? Ratings Read Ratings Read Rating Read, and updated, ratings from the following tracks: Not all Song ratings have been read from MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Song rating has not been read from MPD! Write ratings for all tracks to the music files? Write rating to music file? Write Ratings Write Rating Failed to write ratings of the following tracks: Failed to write rating to music file! All tracks [modified] %1 [modified] %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: Would you also like to rename your song files, so as to match your tags? Rename Files Rename Device has been removed! Device is not connected. Device is busy? TagSpinBox (Various) ThinSplitter Reset Spacing TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Selected: TrackOrganiser Filenames Filename scheme: VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names Original Name New Name Ratings will be lost if a file is renamed. Organize Files Rename Remove From List Abort renaming of files? Abort Source file does not exist! Skip Auto Skip Destination file already exists! Failed to create destination folder! Failed to rename '%1' to '%2' Remove the selected tracks from the list? Remove Tracks Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Device has been removed! Device is not connected. Device is busy? TrayItem Cantata Now playing UltimateLyricsProvider (Polish Translations) (Portuguese Translations) UmsDevice Not Scanned Not Connected %1 free ValueSlider (recommended) View Cancel VolumeSlider Mute Unmute Volume %1% (Muted) Volume %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | album|score|soundtrack Search pattern for an album, separated by | WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Reload cantata-2.2.0/translations/cantata_cs.ts000066400000000000000000025207061316350454000203340ustar00rootroot00000000000000 Refresh Album Information Obnovit informace o albu Album Album Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Skladby Refresh Artist Information Obnovit informace o umělci Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Umělec Albums Alba Web Links Internetové odkazy Similar Artists Podobní umělci Lyrics Providers Poskytovatelé textů písní Wikipedia Languages Jazyky Wikipedie Other Jiné Reset Spacing Nastavit znovu řádkování &Artist &Umělec Al&bum &Album &Track &Skladba Read more on last.fm Číst více na Last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Pokud se Cantatě nepodařilo najít slova písně, nebo našla špatná, použijte tento dialog pro zadání nových podrobností hledání. Například může být nynější píseň ve skutečnosti přezpívaná verze. Je-li tomu tak, může pomoci hledání slov písně podle původního umělce. Pokud toto vyhledávání nenalezne nová slova, tato pořád budou spojena s původním názvem písně a umělcem, jak je zobrazen v Cantatě. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Název: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Umělec: Search For Lyrics Hledat slova písně Choose the websites you want to use when searching for lyrics. Vyberte stránky, které chcete použít při hledání textů písní. Song Information Informace o písni Images (*.png *.jpg) Obrázky (*.png *.jpg) 10px pixels 10 px %1% value% %1% %1 px pixels %1 px Lyrics Slova písně Information Informace Metadata Popisná data Scroll Lyrics Projíždět text písně Refresh Lyrics Obnovit slova písně Edit Lyrics Upravit slova písně Delete Lyrics File Smazat soubor se slovy písně Refresh Track Information Obnovit informace o skladbě Cancel Zrušit Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Skladba Reload lyrics? Reload from disk, or delete disk copy and download? Nahrát text písně znovu? Nahrát znovu z disku, nebo smazat kopii na disku a stáhnout? Reload Nahrát znovu Reload From Disk Nahrát znovu z disku Download Stáhnout Current playing song has changed, still perform search? Nyní hrající píseň se změnila. Pořád ještě provést hledání? Song Changed Píseň změněna Perform Search Provést hledání Delete lyrics file? Smazat soubor se slovy písně? Delete File Smazat soubor Album artist Umělec alba Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Skladatel Lyricist Textař Conductor Dirigent Remixer Autor předělávky Subtitle Podnázev Track number Číslo skladby Disc number Číslo disku Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Žánr Date Datum Original date Původní datum Comment Poznámka Copyright Autorské právo Label Štítek Catalogue number Katalogové číslo Title sort Třídění podle názvu Artist sort Třídění podle umělce Album artist sort Třídění podle alba a umělce Album sort Třídění podle alba Encoded by Zákodováno Encoder Kodér Mood Nálada Media Média Bitrate Datový tok Sample rate Vzorkovací kmitočet Channels Kanály Tagging time Čas značkování Performer (%1) Účinkující (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bity Performer Účinkující Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Rok Filename Název souboru Fetching lyrics via %1 Natahují se slova písně přes %1 (Polish Translations) Polština (Portuguese Translations) Portugalština Track listing Seznam skladeb Read more on wikipedia Číst víc na Wikipedii Open in browser Otevřít v prohlížeči artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | umělec|skupina|pěvec|zpěvák|zpěvačka|hudebník album|score|soundtrack Search pattern for an album, separated by | album|hudba|zvukový záznam Choose the wikipedia languages you want to use when searching for artist and album information. Vyberte jazyky na Wikipedii, které chcete použít při hledání umělců a informací o albech Cantata is playing a track Cantata přehrává skladbu <b>INVALID</b> <b>NEPLATNÝ</b> <i>(When different)</i> <i>(Když jiný)</i> Artists:%1, Albums:%2, Songs:%3 Umělci:%1, Alba:%2, Písně:%3 %1 free %1 volno Local Music Library Místní hudební knihovna Audio CD Audio CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Na cílovém zařízení nezbývá dost místa. Vybrané písně zabírají %1, ale zbývá jen %2. Písně bude třeba překódovat na menší velikost souborů, aby mohly být úspěšně zkopírovány. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. V cíli nezbývá dost místa. Vybrané písně zabírají %1, ale zbývá jen %2. Copy Songs To Library Kopírovat písně do knihovny Copy Songs To Device Kopírovat písně do zařízení Copy Songs Kopírovat písně Delete Songs Smazat písně You have not configured the destination device. Continue with the default settings? Nenastavil jste cílové zařízení. Pokračovat s výchozím nastavením? Not Configured Nenastaveno Use Defaults Použít výchozí nastavení You have not configured the source device. Continue with the default settings? Nenastavil jste zdrojové zařízení. Pokračovat s výchozím nastavením? Are you sure you wish to stop? Opravdu chcete zastavit? Stop Zastavit Device has been removed! Zařízení bylo odstraněno! Device is not connected! Zařízení není připojeno! Device is busy? Zařízení je zaneprázdněno? Device has been changed? Zařízení bylo změněno? Clearing unused folders Uklízí se nepoužívané složky Calculate ReplayGain for ripped tracks? Spočítat vyrovnání hlasitosti skladeb pro vytažené skladby? ReplayGain Vyrovnání hlasitosti Calculate Spočítat The destination filename already exists! Cílový souborový název již existuje! Song already exists! Píseň již existuje! Song does not exist! Píseň neexistuje! Failed to create destination folder!<br/>Please check you have sufficient permissions. Nepodařilo se vytvořit cílovou složku!<br/>Prověřte, prosím, zda máte dostatečná oprávnění. Source file no longer exists? Zdrojový soubor už neexistuje? Failed to copy. Nepodařilo se zkopírovat. Failed to delete. Nepodařilo se smazat. Not connected to device. Nepřipojeno k zařízení. Selected codec is not available. Vybraný kodek není dostupný. Transcoding failed. Překódování se nezdařilo. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Nepodařilo se vytvořit dočasný soubor.<br/>(Požadováno pro překódování na zařízení MTP.) Failed to read source file. Nepodařilo se přečíst zdrojový soubor. Failed to write to destination file. Nepodařilo se zapsat do cílového souboru. No space left on device. Na zařízení není žádné místo. Failed to update metadata. Nepodařilo se zaktualizovat popisná data. Failed to download track. Nepodařilo se stáhnout skladbu. Failed to lock device. Nepodařilo se uzamknout zařízení. Local Music Library Properties Vlastnosti místní hudební knihovny Error Chyba Skip Přeskočit Auto Skip Automaticky přeskočit Retry Zkusit znovu Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Album: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Skladba: Source file: Zdrojový soubor: Destination file: Cílový soubor: File: Soubor: Calculating... Počítá se... %1 (Estimated) time (Estimated) %1 (odhadováno) Time remaining: Zbývající čas: Saving cache Ukládá se vyrovnávací paměť Apply "Various Artists" Workaround Použít zařazení pod Různí umělci Revert "Various Artists" Workaround Zvrátit zařazení pod Různí umělci Capitalize Psát velkými písmeny Adjust Track Numbers Upravit čísla skladeb Tools Nástroje Apply "Various Artists" workaround? Použít zařazení pod Různí umělci? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Toto nastaví Umělce alba a Umělce na Různí umělci, a nastaví Název na "Umělec skladby - Název skladby" Revert "Various Artists" workaround? Zvrátit zařazení pod Různí umělci? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Tam, kde Umělec alba je stejný jako Umělec a Název je ve formátu "Umělec skladby - Název skladby", se Umělec vezme z Názvu a samotný název se nastaví na prostý Název. Např. Pokud je Název "Wibble - Wobble", pak Umělec se nastaví na "Wibble" a Název na "Wobble" Revert Vrátit Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Psát velkým písmenem první písmeno Názvu, Umělce, Umělce alba a Alba? Adjust track number by: Upravit číslo skladby o: Reading disc Čte se disk CDDB CDDB MusicBrainz MusicBrainz Data Track Datová stopa Failed to open CD device Nepodařilo se otevřít zařízení CD Track %1 Stopa %1 Failed to create CDDB connection Nepodařilo se vytvořit připojení CDDB Failed to contact CDDB server, please check CDDB and network settings Nepodařilo se spojit se se serverem CDDB. Prověřte, prosím, nastavení CDDB a sítě No matches found in CDDB V CDDB nenalezeny žádné shody CDDB error: %1 Chyba CDDB: %1 Multiple matches were found. Please choose the relevant one from below: Bylo nalezeno více shod. Vyberte, prosím, náležitou shodu z uvedených níže: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Název Disc Selection Výběr disku %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disk %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Obnovuje se (%1)... Updating (%1%)... Obnovuje se (%1%)... Device Properties Vlastnosti zařízení Don't copy covers Nekopírovat obaly Embed cover within each file Vložit obal do každého souboru No maximum size Žádná největší velikost 400 pixels 400 pixelů 300 pixels 300 pixelů 200 pixels 200 pixelů 100 pixels 100 pixelů <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Když se skladby kopírují na zařízení, a 'Umělec alba' je nastaven na 'Různí umělci', Cantata nastaví značku 'Umělec' u všech skladeb na 'Různí umělci' a značku 'Název' skladby na 'Umělec skladby - Název skladby'.<hr/> Když se kopíruje ze zařízení, Cantata prověří, zda jsou 'Umělec alba' a 'Umělec' oba nastaveni na 'Různí umělci'. Pokud ano, pokusí se vytáhnout skutečného umělce ze značky 'Název' a odstranit umělcovo jméno ze značky 'Název'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Pokud toto povolíte, Cantata vytvoří vyrovnávací paměť hudební knihovny zařízení. To pomůže urychlit následné prohledávání knihovny (protože namísto čtení značky u každého souboru se použije vyrovnávací paměť.)<hr/><b>POZNÁMKA:</b> Pokud na obnovu knihovny zařízení použijete jiný program, potom se tato vyrovnávací paměť stane zastaralou. Abyste to dal do pořádku, jednoduše klepněte na ikonu pro 'obnovu' v seznamu zařízení. To způsobí odstranění souboru s vyrovnávací pamětí a opětovné znovuprohledání obsahu zařízení.</p> Do not transcode Nepřekódovat Transcode to %1 Překódovat do %1 %1 (%2 free) name (size free) %1 (%2 volné) Copy To Library Kopírovat do knihovny Synchronise Seřídit Forget Device Zapomenout zařízení Add Device Přidat zařízení Lookup album and track details? Vyhledat podrobnosti alba a skladby? Refresh Obnovit Via CDDB Pomocí CDDB Via MusicBrainz Pomocí MusicBrainz Which type of refresh do you wish to perform? Který typ aktualizace chcete provést? Partial - Only new songs are scanned (quick) Částečný - Jsou prohledány pouze nové písně (rychlé) Full - All songs are rescanned (slow) Úplný - Jsou prohledány všechny písně (pomalé) Partial Částečný Full Úplný Are you sure you wish to delete the selected songs? This cannot be undone. Opravdu chcete odstranit vybrané písně? Tento krok nelze vrátit zpět. Are you sure you wish to forget '%1'? Opravdu chcete zapomenout na '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Opravdu chcete vysunout zvukové CD '%1 - %2'? Eject Vysunout Are you sure you wish to disconnect '%1'? Opravdu chcete odpojit '%1'? Disconnect Odpojit Please close other dialogs first. Nejprve, prosím, zavřete další dialogy <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) je patentovaný ztrátový kodek pro digitální zvuk.<br>AAC všeobecně dosahuje při podobných datových tocích lepší jakosti zvuku než MP3. Je rozumnou volbou pro iPod a některé další přenosné přehrávače hudby. Provedení není zdarma. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>AAC</b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem než ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok bitrate po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem <a href=http://www.ffmpeg.org/faq.html#SEC21>průměrného datového toku</a> kódované skladby.<br><b>150 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>120 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>200 kb/s</b> je pravděpodobně až přespříliš. Expected average bitrate for variable bitrate encoding Očekávaný datový tok pro kódování proměnlivého datového toku Smaller file Menší soubor Better sound quality Lepší kvalita zvuku <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) je patentem chráněný digitální zvukový kodek, který používá druh ztrátového zhuštění dat.<br>Navzdory svým slabinám je to běžný formát pro spotřebitelské ukládání zvuku a je široce podporován v přenosných přehrávačích hudby. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>MP3</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/MP3#VBR>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem <a href=http://wwwffmpeg.org/faq.html#>průměrného datového toku</a> kódované skladby.<br><b>160 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>120 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>205 kb/s</b> je pravděpodobně až přespříliš. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> je otevřený kodek bez poplatků za užívání pro ztrátové zhuštění zvuku.<br>Vyrábí menší soubory než MP3 při stejné nebo vyšší kvalitě. Ogg Vorbis je všestranně vynikající, skvělou, výbornou, znamenitou a prvotřídní volbou pro přenosné přehrávače hudby, které jej podporují. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>Vorbis</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor, než jaký dává stálý datový tok po celou dobu skladby.<br>Kodér Vorbis používá při hodnocení jakosti parametr "-q", což je hodnota mezi -1 a 10, aby stanovil určitou očekávanou úroveň kvality zvuku. Měřítko datového toku v tomto posuvníku je jen hrubým odhadem (obstaraným Vorbisem) průměrného datového toku kódované skladby daný hodnotou q. Vlastně je s novějšími a účinnějšími verzemi kodéru Vorbis skutečný datový tok dokonce nižší.<br><b>-q5</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>-q3</b> může být hudebně neuspokojivé a cokoli nad <b>-q8</b> je pravděpodobně až přespříliš. Quality rating Hodnocení jakosti Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> digitální audio kodek nezatížený patenty používající ztrátovou kompresi dat. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>Opus</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Variable_bitrate>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem průměrného datového toku kódované skladby.<br><b>128 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>100 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>256 kb/s</b> je pravděpodobně až přespříliš. Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) je zvukový kodek pro bezztrátové zhuštění digitální hudby.<br>Doporučováno pouze pro hudební přehrávače od firmy Apple a přehrávače nepodporující FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) je otevřený kodek bez poplatků za užívání pro bezztrátové zhuštění digitální hudby.<br>Pokud si svou hudbu přejete ukládat bez ústupků, co se týče jakosti zvuku, FLAC je prostě excelentní, tedy vynikající, skvělou, výbornou, znamenitou a prvotřídní volbou. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Úroveň zhuštění</a> je hodnota celého čísla ležící mezi 0 a 8, která představuje vyvážení mezi velikostí souboru a rychlostí zhuštění během kódování s <b>FLAC</b>.<br/> Nastavení úrovně zhuštění na <b>0</b> dává nejkratší čas zhuštění, ale způsobuje srovnatelně velký soubor<br/>Na druhou stranu úroveň zhuštění <b>8</b> dělá zhušťování docela pomalým,ale vytvoří nejmenší soubor.<br/>Uvědomte si, že vzhledem k tomu, že FLAC je ze své podstaty bezeztrátový kodek, je zvuková jakost výstupu přesně tatáž bez ohledu na úroveň zhuštění.<br/>Úrovně nad <b>5</b> kromě toho napínavě zvyšují čas zhuštění, ale vytvářejí jen nepatrně menší soubor, a nedoporučují se. Compression level Úroveň zhuštění Faster compression Rychlejší zhuštění Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) je kodek, který je patentově chráněn, vyvíjený firmou Microsoft pro ztrátové zhuštění zvuku.<br>Doporučován jen pro přenosné přehrávače hudby, jež nepodporují formát Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>V důsledku omezení patentově chráněného formátu <b>WMA</b> a obtížnosti obráceného inženýrství soukromého kodéru, kodér WMA používaný Amarokem nastavuje <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>stálý datový tok (CBR).<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku slušným odhadem datového toku kódované skladby.<br><b>136kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>112kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>182kb/s</b> je pravděpodobně až přespříliš. Filename Scheme Schéma názvu souboru Various Artists Example album artist Různí umělci Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Nyní 5001 Wobble Example song name Výkyv Dance Example genre Tanec The following variables will be replaced with their corresponding meaning for each track name. Následující proměnné budou nahrazeny jejich odpovídajícím významem pro každý název skladby. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Proměnná</em></th><th><em>Tlačítko</em></th><th><em>Popis</em></th></tr> Updating... Obnovuje se... Reading cache Čte se vyrovnávací paměť %1 %2% Message percent %1 %2% Connecting to device... Připojuje se k zařízení... No devices found Nenalezeno žádné zařízení Connected to device Připojeno k zařízení Disconnected from device Odpojeno od zařízení Updating folders... Obnovují se složky... Updating files... Obnovují se soubory... Updating tracks... Obnovují se skladby... Not Connected Nepřipojeno %1 (Disc %2) %1 (Disk %2) No matches found in MusicBrainz V MusicBrainz nenalezeny žádné shody Connection Spojení Music Library Hudební knihovna A remote device named '%1' already exists! Please choose a different name. Vzdálené zařízení s názvem "'%1" již existuje! Vyberte, prosím, jiný název. Samba Share Sdílení Samba Samba Share (Auto-discover host and port) Sdílení Samba (automatické zjištění hostitele a přípojky) Secure Shell (sshfs) Bezpečný shell (sshfs) Locally Mounted Folder Místně připojená složka Available Dostupné Not Available Nedostupné Failed to resolve connection details for %1 Nepodařilo se vyřešit podrobnosti připojení pro %1 Connecting... Připojuje se... Password prompting does not work when cantata is started from the commandline. Výzva k zadání hesla nepracuje, když je Cantata spuštěna z příkazového řádku. No suitable ssh-askpass application installed! This is required for entering passwords. Žádný vhodný program ssh-askpass není nainstalován! Toto je vyžadováno pro zadávání hesel Mount point ("%1") is not empty! Bod připojení ("%1") není prázdný! "sshfs" is not installed! "sshfs" není nainstalován! Disconnecting... Odpojuje se... "fusermount" is not installed! "fusermount" není nainstalován! Failed to connect to "%1" Nepodařilo se připojit k "%1" Failed to disconnect from "%1" Nepodařilo se odpojit od "%1" Capacity Unknown Neznámá velikost Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Hledat Check Items Zaškrtnout položky Uncheck Items Zrušit zaškrtnutí položek Library: Knihovna: Device: Zařízení: Loading all songs from library, please wait... Nahrávají se všechny písně v knihovně. Počkejte, prosím... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Knihovna</code> uvádí pouze písně, které jsou v knihovně, ale ne na zařízení. Stejně tak <code>Zařízení</code> uvádí písně, které jsou jen na zařízení.<br/>Vyberte písně z <code>knihovny</code>, jež chcete zkopírovat do <code>zařízení</code>, a vyberte písně ze <code>zařízení</code>, jež chcete zkopírovat do <code>knihovny</code>. Potom stiskněte tlačítko <code>Seřídit</code>. Synchronize Seřídit Device and library are in sync. Zařízení a knihovna jsou vzájemně seřízeny. Loading all songs from library, please wait...%1%... Nahrávají se všechny písně z knihovny. Počkejte, prosím... %1%... Not Scanned Neprohledáno (recommended) (doporučeno) Empty filename. Prázdný název souboru. Invalid filename. (%1) Neplatný název souboru. (%1) Failed to save %1. Nepodařilo se uložit %1. Failed to delete rules file. (%1) Nepodařilo se smazat soubor s pravidly. (%1) Invalid command. (%1) Neplatný příkaz. (%1) Could not remove active rules link. Nepodařilo se odstranit odkaz na činná pravidla. Active rules is not a link. Činná pravidla není odkaz. Could not create active rules link. Nepodařilo se vytvořit odkaz na činná pravidla. Rules file, %1, does not exist. Soubor s pravidly, %1, neexistuje. Incorrect arguments supplied. Poskytnuty nesprávné argumenty. Unknown method called. Zavolána neznámá metoda. Unknown error Neznámá chyba Start Dynamic Playlist Spustit dynamický seznam skladeb Stop Dynamic Mode Zastavit dynamický režim Dynamic Playlists Dynamické seznamy skladeb Dynamically generated playlists Dynamicky tvořené seznamy skladeb - Rating: %1..%2 - Hodnocení: %1...%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Aby pracoval dynamický režim Cantaty, musíte do vašeho systému nainstalovat "perl". Failed to locate rules file - %1 Nepodařilo se najít soubor s pravidly - %1 Failed to remove previous rules file - %1 Nepodařilo se odstranit předchozí soubor s pravidly - %1 Failed to install rules file - %1 -> %2 Nepodařilo se nainstalovat soubor s pravidly - %1 -> %2 Dynamizer has been terminated. Dynamizátor byl ukončen. Saving rule Ukládá se pravidlo Deleting rule Maže se pravidlo Awaiting response for previous command. (%1) Očekává se odpověď na předchozí příkaz. (%1) Failed to save %1. (%2) Nepodařilo se uložit %1. (%2) Failed to control dynamizer state. (%1) Nepodařilo se zkontrolovat stav dynamizátoru. (%1) Failed to set the current dynamic rules. (%1) Nepodařilo se nastavit nynější dynamická pravidla. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Přidat Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Upravit Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Odstranit Remote dynamizer is not running. Vzdálený dynamizátor neběží. Are you sure you wish to remove the selected rules? This cannot be undone. Opravdu chcete odstranit vybraná pravidla? Tento krok nelze vrátit zpět. Remove Dynamic Rules Odstranit dynamická pravidla Dynamic Rule Dynamické pravidlo <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>CHYBA</b>: 'Od roku' má být menší než 'Do roku'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>CHYBA</b>: Rozsah data je příliš velký (může být nanejvýš jen %1 roků)</i> SimilarArtists Podobní umělci AlbumArtist Umělec alba Include Zahrnout Exclude Vyloučit (Exact) (Přesné) Dynamic Rules Dynamická pravidla None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Žádné About dynamic rules O dynamických pravidlech Failed to save %1 Nepodařilo se uložit %1 A set of rules named '%1' already exists! Overwrite? Seznam pravidel pojmenovaný '%1' již existuje! Přepsat? Overwrite Rules Přepsat pravidla Saving %1 Ukládá se %1 Deleting... Maže se... Name Název Item Count Počet položek Space Used Použité místo Total space used: %1 Celkové použité místo: %1 Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata ukládá různé kousky údajů (obaly, slova písní atd.) do vyrovnávací paměti. Dole je přehled nynějšího využití vyrovnávací paměti Cantatou. Covers Obaly Scaled Covers Obaly se změněnou velikostí Backdrops Pozadí Artist Information Informace o umělci Album Information Informace o albu Track Information Informace o skladbě Stream Listings Soupisy proudů Podcast Directories Adresáře se záznamy (podcasty) Scrobble Tracks Odesílat informace o skladbách Delete All Smazat vše Delete all '%1' items? Smazat všech '%1' položek? Delete Cache Items Smazat položky ve vyrovnávací paměti Delete items from all selected categories? Smazat položky z vybraných skupin? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Nynější obal CoverArt Archive Archiv obalů Image Obrázek Downloading... Stahuje se... Image (%1 x %2 %3%) Image (width x height zoom%) Obrázek (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Pro tohoto umělce již existuje jeden obrázek. Soubor však není zapisovatelný. A cover already exists for this album, and the file is not writeable. Pro toto album již existuje jeden obrázek. Soubor však není zapisovatelný. '%1' Artist Image '%1' Obrázek umělce '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Obal alba Failed to set cover! Could not download to temporary file! Nepodařilo se nastavit obal! Nepodařilo se stáhnout do dočasného souboru! Failed to download image! Nepodařilo se stáhnout obrázek! Load Local Cover Nahrát místní obal File is already in list! Soubor je již v seznamu! Failed to read image! Nepodařilo se přečíst obrázek! Display Zobrazit Failed to set cover! Could not make copy! Nepodařilo se nastavit obal! Nepodařilo se udělat kopii! Failed to set cover! Could not backup original! Nepodařilo se nastavit obal! Nepodařilo se zazálohovat originál! Failed to set cover! Could not copy file to '%1'! Nepodařilo se nastavit obal! Nepodařilo se zkopírovat soubor do '%1'! Searching... Hledá se... Custom Actions Vlastní činnosti Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Název: Command: Příkaz: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. V příkazovém řádku výše bude %f nahrazeno seznamem souborů a %d seznamem složek. pokud není dodán ani jeden z nich, bude k příkazu připojen seznam souborů. c-format Add New Command Přidat nový příkaz Edit Command Upravit příkaz To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Aby Cantata volala vnější příkazy (např. úprava značek v jiném programu), přidejte položku pro příkaz níže. Když je stanoven alespoň jeden příkaz, bude do nabídek souvisejících činností v pohledech na knihovnu, na složky a na seznamy skladeb přidána položka Vlastní činnosti. Command Příkaz Remove the selected commands? Odstranit vybrané příkazy? Open In File Manager Otevřít ve správci souborů Connection Established Spojení navázáno Connection Failed Spojení se nezdařilo Grouped Albums Seskupená alba Table Tabulka Parse in Library view, and show in Folders view Zpracovat v pohledu na knihovnu a ukázat v pohledu na složky Only show in Folders view Ukázat jen v pohledu na složky Do not list Neuvádět Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Řada Library Knihovna Folders Složky Playlists Seznamy Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet - proudy, Jamendo, Maganatune, SoundCloud, a zvukové záznamy Devices - UMS, MTP (e.g. Android), and AudioCDs Zařízení - UMS, MTP (např. Android), a zvuková CD Search (via MPD) Hledání (přes MPD) Info - Current song information (artist, album, and lyrics) Informace - informace o nynější písni (umělec, album a slova písně) Large Velký Small Malý Tab-bar Pruh s kartami Left Vlevo Right Vpravo Top Nahoře Bottom Dole Notifications Oznámení System default Výchozí nastavení systému Show Artist Images Ukázat obrázky umělců Sort Albums Třídit alba Album, Artist, Year Album, umělec, rok Album, Year, Artist Album, rok, umělec Artist, Album, Year Umělec, album, rok Artist, Year, Album Umělec, rok, album Year, Album, Artist Rok, album, umělec Year, Artist, Album Rok, umělec, album Modified Date Datum změny Group By Seskupit podle Configure Cantata... Nastavit Cantatu... Preferences Nastavení Quit Ukončit About Cantata... Qt-only O programu Cantata Show Window Ukázat okno Server information... Informace o serveru... Refresh Database Obnovit databázi Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Spojit Collection Sbírka Outputs Výstupy Stop After Track Zastavit po skladbě Add To Stored Playlist Přidat do uloženého seznamu skladeb Crop Others Vystřihnout jiné Add Stream URL Přidat adresu proudu Clear Vyprázdnit Center On Current Track Zaměřit na nynější skladbu Expanded Interface Rozšířené rozhraní Show Current Song Information Ukázat informace o nynější skladbě Full Screen Na celou obrazovku Random Náhodné Repeat Opakování Single Jednotlivé When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Když jsou zapnuty jednotlivé skladby, je přehrávání zastaveno po nynější písni, nebo je píseň opakována, pokud je povolen režim opakování. Consume Sníst When consume is activated, a song is removed from the play queue after it has been played. Když je zapnuto snězení, píseň je odstraněna z řady skladeb k přehrání, poté co byla přehrána Find in Play Queue Hledat v řadě skladeb k přehrání Play Stream Přehrát proud Locate In Library Najít v knihovně Expand All Rozbalit vše Collapse All Složit vše Internet Internet Devices Zařízení Info Informace Show Menubar Ukázat pruh s nabídkou &Music &Hudba &Edit Úp&ravy &View &Pohled &Queue Řa&da &Settings &Nastavení &Help Nápo&věda Set Rating Nastavit hodnocení No Rating Žádné hodnocení Failed to locate any songs matching the dynamic playlist rules. Nepodařilo se najít žádné písně odpovídající pravidlům dynamického seznamu skladeb. Connecting to %1 Připojuje se k %1 Refresh MPD Database? Obnovit databázi MPD? About Cantata Qt-only O programu Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Qt-only <b>Cantata %1</b><br/><br/>Klient pro MPD.<br/><br/>© Craig Drummond 2011-2016.<br/>Vydáno pod <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Založeno na <a href="http://lowblog.nl">QtMPC</a> - © 2007-2010 Autoři QtMPC<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Pozadí pohledu na souvislosti díky laskavosti <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Popisná data pohledu na souvislosti díky laskavosti <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Zvažte, prosím, nahrání vašeho vlastního fanouškovského umění na <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Zvukový záznam se nyní stahují. Pokud bude program ukončen nyní, bude stahování zrušeno. Abort download and quit Zrušit stahování a ukončit. Enabled: %1 Povoleno: %1 Disabled: %1 Zakázáno: %1 Server Information Informace o serveru <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protokol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Doba provozu:&nbsp;</td><td>%4</td></tr><tr><td align="right">Přehrává se:&nbsp;</td><td>%5</td></tr><tr><td align="right">Ovladače:&nbsp;</td><td>%6</td></tr><tr><td align="right">Značky:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Databáze</b></td></tr><tr><td align="right">Umělci:&nbsp;</td><td>%1</td></tr><tr><td align="right">Alba:&nbsp;</td><td>%2</td></tr><tr><td align="right">Písně:&nbsp;</td><td>%3</td></tr><tr><td align="right">Doba trvání:&nbsp;</td><td>%4</td></tr><tr><td align="right">Obnoveno:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD nahlásilo následující chybu: %1 Cantata Cantata Playback stopped Přehrávání zastaveno Remove all songs from play queue? Odstranit všechny písně z řady skladeb k přehrání? Priority Přednost Enter priority (0..255): Zadejte přednost (0..255): Playlist Name Název seznamu skladeb Enter a name for the playlist: Zadejte název pro seznam skladeb: '%1' is used to store favorite streams, please choose another name. '%1' se používá na ukládání oblíbených proudů. Vyberte, prosím, jiný název. A playlist named '%1' already exists! Add to that playlist? Seznam skladeb pojmenovaný '%1' již existuje! Přidat do tohoto seznamu skladeb? Existing Playlist Existující seznam skladeb Auto Automaticky <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Připojeno k %1.<br/>Záznamy níže použít na nyní připojenou sbírku MPD.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Nepřipojeno.<br/>Záznamy níže nelze změnit, protože Cantata není připojena k MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Vyrovnání hlasitosti (Replay Gain) je navržený standard zveřejněný v roce 2001 k normalizaci vnímané hlasitosti počítačových zvukových formátů, jako jsou MP3 a Ogg Vorbis. Pracuje na základě skladba/album, a je nyní podporován rostoucím počtem přehrávačů.<br/><br/>Je možné použít následující nastavení vyrovnání hlasitosti:<ul><li><i>Žádné</i> - Není použito žádné vyrovnání hlasitosti.</li><li><i>Skladba</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u skladby.</li><li><i>Album</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u alba.</li><li><i>Automaticky</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u skladby, v případě že je zapnuto náhodné přehrávání, jinak se použijí značky u alba.</li></ul> Rename Přejmenovat Remove Duplicates Odstranit zdvojené Initially Collapse Albums Na začátku alba složit Are you sure you wish to remove the selected playlists? This cannot be undone. Opravdu chcete odstranit vybrané seznamy skladeb? Tento krok nelze vrátit zpět. Remove Playlists Odstranit seznamy skladeb A playlist named '%1' already exists! Overwrite? Seznam skladeb pojmenovaný '%1' již existuje! Přepsat? Overwrite Playlist Přepsat seznam skladeb Rename Playlist Přejmenovat seznam skladeb Enter new name for playlist: Zadejte nový název pro seznam skladeb: Cannot add songs from '%1' to '%2' Nelze přidat písně z '%1' do '%2' 1 Track Jedna skladba %1 skladby %1 skladeb %1 Tracks 1 Track (%2) Jedna skladba (%2) %1 skladby (%2) %1 skladeb (%2) %1 Tracks (%2) 1 Album Jedno album %1 alba %1 alb %1 Albums 1 Artist Jeden umělec %1 umělci %1 umělců %1 Artists 1 Stream Jeden proud %1 proudy %1 proudů %1 Streams 1 Entry Jedna položka %1 položky %1 položek %1 Entries 1 Rule Jedno pravidlo %1 pravidla %1 pravidel %1 Rules 1 Podcast Jeden záznam %1 záznamy %1 záznamů %1 Podcasts 1 Episode Jeden díl %1 díly %1 dílů %1 Episodes 1 Update available Jedna aktualizace dostupná %1 aktualizace dostupné %1 aktualizací dostupných %1 Updates available Collection Settings Nastavení sbírky Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Přehrávání Playback Settings Nastavení přehrávání Downloaded Files Stažené soubory Downloaded Files Settings Nastavení pro stažené soubory Interface Rozhraní Interface Settings Nastavení rozhraní Info View Settings Nastavení pohledu na informace Scrobbling Odesílání informací o skladbách Scrobbling Settings Nastavení odesílání informací o skladbách Audio CD Settings Nastavení zvukového CD Proxy Proxy Proxy Settings Qt-only Nastavení proxy Shortcuts Qt-only Zkratky Keyboard Shortcut Settings Qt-only Nastavení klávesových zkratek Cache Vyrovnávací paměť Cached Items Položky ve vyrovnávací paměti Cantata Preferences Nastavení Cantaty Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Nastavit Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Skladatel: Performer: Účinkující: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Žánr: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Poznámka: Date: Datum: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Najít písně hledáním ve značce Datum. <br/><br/>Obvykle by mělo stačit jen zadat rok. Modified: Změněno: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Zadejte datum (RRRR/MM/DD - např. 2015/01/31) k vyhledání souborů změněných od toho databázi.<br/><br> Any: Jakékoli: No tracks found. Nenalezeny žádné skladby Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Server: Which type of collection do you wish to connect to? Který typ sbírky chcete připojit? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Obvyklý - hudební sbírku může být sdílena, je na jiném stroji, nebo je již nastavena, nebo chcete povolit přístup z jiných klientů (např. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Základní - hudební sbírka není sdílena s ostatními, a Cantata nastaví a bude řídit instanci MPD. Toto nastavení bude pro Cantatu výlučné a <b>nebude</b> přístupné pro jiné klienty MPD. If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' i18n: file: gui/initialsettingswizard.ui:236 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel_2) Pokud chcete mít pokročilé nastavení MPD (např. více výstupů zvuku, plnou podporu pro DSD atd.), potom <b>musíte</b> vybrat Obvyklý <i><b>NOTE:</b> %1</i> <i><b>POZNÁMKA:</b> %1</i> Add Collection Přidat sbírku Standard Obvyklý Basic Základní Delete '%1'? Smazat '%1'? Delete Smazat New Collection %1 Nová sbírka %1 Default Výchozí Previous Track Předchozí skladba Next Track Další skladba Play/Pause Přehrát/Pozastavit Stop After Current Track Zastavit po současné skladbě Increase Volume Zvýšit hlasitost Decrease Volume Snížit hlasitost Save As Uložit jako Append Připojit Append To Play Queue Připojit do řady skladeb k přehrání Append And Play Připojit a přehrát Add And Play Přidat a přehrát Append To Play Queue And Play Připojit do řady skladeb k přehrání a přehrát Insert After Current Vložit po nynější Append Random Album Připojit náhodné album Play Now (And Replace Play Queue) Přehrát nyní (a nahradit řadu skladeb k přehrání) Add With Priority Přidat s předností Set Priority Nastavit přednost Highest Priority (255) Nejvyšší přednost (255) High Priority (200) Vysoká přednost (200) Medium Priority (125) Střední přednost (125) Low Priority (50) Nízká přednost (50) Default Priority (0) Výchozí přednost (0) Custom Priority... Vlastní přednost... Add To Playlist Přidat do seznamu skladeb Organize Files Uspořádat soubory Edit Track Information Upravit informace o skladbě Set Image Nastavit obrázek Find Najít Add To Play Queue Přidat do řady skladeb k přehrání Now playing Nyní se hraje Play Přehrát Pause Pozastavit Cue Sheet List CUE Playlist Seznam skladeb Configure Device Nastavit zařízení Refresh Device Obnovit zařízení Connect Device Připojit zařízení Disconnect Device Odpojit zařízení Edit CD Details Upravit podrobnosti CD No Devices Attached Nepřipojeno žádné zařízení Not logged in Nepřihlášen Logged in Přihlášen No subscriptions Žádné odběry You do not have an active subscription Nemáte žádný činný odběr Logged in (expiry:%1) Přihlášen (vypršení: %1) Session expired Sezení vypršelo Parse error loading cache file, please check your songs tags. Chyba ve zpracování při nahrávání souboru s vyrovnávací pamětí. Prověřte, prosím, značky vaší písně. %1 by %2 Album by Artist %1 od %2 New Playlist... Nový seznam skladeb... Stored Playlists Uložené seznamy skladeb Standard playlists Obvyklé seznamy skladeb Smart Playlist Chytrý seznam skladeb # Track number Č. Length Délka Disc Disk Rating Hodnocení Undo Zpět Redo Znovu Shuffle Zamíchat Sort By Třídit podle Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Umělec alba Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Název skladby # (Track Number) Číslo skladby TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Hledání proudu Search for radio streams Hledat rozhlasové proudy Enter string to search Zadejte hledaný řetězec Not Loaded Nenahráno Loading... Nahrává se... Bookmarks Záložky IceCast IceCast Favorites Oblíbené Bookmark Category Skupina záložky Add Stream To Favorites Přidat proud do oblíbených Configure Digitally Imported Nastavit Digitally Imported Streams Proudy Radio stations Rozhlasové stanice Unknown Neznámý "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed Nepodařilo se připojit k %1 Connection to %1 failed - please check your proxy settings Nepodařilo se připojit k %1 - Prověřte, prosím, nastavení vaší proxy Connection to %1 failed - incorrect password Nepodařilo se připojit k %1 - nesprávné heslo Failed to send command to %1 - not connected Nepodařilo se poslat příkaz %1 - nepřipojeno Failed to load. Please check user "mpd" has read permission. Nepodařilo se nahrát. Ověřte, prosím, zda má mpd oprávnění ke čtení. Failed to load. MPD can only play local files if connected via a local socket. Nepodařilo se nahrát. MPD může přehrát jen místní soubory, pokud je připojen přes místní zásuvku. Failed to send command. Disconnected from %1 Nepodařilo se poslat příkaz. Odpojeno od %1 Failed to rename <b>%1</b> to <b>%2</b> Nepodařilo se přejmenovat <b>%1</b> na <b>%2</b> Failed to save <b>%1</b> Nepodařilo se uložit <b>%1</b>! You cannot add parts of a cue sheet to a playlist! Nelze přidat části seznamu v souboru CUE do seznamu skladeb! You cannot add a playlist to another playlist! Nelze přidat seznam skladeb do jiného seznamu skladeb! Failed to send '%1' to %2. Please check %2 is registered with MPD. Nepodařilo se poslat '%1' %2. Ověřte, prosím, že %2 je zaregistrováno u MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. Nelze uložit hodnocení, jelikož příkaz lepiče MPD není podporován. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Jednotlivé skladby Personal Osobní Various Artists Různí umělci <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> na <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> od <b>%2</b> na <b>%3</b> No proxy Žádná proxy Use the system proxy settings Použít systémové nastavení proxy Manual proxy configuration Ruční nastavení proxy The world's largest digital service for free music Největší světová digitální služba pro volnou hudbu Jamendo Settings Nastavení pro Jamendo MP3 MP3 Ogg Ogg Streaming format: Formát přenosu: Streaming Přenos MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Online music from magnatune.com Internetová hudba z magnatune.com Magnatune Settings Nastavení pro Magnatune Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Uživatelské jméno: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Heslo: Membership: Členství: Downloads: Stahování: Failed to parse Nepodařilo se zpracovat Downloading...%1% Stahuje se... %1% Parsing music list.... Zpracovává se seznam s hudbou... Failed to download Nepodařilo se stáhnout The music listing needs to be downloaded, this can consume over %1Mb of disk space Je třeba stáhnout seznamu hudby. To může spotřebovat přes %1 MB místa na disku Dowload music listing? Stáhnout seznam hudby Re-download music listing? Stáhnout seznam hudby znovu? RSS: RSS: Website: Stránky: Podcast details Podrobnosti záznamu Select a podcast to display its details Vybrat záznam pro zobrazení jeho podrobností Enter search term... Zadat hledaný pojem... Failed to fetch podcasts from %1 Nepodařilo se natáhnout zvukové záznamy z %1 There was a problem parsing the response from %1 Nastaly potíže se zpracováním odpovědi z %1 Failed to download directory listing Nepodařilo se stáhnout soupis adresáře Failed to parse directory listing Nepodařilo se zpracovat soupis adresáře URL Adresa (URL) Enter podcast URL... Zadejte adresu zvukového záznamu (URL)... Load Nahrát Enter podcast URL below, and press 'Load' Zadejte adresu zvukového záznamu (URL) níže a stiskněte Nahrát Invalid URL! Neplatná adresa (URL)! Failed to fetch podcast! Nepodařilo se natáhnout zvukový záznam! Failed to parse podcast. Nepodařilo se zpracovat zvukový záznam. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata podporuje pouze záznamy zvuku! Zadaná adresa (URL) obsahuje jen záznamy obrazu. Subscribe Odebírat Enter URL Zadat adresu (URL) Manual podcast URL Ruční adresa zvukového záznamu (URL) Search %1 Hledat %1 Search for podcasts on %1 Hledat zvukové záznamy na %1 Add Podcast Subscription Přidat odběr zvukového záznamu Browse %1 Procházet %1 Browse %1 podcasts Procházet %1 zvukových záznamů You are already subscribed to this podcast! Již jste přihlášen k odběru tohoto záznamu! Subscription added Odběr přidán Subscribe to RSS feeds Odebírat kanál RSS %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Stahuje se: %1%) Failed to parse %1 Nepodařilo se zpracovat %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata podporuje pouze záznamy zvuku! %1 obsahuje jen záznamy obrazu. Failed to download %1 Nepodařilo se stáhnout %1 Check for new episodes: Podívat se po nových dílech: Download episodes to: Stáhnout díly do: Download automatically: Stáhnout automaticky: Podcast Settings Nastavení záznamu Manually Ručně Every 15 minutes Každých 15 minut Every 30 minutes Každých 30 minut Every hour Každou hodinu Every 2 hours Každé 2 hodiny Every 6 hours Každých 6 hodin Every 12 hours Každých 12 hodin Every day Každý den Every week Každý týden Don't automatically download episodes Nestahovat díly automaticky Latest episode Poslední díl Latest %1 episodes Poslední %1 díly All episodes Všechny díly Add Subscription Přidat odběr Remove Subscription Odstranit odběr Download Episodes Stáhnout díly Delete Downloaded Episodes Smazat stažené díly Cancel Download Zrušit stahování Mark Episodes As New Označit díly jako nové Mark Episodes As Listened Označit díly jako poslechnuté Unsubscribe from '%1'? Odhlásit odběr z '%1'? Do you wish to download the selected podcast episodes? Chcete stáhnout vybrané díly zvukového záznamu? Cancel podcast episode downloads (both current and any that are queued)? Zrušit stahování dílů zvukového záznamu (nynější a všechny zařazené)? Do you wish to the delete downloaded files of the selected podcast episodes? Chcete smazat stažené soubory vybraných dílů zvukového záznamu? Do you wish to mark the selected podcast episodes as new? Chcete označit vybrané díly zvukového záznamu jako nové? Do you wish to mark the selected podcast episodes as listened? Chcete označit vybrané díly zvukového záznamu jako poslechnuté? Refresh all subscriptions? Obnovit všechny odběry? Refresh All Obnovit vše Refresh all subscriptions, or only those selected? Obnovit všechny odběry nebo jen ty vybrané? Refresh Selected Obnovit vybrané Search for tracks from soundcloud.com Hledat skladby na soundcloud.com Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Obrázek pozadí Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Obrázek umělce Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Vlastní obrázek: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Rozmazání: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10 px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Neprůhlednost: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40 % no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) Automaticky přepnout do pohledu po: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) Nepřepínat automaticky ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Tmavé pozadí Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Ztmavit pozadí a použít bílý text, bez ohledu na nynější barevnou paletu. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Vždy složit do jednoho pole Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) Ukázat jen umělce, album nebo skladbu, i když je dost místa na ukázání všech tří. Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Ukázat jen základní text na Wikipedii Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) Cantata ukáže pouze zkrácenou verzi stránek na Wikipedii (žádné obrázky, odkazy atd.). Toto ořezání obsahu není vždy stoprocentně přesné. To je důvodem toho, proč Cantata ve výchozím nastavení ukazuje pouze úvod. Pokud si zvolíte zobrazení celého článku, mohou se vyskytnout chyby ve zpracování. Také budete muset odstranit všechny články, které jsou nyní uloženy ve vyrovnávací paměti (pomocí stránky Vyrovnávací paměť) no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Dostupné: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Vybrané: Calculating size of files to be copied, please wait... i18n: file: devices/actiondialog.ui:86 i18n: ectx: property (text), widget (QLabel, fileS) Počítá se velikost souborů ke zkopírování. Počkejte, prosím... Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Kopírovat písně z: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Potřeba nastavit) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Kopírovat písně do: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Cílový formát: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Přepsat písně To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Ke kopírování: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Podrobnosti alba Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Rok: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Disk: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Umělec písně Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Vyhledání informací o albu a skladbě Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Zpočátku vyhledávat pomocí: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) Server CDDB: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) Přípojka CDDB: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Vyhledat informace, jakmile je CD vloženo Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Vytažení zvuku Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Úplný režim paranoia (nejlepší kvalita): Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Nikdy nepřeskakovat při chybě čtení These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) Tato nastavení jsou platná, a upravitelná, jen když je zařízení připojeno. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Složka s hudbou: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Kopírovat obaly alb jako: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Největší velikost obalu: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Výchozí svazek: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) Ošetření pro Různí umělci Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Automaticky prohledat hudbu při připojení Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Použít vyrovnávací paměť Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Názvy souborů Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Schéma názvu souboru: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) VFAT bezpečný Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Použít pouze znaky ASCII Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Nahradit mezery podtržítky Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) Připojit The ke jménům umělců If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' i18n: file: devices/devicepropertieswidget.ui:195 i18n: ectx: property (toolTip), widget (QCheckBox, ignoreThe) Pokud začíná jméno umělce na The, potom je připojit před název složky, např. z The Beatles se stane Beatles, The Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Překódování Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Překódovat pouze, když zdrojový soubor je v jiném formátu Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Příklad: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) O schématech pro názvy souborů The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) Umělec alba. U většiny alb je totožný s <i>umělcem skladby.</i> U sbírek půjde často o <i>různé umělce.</i> The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) Název alba. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Název alba The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) Skladatel. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) Umělec každé skladby. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Umělec skladby The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) Název skladby (bez <i>umělce skladby</i>). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) Název skladby (s <i>umělcem skladby</i>, pokud se liší od <i>umělce alba</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Název skladby (+Umělec) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Číslo skladby. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Skladba # The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) Číslo alba v případě alba složeného z více alb. Kompilace se často skládají z více alb. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) Rok vydání alba. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) Žánr alba. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) Tato nastavení jsou upravitelná, jen když není zařízení připojeno. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Typ: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Volby Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Přípojka: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Uživatel: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Doména: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Sdílení: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) Pokud zde zadáte heslo, bude uloženo <b>nezašifrované</b> v souboru s nastavením pro Cantatu. Aby Cantata vyzvala k zadání hesla před přístupem k sdílení, nastavte heslo na '-' Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Název služby: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Složka: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Další volby: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) Kvůli způsobu, jakým sshfs pracuje, bude k zadání hesla vyžadován vhodný program ssh-askpass (ksshaskpass, ssh-askpass-gnome, atd.). This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) Tento dialog se používá jen pro přidání vzdálených zařízení (např. přes Samba), nebo pro přístup k místně připojeným složkám. Pro běžné přehrávače záznamů, připojené přes USB, Cantata zobrazí zařízení automaticky, když je připojeno. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Název pro dynamická pravidla - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) sekund About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) O pravidlech Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Zahrnout písně odpovídající následujícímu: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Vyloučit písně odpovídající následujícímu: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Umělci podobní: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Umělec alba From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) Od roku: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Jakýkoli To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) Do roku: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Přesná shoda Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) Zadejte hodnoty pouze pro značky, které si přejete hledat. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) Když má žánr odpovídat různým žánrům, ukončete řetězec hvězdičkou. Např. 'rock*' odpovídá 'Hard Rock' a 'Rock and Roll'. Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Přidat místní soubor Save downloaded covers, artist, and composer images, in music folder i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) Uložit stažené obaly, umělce a obrázky skladatelů ve složce s hudbou Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Uložit stažená slova písně ve složce s hudbou Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Uložit stažená pozadí ve složce s hudbou If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) Pokud se rozhodnete, že má Cantata ukládat obaly, texty písní nebo obrázky pozadí do složky s hudbou, a nemáte přístupová práva pro zápis do této složky, Cantata se vrátí k zapisování souborů do vaší osobní složky pro ukládání vyrovnávací paměti. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) Cantata může pozadí, umělce a obrázky skladatelů ukládat ve složkách s hudbou a jejich podsložkách, pouze pokud tato hierarchie jde jen do dvou úrovní dolů. (např. 'Umělec/Album/Skladby'. Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) První spuštění Cantaty Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Vítejte v Cantatě <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>Cantata je klient pro Music Player Daemon (MPD). MPD je program pružný, schopný, běžící na pozadí (server), k přehrávání hudby.</p><p>Více informací o MPD naleznete na stránkách MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Tento průvodce vás provede základními nastaveními, která jsou potřeba pro to, aby Cantata pracovala správně.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Vítejte v Cantatě</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> i18n: file: gui/initialsettingswizard.ui:134 i18n: ectx: property (text), widget (QLabel, label_8) <p>Cantata je klient pro Music Player Daemon (MPD). MPD je program běžící na pozadí, který se používá k přehrávání hudby. MPD lze spustit buď pro celý systém, anebo uživatelsky.<br/><br/>Vyberte, prosím, jak chcete, aby byla Cantata na začátku připojena k (nebo spouštěna) MPD:</p> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Standardní nastavení pro více uživatelů/server <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> i18n: file: gui/initialsettingswizard.ui:172 i18n: ectx: property (text), widget (BuddyLabel, label_10) <i>Tuto volbu vyberte v případě, že vaše sbírka je sdílena mezi uživateli, vaše instance MPD běží na jiném stroji, když již máte osobní nastavení MPD, nebo chcete povolit přístup z jiných klientů (např. MPDroid). Pokud vyberete tuto volbu, Cantata sama nedokáže řídit spouštění a zastavování serveru MPD. Z toho důvodu budete muset zajistit, aby MPD bylo již nastaveno a běželo.</i> Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Základní nastavení pro jednoho uživatele <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> i18n: file: gui/initialsettingswizard.ui:217 i18n: ectx: property (text), widget (BuddyLabel, label_9) <i>Vyberte tuto volbu, pokud vaše hudební sbírka není sdílena s ostatními, a vy chcete, aby aby Cantata nastavila a řídila instanci MPD. Toto nastavení bude pro Cantatu výlučné a <b>nebude</b> přístupné pro jiné klienty (např. MPDroid)</i> For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. i18n: file: gui/initialsettingswizard.ui:259 i18n: ectx: property (text), widget (QLabel, label_11) Více informací o MPD naleznete na stránkách MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>Tento průvodce vás provede základními nastaveními, která jsou potřeba pro to, aby Cantata pracovala správně. Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Podrobnosti spojení The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Nastavení níže jsou základní nastavení požadovaná Cantatou. Zadejte, prosím, náležité podrobnosti a použijte tlačítko Spojit pro vyzkoušení spojení. The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) Nastavení složky s hudbou je používáno k vyhledávání obalu alba, textu písně atd. Pokud se vaše instance MPD nachází na vzdáleném počítači, můžete tuto nastavit na adresu HTTP. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Složka s hudbou Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Prosím, vyberte složku, která obsahuje vaši hudební sbírku. Covers and Lyrics i18n: file: gui/initialsettingswizard.ui:620 i18n: ectx: property (text), widget (QLabel, label_6f) Obaly a slova písně <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata stáhne chybějící obaly a texty písní z internetu.</p><p>U každého, prosím, potvrďte, zda chcete, aby Cantata soubory ukládala ve složce s hudbou, nebo ve vašich osobních složkách s vyrovnávací pamětí/pro ukládání nastavení.</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) Složka s hudbou je nastavena na adresu HTTP a Cantata v současnosti nedokáže nahrávat soubory na vnější servery HTTP. Z tohoto důvodu by se nastavení výše měla ponechat zakázána. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Dokončeno! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata je nyní nastavena!<br/><br/>Dialog pro nastavení Cantaty může být použit k přizpůsobení vzhledu Cantaty, a rovněž pro přidání dalších serverů MPD, a tak dále. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) Cantata skladby seskupí do alb pomocí značky AlbumUmělec, pokud je to tak nastaveno, jinak použije značku Umělec. Pokud máte alba s více umělci, <b>musíte</b> nastavit značku AlbumUmělec, aby při seskupování pracovala správně. Při této možnosti se doporučuje použít Různí umělci. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>Varování:</b> Nyní nejste členem skupiny uživatelé (users). Cantata bude pracovat lépe (ukládání obalů alb, textů písní atd. se správnými oprávněními), pokud vy (nebo váš správce systému) přidáte sebe sama do této skupiny. Pokud přidáte sebe sama, budete se muset odhlásit a zase přihlásit, aby se tato změna projevila. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Postranní panel Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Pohledy Use the checkboxes below to configure which views will appear in the sidebar. i18n: file: gui/interfacesettings.ui:48 i18n: ectx: property (text), widget (QLabel, label_2) Použijte zaškrtávací okénka níže k nastavení pohledů, které se objeví v postranním panelu. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. i18n: file: gui/interfacesettings.ui:61 i18n: ectx: property (text), widget (NoteLabel, sbPlayQueueLabel) Pokud není řada skladeb k přehrání výše zaškrtnuta, potom se objeví po straně ostatních pohledů. Pokud nejsou výše zaškrtnuty informace, pak bude do nástrojového pruhu přidáno tlačítko umožňující přístup k informacím o písni. Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Styl: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Poloha: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Ukázat jen ikony, žádný text Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Automatické skrývání Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Na začátku alba složit Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Automaticky rozbalit nynější album Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Projíždět k nynější skladbě Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) Vyzvat před vyprázdněním Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) Samostatná činnost (a klávesová zkratka) pro hledání v řadě skladeb určených k přehrání Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Obal nynějšího alba Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) Nástrojový pruh Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) Ukázat tlačítko pro zastavení Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) Ukázat obal nynější skladby Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) Ukázat hodnocení skladby External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Vnější Enable MPRIS D-BUS interface i18n: file: gui/interfacesettings.ui:404 i18n: ectx: property (text), widget (QCheckBox, enableMpris) Povolit rozhraní MPRIS D-BUS Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Ukázat vyskakovací zprávy při změnách skladeb Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Ukázat ikonu v oznamovací oblasti Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Při zavření zmenšit do oznamovací oblasti On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) Při spuštění Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Ukázat hlavní okno Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) Skrýt hlavní okno Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Obnovit předchozí stav Tweaks i18n: file: gui/interfacesettings.ui:482 i18n: ectx: attribute (title), widget (QWidget, tab_4z) Doladění Artist && Album Sorting i18n: file: gui/interfacesettings.ui:488 i18n: ectx: property (title), widget (QGroupBox, groupBox_2p) Třídění umělců a alb Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' i18n: file: gui/interfacesettings.ui:494 i18n: ectx: property (text), widget (QLabel, labelp) Zadejte (čárkou oddělený) seznam předpon, které se budou při třídění umělců a alb přehlížet, např. pokud nastaveno na The, potom se The Beatles budou třídit podle Beatles Enter comma separated list of prefixes... i18n: file: gui/interfacesettings.ui:504 i18n: ectx: property (placeholderText), widget (LineEdit, ignorePrefixes) Zadejte čárkou oddělený seznam předpon... Composer Support i18n: file: gui/interfacesettings.ui:514 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Podpora pro skladatele By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. i18n: file: gui/interfacesettings.ui:520 i18n: ectx: property (text), widget (QLabel, label) Ve výchozím nastavení Cantata používá značku Umělec alba (nebo značku Umělec, pokud píseň nemá žádného umělce alba) k seskupení písní a alb. U některých žánrů, např. Klasický, může být lepší použít značku Skladatel (pokud nastaven) k provedení tohoto seskupení. Zadejte, prosím, (čárkou oddělený) seznam žánrů, u nichž chcete, aby Cantata použila značku Skladatel. Enter comma separated list of genres... i18n: file: gui/interfacesettings.ui:530 i18n: ectx: property (placeholderText), widget (LineEdit, composerGenres) Zadejte čárkou oddělený seznam žánrů... If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' i18n: file: gui/interfacesettings.ui:546 i18n: ectx: property (text), widget (QLabel, label_3) pokud máte ve své sbírce mnoho umělců, kteří mají jen jednu skladbu, potom může být těžkopádné mít pro každého z nich jejich vlastní záznam v seznamu umělců. Můžete to obejít tím, že tyto skladby umístíte do samostatné složky a níže zadáte název této složky. Potom bude Cantata tyto seskupovat pod albem nazvaným Jednotlivé skladby s umělcem alba Různí umělci Folder that contains single track files... i18n: file: gui/interfacesettings.ui:556 i18n: ectx: property (placeholderText), widget (LineEdit, singleTracksFolders) Složka, která obsahuje soubory s jednotlivými skladbami... CUE Files i18n: file: gui/interfacesettings.ui:566 i18n: ectx: property (title), widget (QGroupBox, groupBox_3xx) Soubory CUE A cue file is a metadata file which describes how the tracks of a CD are laid out. i18n: file: gui/interfacesettings.ui:572 i18n: ectx: property (text), widget (QLabel, label_3x) Soubor CUE je soubor s popisnými údaji, který popisuje, jak jsou skladby na CD rozloženy. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. i18n: file: gui/interfacesettings.ui:588 i18n: ectx: property (text), widget (NoteLabel, tweaksLabel) Změna čehokoli výše bude vyžadovat obnovení databáze (a možná opětovné spuštění Cantaty), aby se tato změna projevila. General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Obecné Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) Natáhnout chybějící obaly z Last.fm Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Ukázat položku Smazat v nabídkách souvisejících činností Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Vynutit zapnutí položek jedním klepnutím <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>Toto změní rozhraní Cantaty, jak je to popsáno dále: <ul><li>Tlačítka pro přehrávání a ovládání budou o 33 % širší</li><li>Pohledy budou odstrčitelné.</li><li>Pro táhnutí položek budete potřebovat najet do horního levého rohu</li><li>Posuvníky budou široké jen několik pixelů</li><li>Činnosti (např. Přidat do řady skladeb k přehrání) budou vždy viditelné (ne jen tehdy, když je položka pod myší)</li><li>Otočná tlačítka budou mít tlačítka + a - na straně textového pole.</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) Udělat rozhraní dotekově přátelštější Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) Ukázat nástrojové rady s informacemi k písni Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) Podpora pro obrazovky retina (sítnice) Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Jazyk: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) Změna nastavení Vynutit zapnutí jedním klepnutím u položek vyžaduje opětovné spuštění Cantaty. Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) Změna nastavení jazyka vyžaduje opětovné spuštění Cantaty. Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) Změna nastavení rozhraní na dotykově přátelské vyžaduje opětovné spuštění Cantaty. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) Povolení podpory pro obrazovky retina (retina = sítnice; obrazovky s vyšší hustotou pixelů, než jakou měly předchozí modely, firma Apple) povede k ostřejším ikonám na obrazovkách typu "retina", ale může vést k méně ostrým ikonám na obrazovkách, které typu "retina" nejsou. Změna tohoto nastavení vyžaduje opětovné spuštění Cantaty. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dynamický] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Opustit režim zobrazení na celou obrazovku Fa&deout on stop: i18n: file: gui/playbacksettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, label_6b) Postupné &zeslabení zvuku při zastavení: Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Zastavit přehrávání při ukončení Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) Bránit uspání při přehrávání If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Pokud stisknete a podržíte tlačítko pro zastavení, ukáže se nabídka, v níž si budete moci zvolit, zda se má přehrávání zastavit nyní, nebo po nynější skladbě. (Tlačítko pro zastavení lze povolit v části nastavení Rozhraní/Nástrojový pruh) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Výstup &Crossfade between tracks: i18n: file: gui/playbacksettings.ui:112 i18n: ectx: property (text), widget (BuddyLabel, crossfadingLabel) &Prolínání mezi skladbami: s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s Replay &gain: i18n: file: gui/playbacksettings.ui:135 i18n: ectx: property (text), widget (BuddyLabel, replayGainLabel) Vyrovnání &hlasitosti: About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) O vyrovnání hlasitosti Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Použijte zaškrtávací okénka níže k ovládání činných výstupů. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Sbírka: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Název souboru obalu: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Souborový název (bez přípony), pod nímž se mají uložit stažené obaly.<br/> Jestliže bude ponecháno prázdné, použije se "cover".<br/><br/><i>%artist% bude nahrazen umělcem alba současné písně, a %album% bude nahrazeno názvem alba.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) Adresa (URL) proudu HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. i18n: file: gui/serversettings.ui:171 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) Nastavení pro hudební složku je používáno k vyhledání obrázků obalů. Může být nastaveno na adresu (URL) HTTP, pokud je vaše MPD na jiném stroji a obaly jsou přístupné přes HTTP. Pokud není nastaveno na adresu (URL) HTTP a vy máte i oprávnění pro zápis do této složky (a jejích podsložek), Cantata uloží všechny stažené obaly do příslušné složky s albem. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) Pokud není nastaveno žádné nastavení pro název souboru s obalem, pak bude Cantata používat výchozí <code>obal</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) Adresa (URL) proudu HTTP se používá jen tehdy, když máte nastaveno MPD na výstup do proudu HTTP, a přejete si, aby Cantata mohla tento proud přehrávat. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) Pokud změníte nastavení pro složku s hudbou, budete muset hudební databázi obnovovat ručně. To je možné provádět stisknutím tlačítka Obnovit databázi v pohledech s umělci nebo alby. Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Režim: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy HTTP SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy SOCKS Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Použijte zaškrtávací okénka níže k nastavení seznamu činných služeb. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Nastavit službu Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Odesílat informace o skladbách pomocí: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Stav: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Přihlášení Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Odesílat informace o skladbách Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Ukázat tlačítko pro ukázání náklonnosti k písničce You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Můžete poslouchat zdarma bez účtu, ale členové Premium mohou poslouchat proudy o vyšší kvalitě a bez reklam. Navštivte stránky <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>, kde můžete udělat povýšení na prémiový účet. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Prémiový účet Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Typ proudu: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Vypršení sezení: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Tato nastavení se použijí na Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm. If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) Pokud zadáte podrobnosti účtu, stav 'DI' (Digitally Imported) se objeví pod seznamem proudů. To ukáže, zda jste přihlášen nebo nejste. Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Použijte zaškrtávací okénka níže k nastavení seznamu činných poskytovatelů. Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) Vestavěné skupiny jsou zobrazovány kurzívou, a tyto nelze odstranit. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Hledání: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Zkratka pro vybranou činnost Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Výchozí: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Vlastní: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Umělec alba: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Číslo skladby: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Číslo disku: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) Hodnocení: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(Různé)</i> Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') i18n: file: tags/tageditor.ui:210 i18n: ectx: property (text), widget (PlainNoteLabel, label_7x) Více žánrů se musí oddělit čárkou (např. 'Rock;Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) Hodnocení jsou ukládána ve vnější databázi, <b>ne</b> v souboru s písní. Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Původní název New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Nový název Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) Hodnocení budou ztracena, pokud bude soubor přejmenován. Your names NAME OF TRANSLATORS Pavel Fric Your emails EMAIL OF TRANSLATORS pavelfric@seznam.cz Show All Tracks Ukázat všechny skladby Show Untagged Tracks Ukázat skladby bez značek Remove From List Odstranit ze seznamu Album Gain Zesílení alba Track Gain Zesílení skladby Album Peak Vrchol alba Track Peak Vrchol skladby Scan Prohledat Update ReplayGain tags in tracks? Obnovit značky pro vyrovnání hlasitosti ve skladbách? Update Tags Obnovit značky Abort scanning of tracks? Zrušit prohledávání značek? Abort Přerušit Abort reading of existing tags? Zrušit čtení stávajících značek? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Prohledat <b>všechny</b> skladby?<br/><br/><i>Všechny skladby mají existující značky pro vyrovnání hlasitost.</i> Do you wish to scan all tracks, or only tracks without existing tags? Chcete prohledat všechny skladby, nebo pouze soubory bez existujících značek? Untagged Tracks Skladby bez značek All Tracks Všechny skladby Scanning tracks... Prohledávají se skladby... Reading existing tags... Čtou se stávající značky... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (poškozené značky?) Failed to update the tags of the following tracks: Nepodařilo se zaktualizovat značky následujících skladeb: Device is not connected. Zařízení není připojeno. %1 dB %1 dB Failed Nepodařilo se Original: %1 dB Původní: %1 dB Original: %1 Původní: %1 Remove the selected tracks from the list? Odstranit vybrané skladby ze seznamu? Remove Tracks Odstranit skladby Invalid service Neplatná služba Invalid method Neplatná metoda Authentication failed Ověření se nezdařilo Invalid format Neplatný formát Invalid parameters Neplatné parametry Invalid resource specified Zadán neplatný zdroj Operation failed Operace se nezdařila Invalid session key Neplatný klíč sezení Invalid API key Neplatný klíč API Service offline Služba není dostupná Last.fm is currently busy, please try again in a few minutes Služba Last.fm je nyní zaneprázdněna. Zkuste to, prosím, znovu za několik minut Rate-limit exceeded Překročeno omezení na počet hodnocení %1 error: %2 %1 chyba: %2 %1: Loved Current Track %1: Nynější skladba byla zařazena mezi oblíbené %1: Love Current Track %1: Zařadit nynější skladbu mezi oblíbené %1 (via MPD) scrobbler name (via MPD) %1 (přes MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Pokud použijete odesílání údajů o skladbách (scrobbler), což je označeno jako '(přes MPD)' (například %1), potom musíte mít tuto službu již spuštěnu a běžící. Cantata umí skladby Zařadit mezi oblíbené jen pomocí této služby, a neumí odesílání údajů o skladbách zakázat/povolit. Authenticating... Ověřuje se pravost... Authenticated Pravost ověřena Not Authenticated Pravost neověřena %1: Scrobble Tracks %1: Odesílat informace o skladbách Digitally Imported Settings Nastavení pro Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Odhlásit se URL: Adresa (URL): Add Stream Přidat proud Edit Stream Upravit proud <i><b>ERROR:</b> Invalid protocol</i> <i><b>CHYBA:</b> Neplatný protokol</i> Loading %1 Nahrává se %1 Installed Nainstalováno Update available Aktualizace dostupná Check the providers you wish to install/update. Prověřit poskytovatele, které si přejete nainstalovat/aktualizovat. Install/Update Stream Providers Nainstalovat/Aktualizovat poskytovatele proudů Downloading list... Stahuje se seznam... Digitally Imported Digitally Imported Local and National Radio (ListenLive) Místní a národní rádio (Poslouchat živě) Failed to download list of stream providers! Nepodařilo se stáhnout seznam poskytovatelů proudů! Installing/updating %1 Instaluje se/Aktualizuje se %1 Failed to install '%1' Nepodařilo se nainstalovat '%1' Failed to download '%1' Nepodařilo se stáhnout '%1' Install/update the selected stream providers? Nainstalovat/Aktualizovat poskytovatele vybraných proudů? Install the selected stream providers? Nainstalovat poskytovatele vybraných proudů? Update the selected stream providers? Aktualizovat poskytovatele vybraných proudů? Install/Update Instalovat/Aktualizovat Abort installation/update? Zrušit instalaci/aktualizaci? Downloading %1 Stahuje se %1 Update all updateable providers Aktualizovat všechny aktualizovatelné poskytovatele Import Streams Into Favorites Zavést proudy do oblíbených Export Favorite Streams Vyvést oblíbené proudy Add New Stream To Favorites Přidat nový proud do oblíbených Seatch For Streams Hledat rozhlasové proudy Digitally Imported Service name Digitally Imported Import Streams Zavést proudy XML Streams (*.xml *.xml.gz *.cantata) Proudy XML (*.xml *.xml.gz *.cantata) Export Streams Vyvést proudy XML Streams (*.xml.gz) Proudy XML (*.xml.gz) Failed to create '%1'! Nepodařilo se vytvořit '%1'! Stream '%1' already exists! Proud '%1' již existuje! A stream named '%1' already exists! Proud pojmenovaný '%1' již existuje! Bookmark added Záložka přidána Already bookmarked Již opatřeno záložkou Already in favorites Již v oblíbených Reload '%1' streams? Nahrát znovu '%1' proudů? Are you sure you wish to remove bookmark to '%1'? Opravdu chcete odstranit záložku k '%1'? Are you sure you wish to remove all '%1' bookmarks? Opravdu chcete odstranit všech '%1' záložek? Are you sure you wish to remove the %1 selected streams? Opravdu chcete odstranit %1 vybrané proudy? Are you sure you wish to remove '%1'? Opravdu chcete odstranit '%1'? Added '%1'' to favorites Přidáno "%1'' do oblíbených Configure Streams Nastavit proudy From File... Ze souboru... Download... Stáhnout... Configure Provider Nastavit poskytovatele Install Instalovat Install Streams Nainstalovat proudy Cantata Streams (*.streams) Proudy Cantata (*.streams) A category named '%1' already exists! Overwrite? Skupina pojmenovaná '%1' již existuje! Přepsat? Failed top open package file. Nepodařilo se otevřít soubor s balíčkem. Invalid file format! Neplatný souborový formát! Failed to create stream category folder! Nepodařilo se vytvořit složku pro skupinu proudu! Failed to save stream list! Nepodařilo se uložit seznam proudu. Failed to remove streams folder! Nepodařilo se odstranit složku s proudy! &OK &OK &Cancel &Zrušit &Yes &Ano &No &Ne &Discard Za&hodit &Save &Uložit &Apply &Použít &Close &Zavřít &Overwrite &Přepsat &Reset Nastavit &znovu &Continue &Pokračovat &Delete &Smazat &Stop &Zastavit &Remove &Odstranit &Previous &Předchozí &Next &Další Configure... Nastavit... Password Heslo Please enter password: Zadejte, prosím, heslo: Close Zavřít Warning Varování Question Otázka &Window &Okno Minimize Zmenšit Zoom Zvětšení Select Folder Vybrat složku Select File Vybrat soubor %1 B %1 B %1 kB %1 KB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags Značky Set 'Album Artist' from 'Artist' Nastavit Umělec alba z Umělec Read Ratings from File Načíst hodnocení ze souboru Write Ratings to File Zapsat hodnocení do souboru All tracks Všechny skladby Apply "Various Artists" workaround to <b>all</b> tracks? Použít zařazení pod Různí umělci na <b>všechny</b> skladby? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Toto nastaví Umělce alba a Umělce na Různí umělci, a nastaví Název na "Umělec skladby - Název skladby"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Zvrátit zařazení pod Různí umělci pro <b>všechny</b> skladby? Revert "Various Artists" workaround Zvrátit zařazení pod Různí umělci <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Tam, kde Umělec alba je stejný jako Umělec a Název je ve formátu "Umělec skladby - Název skladby", se Umělec vezme z Názvu a samotný název se nastaví na prostý Název. Např. <br/><br/>Pokud je Název "Wibble - Wobble", pak Umělec se nastaví na "Wibble" a Název na "Wobble"</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Nastavit Umělec alba z Umělec (pokud je Umělec alba prázdný) pro <b>všechny</b> skladby? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Nastavit Umělec alba z Umělec (pokud je Umělec alba prázdný)? Album Artist from Artist Umělec alba z Umělec Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Psát velkým písmenem první písmeno textových polí (např. Název, Umělec, Umělec alba, Album atd.) u <b>všech</b> skladeb? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Psát velkým písmenem první písmeno textových polí (např. Název, Umělec, Umělec alba, Album atd.)? Adjust the value of each track number by: Upravit hodnotu čísla každé skladby o: Read ratings for all tracks from the music files? Načíst hodnocení u všech skladeb z hudebních souborů? Read rating from music file? Načíst hodnocení z hudebního souboru? Ratings Hodnocení Read Ratings Načíst hodnocení Read Rating Načíst hodnocení Read, and updated, ratings from the following tracks: Načíst aktualizovaná hodnocení z následujících skladeb: Not all Song ratings have been read from MPD! Ne všechna hodnocení písní byla načtena z MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Hodnocení písní nejsou uložena v souborech s písněmi, ale uvnitř databáze MPD. Pro jejich uložení do skutečného souboru je Cantata nejprve musí načíst z MPD. Song rating has not been read from MPD! Hodnocení písně nebylo načteno z MPD! Write ratings for all tracks to the music files? Načíst hodnocení u všech skladeb do hudebních souborů? Write rating to music file? Zapsat hodnocení do hudebního souboru? Write Ratings Zapsat hodnocení Write Rating Zapsat hodnocení Failed to write ratings of the following tracks: Nepodařilo se zapsat hodnocení následujících skladeb: Failed to write rating to music file! Nepodařilo se zapsat hodnocení do hudebního souboru! All tracks [modified] Všechny skladby [změněno] %1 [modified] %1 [změněno] Would you also like to rename your song files, so as to match your tags? Chcete také přejmenovat své soubory s písněmi tak, aby se shodovaly se značkami? Rename Files Přejmenovat soubory Abort renaming of files? Zrušit přejmenování souborů? Source file does not exist! Zdrojový soubor neexistuje! Destination file already exists! Cílový soubor již existuje! Failed to create destination folder! Nepodařilo se vytvořit cílovou složku! Failed to rename '%1' to '%2' Nepodařilo se přejmenovat '%1' na '%2' Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Hodnocení písní nejsou uložena v souborech s písněmi, ale uvnitř databáze MPD. Pokud soubor přejmenujete (nebo složku, v níž je), potom bude hodnocení spojené s písní ztraceno. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Skladatel:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Účinkující:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Umělec:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Rok: </b></td><td>%3</td></tr> Filter On Genre Filtr na žánr All Genres Všechny žánry Go Back Jít zpět Menu Nabídka (Stream) (Proud) Search... Hledání... Close Search Bar Zavřít vyhledávací pole Logged into %1 Přihlášen k %1 <b>NOT</b> logged into %1 <b>NENÍ</b>přihlášen k %1 Basic Tree (No Icons) Základní strom (žádné ikony) Simple Tree Jednoduchý strom Detailed Tree Podrobný strom List Seznam Grid Mřížka View Pohled Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Nelze přistupovat k souborům s písněmi! Prověřte, prosím, nastavení složky s hudbou Cantaty a nastavení adresáře s hudbou MPD (music_directory). Cannot access song files! Please check that the device is still attached. Nelze přistupovat k souborům s písněmi! Prověřte, prosím, že zařízení je stále ještě připojeno. Stretch Columns To Fit Window Roztáhnout sloupce tak, aby se vešly do okna Center Střed Alignment Zarovnání (Various) (Různí) Click to go back Klepněte pro návrat zpět Add All To Play Queue Přidat vše do řady skladeb k přehrání Add All And Replace Play Queue Přidat vše a nahradit řadu skladeb k přehrání Mute Ztlumit Unmute Zrušit ztišení Volume %1% (Muted) Hlasitost: %1% (ztlumeno) Volume %1% Hlasitost %1% 1 Track Singular Skladby: 1 %1 Tracks Plural (N!=1) Skladby: %1 1 Track (%1) Singular Skladby: 1 (%1) %1 Tracks (%2) Plural (N!=1) Skladby: %1 (%2) 1 Album Singular Alba: 1 %1 Albums Plural (N!=1) Alba: %1 1 Artist Singular Umělci: 1 %1 Artists Plural (N!=1) Umělci: %1 1 Stream Singular Proudy: 1 %1 Streams Plural (N!=1) Proudy: %1 1 Entry Singular Položky: 1 %1 Entries Plural (N!=1) Položky: %1 1 Rule Singular Pravidla: 1 %1 Rules Plural (N!=1) Pravidla: %1 1 Podcast Singular Jeden záznam %1 Podcasts Plural (N!=1) Záznamy: %1 1 Episode Singular Jeden díl %1 Episodes Plural (N!=1) Díly: %1 1 Update available Singular Jedna aktualizace dostupná %1 Updates available Plural (N!=1) %1 aktualizací dostupných ActionDialog Calculating size of files to be copied, please wait... Počítá se velikost souborů ke zkopírování. Počkejte, prosím... Copy songs from: Kopírovat písně z: Configure Nastavit (Needs configuring) (Potřeba nastavit) Copy songs to: Kopírovat písně do: Destination format: Cílový formát: Overwrite songs Přepsat písně To copy: Ke kopírování: <b>INVALID</b> <b>NEPLATNÝ</b> <i>(When different)</i> <i>(Když jiný)</i> Artists:%1, Albums:%2, Songs:%3 Umělci:%1, Alba:%2, Písně:%3 %1 free %1 volno Local Music Library Místní hudební knihovna Audio CD Audio CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Na cílovém zařízení nezbývá dost místa. Vybrané písně zabírají %1, ale zbývá jen %2. Písně bude třeba překódovat na menší velikost souborů, aby mohly být úspěšně zkopírovány. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. V cíli nezbývá dost místa. Vybrané písně zabírají %1, ale zbývá jen %2. Copy Songs To Library Kopírovat písně do knihovny Copy Songs To Device Kopírovat písně do zařízení Copy Songs Kopírovat písně Delete Songs Smazat písně You have not configured the destination device. Continue with the default settings? Nenastavil jste cílové zařízení. Pokračovat s výchozím nastavením? Not Configured Nenastaveno Use Defaults Použít výchozí nastavení You have not configured the source device. Continue with the default settings? Nenastavil jste zdrojové zařízení. Pokračovat s výchozím nastavením? Are you sure you wish to stop? Opravdu chcete zastavit? Stop Zastavit Device has been removed! Zařízení bylo odstraněno! Device is not connected! Zařízení není připojeno! Device is busy? Zařízení je zaneprázdněno? Device has been changed? Zařízení bylo změněno? Clearing unused folders Uklízí se nepoužívané složky Calculate ReplayGain for ripped tracks? Spočítat vyrovnání hlasitosti skladeb pro vytažené skladby? ReplayGain Vyrovnání hlasitosti Calculate Spočítat The destination filename already exists! Cílový souborový název již existuje! Song already exists! Píseň již existuje! Song does not exist! Píseň neexistuje! Failed to create destination folder!<br/>Please check you have sufficient permissions. Nepodařilo se vytvořit cílovou složku!<br/>Prověřte, prosím, zda máte dostatečná oprávnění. Source file no longer exists? Zdrojový soubor už neexistuje? Failed to copy. Nepodařilo se zkopírovat. Failed to delete. Nepodařilo se smazat. Not connected to device. Nepřipojeno k zařízení. Selected codec is not available. Vybraný kodek není dostupný. Transcoding failed. Překódování se nezdařilo. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Nepodařilo se vytvořit dočasný soubor.<br/>(Požadováno pro překódování na zařízení MTP.) Failed to read source file. Nepodařilo se přečíst zdrojový soubor. Failed to write to destination file. Nepodařilo se zapsat do cílového souboru. No space left on device. Na zařízení není žádné místo. Failed to update metadata. Nepodařilo se zaktualizovat popisná data. Failed to download track. Nepodařilo se stáhnout skladbu. Failed to lock device. Nepodařilo se uzamknout zařízení. Local Music Library Properties Vlastnosti místní hudební knihovny Error Chyba Skip Přeskočit Auto Skip Automaticky přeskočit Retry Zkusit znovu Artist: Umělec: Album: Album: Track: Skladba: Source file: Zdrojový soubor: Destination file: Cílový soubor: File: Soubor: Saving cache Ukládá se vyrovnávací paměť Calculating... Počítá se... Time remaining: Zbývající čas: AlbumDetails Album Details Podrobnosti alba Artist: Umělec: Composer: Skladatel: Title: Název: Genre: Žánr: Year: Rok: Disc: Disk: Single artist Umělec písně Tracks Skladby Track Skladba Artist Umělec Title Název AlbumDetailsDialog Audio CD Audio CD Apply "Various Artists" Workaround Použít zařazení pod Různí umělci Revert "Various Artists" Workaround Zvrátit zařazení pod Různí umělci Capitalize Psát velkými písmeny Adjust Track Numbers Upravit čísla skladeb Tools Nástroje Apply "Various Artists" workaround? Použít zařazení pod Různí umělci? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Toto nastaví Umělce alba a Umělce na Různí umělci, a nastaví Název na "Umělec skladby - Název skladby" Revert "Various Artists" workaround? Zvrátit zařazení pod Různí umělci? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Tam, kde Umělec alba je stejný jako Umělec a Název je ve formátu "Umělec skladby - Název skladby", se Umělec vezme z Názvu a samotný název se nastaví na prostý Název. Např. Pokud je Název "Wibble - Wobble", pak Umělec se nastaví na "Wibble" a Název na "Wobble" Revert Vrátit Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Psát velkým písmenem první písmeno Názvu, Umělce, Umělce alba a Alba? Adjust track number by: Upravit číslo skladby o: AlbumView Refresh Album Information Obnovit informace o albu Album Album Tracks Skladby ArtistView Refresh Artist Information Obnovit informace o umělci Artist Umělec Albums Alba Web Links Internetové odkazy Similar Artists Podobní umělci AudioCdDevice Reading disc Čte se disk %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) AudioCdSettings Album and Track Information Retrieval Vyhledání informací o albu a skladbě Initially look up via: Zpočátku vyhledávat pomocí: CDDB Host: Server CDDB: CDDB Port: Přípojka CDDB: Lookup information as soon as CD is inserted Vyhledat informace, jakmile je CD vloženo Audio Extraction Vytažení zvuku Full paranoia mode (best quality) Úplný režim paranoia (nejlepší jakost): Never skip on read error Nikdy nepřeskakovat při chybě čtení CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet List CUE Playlist Seznam skladeb CacheItem Deleting... Maže se... Calculating... Počítá se... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata ukládá různé kousky údajů (obaly, slova písní atd.) do vyrovnávací paměti. Dole je přehled nynějšího využití vyrovnávací paměti Cantatou. Covers Obaly Scaled Covers Obaly se změněnou velikostí Backdrops Pozadí Lyrics Slova písně Artist Information Informace o umělci Album Information Informace o albu Track Information Informace o skladbě Stream Listings Soupisy proudů Podcast Directories Adresáře se záznamy (podcasty) Wikipedia Languages Jazyky Wikipedie Scrobble Tracks Odesílat informace o skladbách Delete All Smazat vše Delete all '%1' items? Smazat všech '%1' položek? Delete Cache Items Smazat položky ve vyrovnávací paměti Delete items from all selected categories? Smazat položky z vybraných skupin? CacheTree Name Název Item Count Počet položek Space Used Použité místo CddbInterface Data Track Datová stopa Failed to open CD device Nepodařilo se otevřít zařízení CD Track %1 Stopa %1 Failed to create CDDB connection Nepodařilo se vytvořit připojení CDDB Failed to contact CDDB server, please check CDDB and network settings Nepodařilo se spojit se se serverem CDDB. Prověřte, prosím, nastavení CDDB a sítě No matches found in CDDB V CDDB nenalezeny žádné shody CDDB error: %1 Chyba CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Bylo nalezeno více shod. Vyberte, prosím, náležitou shodu z uvedených níže: Artist Umělec Title Název Disc Selection Výběr disku %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disk %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Poskytovatelé textů písní Wikipedia Languages Jazyky Wikipedie Other Jiné ContextWidget &Artist &Umělec Al&bum &Album &Track &Skladba CoverDialog Search Hledat Add a local file Přidat místní soubor Configure Nastavit This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. Toto lze použít jen na změnu souboru použitého pro obaly. Nezmění žádný vložený obal, který by se nacházel u souborů s písněmi. CoverArt Archive Archiv obalů An image already exists for this artist, and the file is not writeable. Pro tohoto umělce již existuje jeden obrázek. Soubor však není zapisovatelný. A cover already exists for this album, and the file is not writeable. Pro toto album již existuje jeden obrázek. Soubor však není zapisovatelný. '%1' Artist Image '%1' Obrázek umělce '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Obal alba Failed to set cover! Could not download to temporary file! Nepodařilo se nastavit obal! Nepodařilo se stáhnout do dočasného souboru! Failed to download image! Nepodařilo se stáhnout obrázek! Load Local Cover Nahrát místní obal Images (*.png *.jpg) Obrázky (*.png *.jpg) File is already in list! Soubor je již v seznamu! Failed to read image! Nepodařilo se přečíst obrázek! Display Zobrazit Remove Odstranit Failed to set cover! Could not make copy! Nepodařilo se nastavit obal! Nepodařilo se udělat kopii! Failed to set cover! Could not backup original! Nepodařilo se nastavit obal! Nepodařilo se zazálohovat originál! Failed to set cover! Could not copy file to '%1'! Nepodařilo se nastavit obal! Nepodařilo se zkopírovat soubor do '%1'! Searching... Hledá se... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Skladatel:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Účinkující:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Umělec:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Rok: </b></td><td>%3</td></tr> CoverPreview Image Obrázek Downloading... Stahuje se... Image (%1 x %2 %3%) Image (width x height zoom%) Obrázek (%1 x %2 %3%) CustomActionDialog Name: Název: Command: Příkaz: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. V příkazovém řádku výše bude %f nahrazeno seznamem souborů a %d seznamem složek. pokud není dodán ani jeden z nich, bude k příkazu připojen seznam souborů. Add New Command Přidat nový příkaz Edit Command Upravit příkaz CustomActions Custom Actions Vlastní činnosti CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Aby Cantata volala vnější příkazy (např. úprava značek v jiném programu), přidejte položku pro příkaz níže. Když je stanoven alespoň jeden příkaz, bude do nabídek souvisejících činností v pohledech na knihovnu, na složky a na seznamy skladeb přidána položka Vlastní činnosti. Add Přidat Edit Upravit Remove Odstranit Name Název Command Příkaz Remove the selected commands? Odstranit vybrané příkazy? Device Updating (%1)... Obnovuje se (%1)... Updating (%1%)... Obnovuje se (%1%)... DevicePropertiesDialog Device Properties Vlastnosti zařízení DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Tato nastavení jsou platná, a upravitelná, jen když je zařízení připojeno. Name: Název: Music folder: Složka s hudbou: Copy album covers as: Kopírovat obaly alb jako: Maximum cover size: Největší velikost obalu: Default volume: Výchozí svazek: 'Various Artists' workaround Ošetření pro Různí umělci Automatically scan music when attached Automaticky prohledat hudbu při připojení Use cache Použít vyrovnávací paměť Filenames Názvy souborů Filename scheme: Schéma názvu souboru: VFAT safe VFAT bezpečný Use only ASCII characters Použít pouze znaky ASCII Replace spaces with underscores Nahradit mezery podtržítky Append 'The' to artist names Připojit The ke jménům umělců If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Pokud začíná jméno umělce na The, potom je připojit před název složky, např. z The Beatles se stane Beatles, The Transcoding Překódování Only transcode if source file is of a different format Překódovat pouze, když zdrojový soubor je v jiném formátu Only transcode if source is FLAC/WAV Překódovat pouze, když zdrojový soubor je ve formátu FLAC/WAV Don't copy covers Nekopírovat obaly Embed cover within each file Vložit obal do každého souboru No maximum size Žádná největší velikost 400 pixels 400 pixelů 300 pixels 300 pixelů 200 pixels 200 pixelů 100 pixels 100 pixelů <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Když se skladby kopírují na zařízení, a 'Umělec alba' je nastaven na 'Různí umělci', Cantata nastaví značku 'Umělec' u všech skladeb na 'Různí umělci' a značku 'Název' skladby na 'Umělec skladby - Název skladby'.<hr/> Když se kopíruje ze zařízení, Cantata prověří, zda jsou 'Umělec alba' a 'Umělec' oba nastaveni na 'Různí umělci'. Pokud ano, pokusí se vytáhnout skutečného umělce ze značky 'Název' a odstranit umělcovo jméno ze značky 'Název'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Pokud toto povolíte, Cantata vytvoří vyrovnávací paměť hudební knihovny zařízení. To pomůže urychlit následné prohledávání knihovny (protože namísto čtení značky u každého souboru se použije vyrovnávací paměť.)<hr/><b>POZNÁMKA:</b> Pokud na obnovu knihovny zařízení použijete jiný program, potom se tato vyrovnávací paměť stane zastaralou. Abyste to dal do pořádku, jednoduše klepněte na ikonu pro 'obnovu' v seznamu zařízení. To způsobí odstranění souboru s vyrovnávací pamětí a opětovné znovuprohledání obsahu zařízení.</p> Do not transcode Nepřekódovat Encoder Kodér Transcode to %1 Překódovat do %1 %1 (%2 free) name (size free) %1 (%2 volné) DevicesModel Configure Device Nastavit zařízení Refresh Device Obnovit zařízení Connect Device Připojit zařízení Disconnect Device Odpojit zařízení Edit CD Details Upravit podrobnosti CD Not Connected Nepřipojeno No Devices Attached Nepřipojeno žádné zařízení DevicesPage Copy To Library Kopírovat do knihovny Synchronise Seřídit Forget Device Zapomenout zařízení Add Device Přidat zařízení Lookup album and track details? Vyhledat podrobnosti alba a skladby? Refresh Obnovit Via CDDB Pomocí CDDB Via MusicBrainz Pomocí MusicBrainz Which type of refresh do you wish to perform? Který typ aktualizace chcete provést? Partial - Only new songs are scanned (quick) Částečný - Jsou prohledány pouze nové písně (rychlé) Full - All songs are rescanned (slow) Úplný - Jsou prohledány všechny písně (pomalé) Partial Částečný Full Úplný Are you sure you wish to delete the selected songs? This cannot be undone. Opravdu chcete odstranit vybrané písně? Tento krok nelze vrátit zpět. Delete Songs Smazat písně Are you sure you wish to forget '%1'? Opravdu chcete zapomenout na '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Opravdu chcete vysunout zvukové CD '%1 - %2'? Eject Vysunout Are you sure you wish to disconnect '%1'? Opravdu chcete odpojit '%1'? Disconnect Odpojit Please close other dialogs first. Nejprve, prosím, zavřete další dialogy. DigitallyImported Not logged in Nepřihlášen Logged in Přihlášen Unknown error Neznámá chyba No subscriptions Žádné odběry You do not have an active subscription Nemáte žádný činný odběr Logged in (expiry:%1) Přihlášen (vypršení: %1) Session expired Sezení vypršelo DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Můžete poslouchat zdarma bez účtu, ale členové Premium mohou poslouchat proudy o vyšší kvalitě a bez reklam. Navštivte stránky <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>, kde můžete udělat povýšení na prémiový účet. Premium Account Prémiový účet Username: Uživatelské jméno: Password: Heslo: Stream type: Typ proudu: Status: Stav: Login Přihlášení Session expiry: Vypršení sezení: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Tato nastavení se použijí na Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm. If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Pokud zadáte podrobnosti účtu, stav 'DI' (Digitally Imported) se objeví pod seznamem proudů. To ukáže, zda jste přihlášen nebo nejste. Digitally Imported Settings Nastavení pro Digitally Imported MP3 256k MP3 256 k AAC 64k AAC 64 k AAC 128k AAC 128 k Not Authenticated Pravost neověřena Authenticating... Ověřuje se pravost... Authenticated Pravost ověřena Logout Odhlásit se DockMenu Play Přehrát Pause Pozastavit DynamicPlaylists Start Dynamic Playlist Spustit dynamický seznam skladeb Stop Dynamic Mode Zastavit dynamický režim Dynamic Playlists Dynamické seznamy skladeb Dynamically generated playlists Dynamicky tvořené seznamy skladeb You need to install "perl" on your system in order for Cantata's dynamic mode to function. Aby pracoval dynamický režim Cantaty, musíte do vašeho systému nainstalovat "perl". Failed to locate rules file - %1 Nepodařilo se najít soubor s pravidly - %1 Failed to remove previous rules file - %1 Nepodařilo se odstranit předchozí soubor s pravidly - %1 Failed to install rules file - %1 -> %2 Nepodařilo se nainstalovat soubor s pravidly - %1 -> %2 Dynamizer has been terminated. Dynamizátor byl ukončen. Awaiting response for previous command. (%1) Očekává se odpověď na předchozí příkaz. (%1) Saving rule Ukládá se pravidlo Deleting rule Maže se pravidlo Failed to save %1. (%2) Nepodařilo se uložit %1. (%2) Failed to delete rules file. (%1) Nepodařilo se smazat soubor s pravidly. (%1) Failed to control dynamizer state. (%1) Nepodařilo se zkontrolovat stav dynamizátoru. (%1) Failed to set the current dynamic rules. (%1) Nepodařilo se nastavit nynější dynamická pravidla. (%1) DynamicPlaylistsPage Add Přidat Edit Upravit Remove Odstranit Remote dynamizer is not running. Vzdálený dynamizátor neběží. Are you sure you wish to remove the selected rules? This cannot be undone. Opravdu chcete odstranit vybraná pravidla? Tento krok nelze vrátit zpět. Remove Dynamic Rules Odstranit dynamická pravidla FancyTabWidget Configure... Nastavit... FileSettings Save downloaded covers, artist, and composer images, in music folder Uložit stažené obaly, umělce a obrázky skladatelů ve složce s hudbou Save downloaded lyrics in music folder Uložit stažená slova písně ve složce s hudbou Save downloaded backdrops in music folder Uložit stažená pozadí ve složce s hudbou If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Pokud se rozhodnete, že má Cantata ukládat obaly, texty písní nebo obrázky pozadí do složky s hudbou, a nemáte přístupová práva pro zápis do této složky, Cantata se vrátí k zapisování souborů do vaší osobní složky pro ukládání vyrovnávací paměti. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata může pozadí, umělce a obrázky skladatelů ukládat ve složkách s hudbou a jejich podsložkách, pouze pokud tato hierarchie jde jen do dvou úrovní dolů. (např. 'Umělec/Album/Skladby'. FilenameSchemeDialog Example: Příklad: About filename schemes O schématech pro názvy souborů The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Umělec alba. U většiny alb je totožný s <i>umělcem skladby.</i> U sbírek půjde často o <i>různé umělce.</i> Album Artist Umělec alba The name of the album. Název alba. Album Title Název alba The composer. Skladatel. Composer Skladatel The artist of each track. Umělec každé skladby. Track Artist Umělec skladby The track title (without <i>Track Artist</i>). Název skladby (bez <i>umělce skladby</i>). Track Title Název skladby The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Název skladby (s <i>umělcem skladby</i>, pokud se liší od <i>umělce alba</i>). Track Title (+Artist) Název skladby (+Umělec) The track number. Číslo skladby. Track # Skladba # The album number of a multi-album album. Often compilations consist of several albums. Číslo alba v případě alba složeného z více alb. Kompilace se často skládají z více alb. CD # CD # The year of the album's release. Rok vydání alba. Year Rok The genre of the album. Žánr alba. Genre Žánr Filename Scheme Schéma názvu souboru Various Artists Example album artist Různí umělci Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Nyní 5001 Wobble Example song name Výkyv Dance Example genre Tanec The following variables will be replaced with their corresponding meaning for each track name. Následující proměnné budou nahrazeny jejich odpovídajícím významem pro každý název skladby. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Proměnná</em></th><th><em>Tlačítko</em></th><th><em>Popis</em></th></tr> FolderPage Open In File Manager Otevřít ve správci souborů Are you sure you wish to delete the selected songs? This cannot be undone. Opravdu chcete odstranit vybrané písně? Tento krok nelze vrátit zpět. Delete Songs Smazat písně FsDevice Updating... Obnovuje se... Reading cache Čte se vyrovnávací paměť Saving cache Ukládá se vyrovnávací paměť %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Filtr na žánr All Genres Všechny žánry GroupedViewDelegate Audio CD Audio CD Streams Proudy %n Track(s) Skladby: %n Skladby: %n Skladby: %n InitialSettingsWizard Cantata First Run První spuštění Cantaty Welcome to Cantata Vítejte v Cantatě <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata je klient pro Music Player Daemon (MPD). MPD je program pružný, schopný, běžící na pozadí (server), k přehrávání hudby.</p><p>Více informací o MPD naleznete na stránkách MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Tento průvodce vás provede základními nastaveními, která jsou potřeba pro to, aby Cantata pracovala správně.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Vítejte v Cantatě</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>Cantata je klient pro Music Player Daemon (MPD). MPD je program běžící na pozadí, který se používá k přehrávání hudby. MPD lze spustit buď pro celý systém, anebo uživatelsky.<br/><br/>Vyberte, prosím, jak chcete, aby byla Cantata na začátku připojena k (nebo spouštěna) MPD:</p> Standard multi-user/server setup Standardní nastavení pro více uživatelů/server <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>Tuto volbu vyberte v případě, že vaše sbírka je sdílena mezi uživateli, vaše instance MPD běží na jiném stroji, když již máte osobní nastavení MPD, nebo chcete povolit přístup z jiných klientů (např. MPDroid). Pokud vyberete tuto volbu, Cantata sama nedokáže řídit spouštění a zastavování serveru MPD. Z toho důvodu budete muset zajistit, aby MPD bylo již nastaveno a běželo.</i> Basic single user setup Základní nastavení pro jednoho uživatele <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>Vyberte tuto volbu, pokud vaše hudební sbírka není sdílena s ostatními, a vy chcete, aby aby Cantata nastavila a řídila instanci MPD. Toto nastavení bude pro Cantatu výlučné a <b>nebude</b> přístupné pro jiné klienty (např. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Pokud chcete mít pokročilé nastavení MPD (např. více výstupů zvuku, plnou podporu pro DSD atd.), potom <b>musíte</b> vybrat Obvyklý For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Více informací o MPD naleznete na stránkách MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>Tento průvodce vás provede základními nastaveními, která jsou potřeba pro to, aby Cantata pracovala správně. Connection details Podrobnosti spojení The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Nastavení níže jsou základní nastavení požadovaná Cantatou. Zadejte, prosím, náležité podrobnosti a použijte tlačítko Spojit pro vyzkoušení spojení. Host: Server: Password: Heslo: Music folder: Složka s hudbou: Connect Spojit The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Nastavení složky s hudbou je používáno k vyhledávání obalu alba, textu písně atd. Pokud se vaše instance MPD nachází na vzdáleném počítači, můžete tuto nastavit na adresu HTTP. Music folder Složka s hudbou Please choose the folder containing your music collection. Prosím, vyberte složku, která obsahuje vaši hudební sbírku. Covers and Lyrics Obaly a slova písně <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata stáhne chybějící obaly a texty písní z internetu.</p><p>U každého, prosím, potvrďte, zda chcete, aby Cantata soubory ukládala ve složce s hudbou, nebo ve vašich osobních složkách s vyrovnávací pamětí/pro ukládání nastavení.</p> Save downloaded covers, artist, and composer images, in music folder Uložit stažené obaly, umělce a obrázky skladatelů ve složce s hudbou Save downloaded lyrics in music folder Uložit stažená slova písně ve složce s hudbou Save downloaded backdrops in music folder Uložit stažená pozadí ve složce s hudbou If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Pokud se rozhodnete, že má Cantata ukládat obaly, texty písní nebo obrázky pozadí do složky s hudbou, a nemáte přístupová práva pro zápis do této složky, Cantata se vrátí k zapisování souborů do vaší osobní složky pro ukládání vyrovnávací paměti. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata může pozadí, umělce a obrázky skladatelů ukládat ve složkách s hudbou a jejich podsložkách, pouze pokud tato hierarchie jde jen do dvou úrovní dolů. (např. 'Umělec/Album/Skladby'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Složka s hudbou je nastavena na adresu HTTP a Cantata v současnosti nedokáže nahrávat soubory na vnější servery HTTP. Z tohoto důvodu by se nastavení výše měla ponechat zakázána. Finished! Dokončeno! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata je nyní nastavena!<br/><br/>Dialog pro nastavení Cantaty může být použit k přizpůsobení vzhledu Cantaty, a rovněž pro přidání dalších serverů MPD, a tak dále. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantata skladby seskupí do alb pomocí značky AlbumUmělec, pokud je to tak nastaveno, jinak použije značku Umělec. Pokud máte alba s více umělci, <b>musíte</b> nastavit značku AlbumUmělec, aby při seskupování pracovala správně. Při této možnosti se doporučuje použít Různí umělci. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Varování:</b> Nyní nejste členem skupiny uživatelé (users). Cantata bude pracovat lépe (ukládání obalů alb, textů písní atd. se správnými oprávněními), pokud vy (nebo váš správce systému) přidáte sebe sama do této skupiny. Pokud přidáte sebe sama, budete se muset odhlásit a zase přihlásit, aby se tato změna projevila. Not Connected Nepřipojeno Connection Established Spojení navázáno Connection Failed Spojení se nezdařilo Cantata will now terminate Cantata nyní skončí InputDialog Password Heslo Please enter password: Zadejte, prosím, heslo: InterfaceSettings Sidebar Postranní panel Views Pohledy Use the checkboxes below to configure which views will appear in the sidebar. Použijte zaškrtávací okénka níže k nastavení pohledů, které se objeví v postranním panelu. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Pokud není řada skladeb k přehrání výše zaškrtnuta, potom se objeví po straně ostatních pohledů. Pokud nejsou výše zaškrtnuty informace, pak bude do nástrojového pruhu přidáno tlačítko umožňující přístup k informacím o písni. Options Volby Style: Styl: Position: Poloha: Only show icons, no text Ukázat jen ikony, žádný text Auto-hide Automatické skrývání Play Queue Řada Initially collapse albums Na začátku alba složit Automatically expand current album Automaticky rozbalit nynější album Scroll to current track Projíždět k nynější skladbě Prompt before clearing Vyzvat před vyprázdněním Separate action (and shortcut) for play queue search Samostatná činnost (a klávesová zkratka) pro hledání v řadě skladeb určených k přehrání Background Image Obrázek pozadí None Žádné Current album cover Obal nynějšího alba Custom image: Vlastní obrázek: Blur: Rozmazání: 10px 10 px Opacity: Neprůhlednost: 40% 40 % Toolbar Nástrojový pruh Show stop button Ukázat tlačítko pro zastavení Show cover of current track Ukázat obal nynější skladby Show track rating Ukázat hodnocení skladby External Vnější Enable MPRIS D-BUS interface Povolit rozhraní MPRIS D-BUS Show popup messages when changing tracks Ukázat vyskakovací zprávy při změnách skladeb Show icon in notification area Ukázat ikonu v oznamovací oblasti Minimize to notification area when closed Při zavření zmenšit do oznamovací oblasti On Start-up Při spuštění Show main window Ukázat hlavní okno Hide main window Skrýt hlavní okno Restore previous state Obnovit předchozí stav Tweaks Doladění Artist && Album Sorting Třídění umělců a alb Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Zadejte (čárkou oddělený) seznam předpon, které se budou při třídění umělců a alb přehlížet, např. pokud nastaveno na The, potom se The Beatles budou třídit podle Beatles Enter comma separated list of prefixes... Zadejte čárkou oddělený seznam předpon... Composer Support Podpora pro skladatele By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Ve výchozím nastavení Cantata používá značku Umělec alba (nebo značku Umělec, pokud píseň nemá žádného umělce alba) k seskupení písní a alb. U některých žánrů, např. Klasický, může být lepší použít značku Skladatel (pokud nastaven) k provedení tohoto seskupení. Zadejte, prosím, (čárkou oddělený) seznam žánrů, u nichž chcete, aby Cantata použila značku Skladatel. Enter comma separated list of genres... Zadejte čárkou oddělený seznam žánrů... Single Tracks Jednotlivé skladby If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' pokud máte ve své sbírce mnoho umělců, kteří mají jen jednu skladbu, potom může být těžkopádné mít pro každého z nich jejich vlastní záznam v seznamu umělců. Můžete to obejít tím, že tyto skladby umístíte do samostatné složky a níže zadáte název této složky. Potom bude Cantata tyto seskupovat pod albem nazvaným Jednotlivé skladby s umělcem alba Různí umělci Folder that contains single track files... Složka, která obsahuje soubory s jednotlivými skladbami... CUE Files Soubory CUE A cue file is a metadata file which describes how the tracks of a CD are laid out. Soubor CUE je soubor s popisnými údaji, který popisuje, jak jsou skladby na CD rozloženy. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. Změna čehokoli výše bude vyžadovat obnovení databáze (a možná opětovné spuštění Cantaty), aby se tato změna projevila. General Obecné Fetch missing covers from Last.fm Natáhnout chybějící obaly z Last.fm Show delete action in context menus Ukázat položku Smazat v nabídkách souvisejících činností Enforce single-click activation of items Vynutit zapnutí položek jedním klepnutím Changing the style setting will require a re-start of Cantata. Změna nastavení stylu vyžaduje opětovné spuštění Cantaty. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>Toto změní rozhraní Cantaty, jak je to popsáno dále: <ul><li>Tlačítka pro přehrávání a ovládání budou o 33 % širší</li><li>Pohledy budou odstrčitelné.</li><li>Pro táhnutí položek budete potřebovat najet do horního levého rohu</li><li>Posuvníky budou široké jen několik pixelů</li><li>Činnosti (např. Přidat do řady skladeb k přehrání) budou vždy viditelné (ne jen tehdy, když je položka pod myší)</li><li>Otočná tlačítka budou mít tlačítka + a - na straně textového pole.</li></ul></p> Make interface more touch friendly Udělat rozhraní dotekově přátelštější Show song information tooltips Ukázat nástrojové rady s informacemi k písni Support retina displays Podpora pro obrazovky retina (sítnice) Language: Jazyk: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Změna nastavení Vynutit zapnutí jedním klepnutím u položek vyžaduje opětovné spuštění Cantaty. Changing the language setting will require a re-start of Cantata. Změna nastavení jazyka vyžaduje opětovné spuštění Cantaty. Changing the 'touch friendly' setting will require a re-start of Cantata. Změna nastavení rozhraní na dotykově přátelské vyžaduje opětovné spuštění Cantaty. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. Povolení podpory pro obrazovky retina (retina = sítnice; obrazovky s vyšší hustotou pixelů, než jakou měly předchozí modely, firma Apple) povede k ostřejším ikonám na obrazovkách typu "retina", ale může vést k méně ostrým ikonám na obrazovkách, které typu "retina" nejsou. Změna tohoto nastavení vyžaduje opětovné spuštění Cantaty. Library Knihovna Folders Složky Playlists Seznamy Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet - proudy, Jamendo, Maganatune, SoundCloud, a zvukové záznamy Devices - UMS, MTP (e.g. Android), and AudioCDs Zařízení - UMS, MTP (např. Android), a zvuková CD Search (via MPD) Hledání (přes MPD) Info - Current song information (artist, album, and lyrics) Informace - informace o nynější písni (umělec, album a slova písně) Large Velký Small Malý Tab-bar Pruh s kartami Left Vlevo Right Vpravo Top Nahoře Bottom Dole Images (*.png *.jpg) Obrázky (*.png *.jpg) 10px pixels 10 px Notifications Oznámení English (en) Angličtina (en) System default Výchozí nastavení systému %1% value% %1% %1 px pixels %1 px ItemView Go Back Jít zpět Updating... Obnovuje se... JamendoService The world's largest digital service for free music Největší světová digitální služba pro volnou hudbu JamendoSettingsDialog Jamendo Settings Nastavení pro Jamendo MP3 MP3 Ogg Ogg Streaming format: Formát přenosu: KeySequenceButton The key you just pressed is not supported by Qt. Klávesa, kterou jste nyní stiskl není podporována Qt. Unsupported Key Nepodporovaná klávesa KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Klepněte na tlačítko, potom zadejte zkratku, jako byste ji použil v programu. Příklad pro Ctrl+a: podržte klávesu Ctrl a stiskněte a. Meta Meta key Meta Ctrl Ctrl key Ctrl Alt Alt key Alt Shift Shift key Shift Input What the user inputs now will be taken as the new shortcut Vstup None No shortcut defined Žádné Shortcut Conflict Střet zkratek The "%1" shortcut is already in use, and cannot be configured. Please choose another one. Klávesová zkratka "%1" se již používá, a nelze ji nastavit. Vyberte, prosím, jinou. The "%1" shortcut is ambiguous with the shortcut for the following action: Klávesová zkratka "%1" je stejná jako zkratka pro následující činnost: Do you want to reassign this shortcut to the selected action? Chcete tuto zkratku přiřadit znovu k vybrané činnosti? Reassign Přiřadit znovu LastFmEngine Read more on last.fm Číst více na Last.fm LibraryDb Database error - please check Qt SQLite driver is installed Chyba v databázi - prověřte, prosím, zda je nainstalován ovladač SQLite Qt LibraryPage Show Artist Images Ukázat obrázky umělců Sort Albums Třídit alba Name Název Year Rok Album, Artist, Year Album, umělec, rok Album, Year, Artist Album, rok, umělec Artist, Album, Year Umělec, album, rok Artist, Year, Album Umělec, rok, album Year, Album, Artist Rok, album, umělec Year, Artist, Album Rok, umělec, album Modified Date Datum změny Group By Seskupit podle Genre Žánr Artist Umělec Album Album Are you sure you wish to delete the selected songs? This cannot be undone. Opravdu chcete odstranit vybrané písně? Tento krok nelze vrátit zpět. Delete Songs Smazat písně LyricSettings Choose the websites you want to use when searching for lyrics. Vyberte stránky, které chcete použít při hledání textů písní. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Pokud se Cantatě nepodařilo najít slova písně, nebo našla špatná, použijte tento dialog pro zadání nových podrobností hledání. Například může být nynější píseň ve skutečnosti přezpívaná verze. Je-li tomu tak, může pomoci hledání slov písně podle původního umělce. Pokud toto vyhledávání nenalezne nová slova, tato pořád budou spojena s původním názvem písně a umělcem, jak je zobrazen v Cantatě. Title: Název: Artist: Umělec: Search For Lyrics Hledat slova písně MPDConnection Unknown Neznámý Connection to %1 failed Nepodařilo se připojit k %1 Connection to %1 failed - please check your proxy settings Nepodařilo se připojit k %1 - Prověřte, prosím, nastavení vaší proxy Connection to %1 failed - incorrect password Nepodařilo se připojit k %1 - nesprávné heslo Connecting to %1 Připojuje se k %1 Failed to send command to %1 - not connected Nepodařilo se poslat příkaz %1 - nepřipojeno Failed to load. Please check user "mpd" has read permission. Nepodařilo se nahrát. Ověřte, prosím, zda má mpd oprávnění ke čtení. Failed to load. MPD can only play local files if connected via a local socket. Nepodařilo se nahrát. MPD může přehrát jen místní soubory, pokud je připojen přes místní zásuvku. MPD reported the following error: %1 MPD nahlásilo následující chybu: %1 Failed to send command. Disconnected from %1 Nepodařilo se poslat příkaz. Odpojeno od %1 Failed to rename <b>%1</b> to <b>%2</b> Nepodařilo se přejmenovat <b>%1</b> na <b>%2</b> Failed to save <b>%1</b> Nepodařilo se uložit <b>%1</b> You cannot add parts of a cue sheet to a playlist! Nelze přidat části seznamu v souboru CUE do seznamu skladeb! You cannot add a playlist to another playlist! Nelze přidat seznam skladeb do jiného seznamu skladeb! Failed to send '%1' to %2. Please check %2 is registered with MPD. Nepodařilo se poslat '%1' %2. Ověřte, prosím, že %2 je zaregistrováno u MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. Nelze uložit hodnocení, jelikož příkaz lepiče MPD není podporován. MagnatuneService None Žádné Streaming Přenos MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com Internetová hudba z magnatune.com MagnatuneSettingsDialog Magnatune Settings Nastavení pro Magnatune Username: Uživatelské jméno: Password: Heslo: Membership: Členství: Downloads: Stahování: MainWindow [Dynamic] [Dynamický] Exit Full Screen Opustit režim zobrazení na celou obrazovku Configure Cantata... Nastavit Cantatu... Preferences Nastavení Quit Ukončit About Cantata... O programu Cantata... Show Window Ukázat okno Server information... Informace o serveru... Refresh Database Obnovit databázi Refresh Obnovit Connect Spojit Collection Sbírka Outputs Výstupy Stop After Track Zastavit po skladbě Seek forward (%1 seconds) Přetočit vpřed (%1 sekund) Seek backward (%1 seconds) Přetočit zpět (%1 sekund) Add To Stored Playlist Přidat do uloženého seznamu skladeb Crop Others Vystřihnout jiné Add Stream URL Přidat adresu proudu Clear Vyprázdnit Center On Current Track Zaměřit na nynější skladbu Expanded Interface Rozšířené rozhraní Show Current Song Information Ukázat informace o nynější skladbě Full Screen Na celou obrazovku Random Náhodné Repeat Opakování Single Jednotlivé When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Když jsou zapnuty jednotlivé skladby, je přehrávání zastaveno po nynější písni, nebo je píseň opakována, pokud je povolen režim opakování. Consume Sníst When consume is activated, a song is removed from the play queue after it has been played. Když je zapnuto snězení, píseň je odstraněna z řady skladeb k přehrání, poté co byla přehrána. Find in Play Queue Hledat v řadě skladeb k přehrání Play Stream Přehrát proud Locate In Library Najít v knihovně Play next Přehrát další Edit Track Information (Play Queue) Upravit informace o skladbě (řada skladeb k přehrání) Expand All Rozbalit vše Collapse All Složit vše Cancel Zrušit Play Queue Řada Library Knihovna Folders Složky Playlists Seznamy Internet Internet Devices Zařízení Search Hledat Info Informace Show Menubar Ukázat pruh s nabídkou &Music &Hudba &Edit Úp&ravy &View &Pohled &Queue Řa&da &Settings &Nastavení &Help Nápo&věda Set Rating Nastavit hodnocení No Rating Žádné hodnocení Failed to locate any songs matching the dynamic playlist rules. Nepodařilo se najít žádné písně odpovídající pravidlům dynamického seznamu skladeb. Connecting to %1 Připojuje se k %1 Refresh MPD Database? Obnovit databázi MPD? About Cantata O programu Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>Klient pro MPD.<br/><br/>© Craig Drummond 2011-2016.<br/>Vydáno pod <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Založeno na <a href="http://lowblog.nl">QtMPC</a> - © 2007-2010 Autoři QtMPC<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Pozadí pohledu na souvislosti díky laskavosti <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Popisná data pohledu na souvislosti díky laskavosti <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Zvažte, prosím, nahrání vašeho vlastního fanouškovského umění na <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Zvukový záznam se nyní stahují. Pokud bude program ukončen nyní, bude stahování zrušeno. Abort download and quit Zrušit stahování a ukončit Please close other dialogs first. Nejprve, prosím, zavřete další dialogy. Enabled: %1 Povoleno: %1 Disabled: %1 Zakázáno: %1 Server Information Informace o serveru <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protokol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Doba provozu:&nbsp;</td><td>%4</td></tr><tr><td align="right">Přehrává se:&nbsp;</td><td>%5</td></tr><tr><td align="right">Ovladače:&nbsp;</td><td>%6</td></tr><tr><td align="right">Značky:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Databáze</b></td></tr><tr><td align="right">Umělci:&nbsp;</td><td>%1</td></tr><tr><td align="right">Alba:&nbsp;</td><td>%2</td></tr><tr><td align="right">Písně:&nbsp;</td><td>%3</td></tr><tr><td align="right">Doba trvání:&nbsp;</td><td>%4</td></tr><tr><td align="right">Obnoveno:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD nahlásilo následující chybu: %1 Cantata Cantata Playback stopped Přehrávání zastaveno Remove all songs from play queue? Odstranit všechny písně z řady skladeb k přehrání? Priority Přednost Enter priority (0..255): Zadejte přednost (0 ... 255): Decrease priority for each subsequent track Zmenšit přednost každé následné skladby Playlist Name Název seznamu skladeb Enter a name for the playlist: Zadejte název pro seznam skladeb: '%1' is used to store favorite streams, please choose another name. '%1' se používá na ukládání oblíbených proudů. Vyberte, prosím, jiný název. A playlist named '%1' already exists! Add to that playlist? Seznam skladeb pojmenovaný '%1' již existuje! Přidat do tohoto seznamu skladeb? Existing Playlist Existující seznam skladeb %n Track(s) Skladby: %n Skladby: %n Skladby: %n %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) MenuButton Menu Nabídka MessageOverlay Cancel Zrušit Mpris (Stream) (Proud) MtpConnection Connecting to device... Připojuje se k zařízení... No devices found Nenalezeno žádné zařízení Connected to device Připojeno k zařízení Disconnected from device Odpojeno od zařízení Updating folders... Obnovují se složky... Updating files... Obnovují se soubory... Updating tracks... Obnovují se skladby... MtpDevice Not Connected Nepřipojeno %1 free %1 volno MusicBrainz Failed to open CD device Nepodařilo se otevřít zařízení CD Track %1 Stopa %1 %1 (Disc %2) %1 (Disk %2) No matches found in MusicBrainz V MusicBrainz nenalezeny žádné shody MusicLibraryModel Cue Sheet List CUE Playlist Seznam skladeb %n Track(s) Skladby: %n Skladby: %n Skladby: %n %n Artist(s) Umělci: %n Umělci: %n Umělci: %n %n Album(s) Alba: %n Alba: %n Alba: %n %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) %1 by %2 Album by Artist %1 od %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>POZNÁMKA:</b> %1</i> NowPlayingWidget (Stream) (Proud) OSXStyle &Window &Okno Close Zavřít Minimize Zmenšit Zoom Zvětšení OnlineDbService Downloading...%1% Stahuje se... %1% Parsing music list.... Zpracovává se seznam s hudbou... Failed to download Nepodařilo se stáhnout %n Artist(s) Umělci: %n Umělci: %n Umělci: %n OnlineDbWidget Group By Seskupit podle Genre Žánr Artist Umělec Configure Nastavit The music listing needs to be downloaded, this can consume over %1Mb of disk space Je třeba stáhnout seznamu hudby. To může spotřebovat přes %1 MB místa na disku Dowload music listing? Stáhnout seznam hudby? Download Stáhnout Re-download music listing? Stáhnout seznam hudby znovu? OnlineSearchService Searching... Hledá se... OnlineSearchWidget No tracks found. Nenalezeny žádné skladby. %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Použijte zaškrtávací okénka níže k nastavení seznamu činných služeb. Configure Service Nastavit službu OnlineView Song Information Informace o písni OnlineXmlParser Failed to parse Nepodařilo se zpracovat OpmlBrowsePage Reload Nahrát znovu Failed to download directory listing Nepodařilo se stáhnout soupis adresáře Failed to parse directory listing Nepodařilo se zpracovat soupis adresáře OtherSettings Background Image Obrázek pozadí None Žádné Artist image Obrázek umělce Custom image: Vlastní obrázek: Blur: Rozmazání: 10px 10 px Opacity: Neprůhlednost: 40% 40 % Automatically switch to view after: Automaticky přepnout do pohledu po: Do not auto-switch Nepřepínat automaticky ms ms Dark background Tmavé pozadí Darken background, and use white text, regardless of current color palette. Ztmavit pozadí a použít bílý text, bez ohledu na nynější barevnou paletu. Always collapse into a single pane Vždy složit do jednoho pole Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Ukázat jen umělce, album nebo skladbu, i když je dost místa na ukázání všech tří. Only show basic wikipedia text Ukázat jen základní text na Wikipedii Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantata ukáže pouze zkrácenou verzi stránek na Wikipedii (žádné obrázky, odkazy atd.). Toto ořezání obsahu není vždy stoprocentně přesné. To je důvodem toho, proč Cantata ve výchozím nastavení ukazuje pouze úvod. Pokud si zvolíte zobrazení celého článku, mohou se vyskytnout chyby ve zpracování. Také budete muset odstranit všechny články, které jsou nyní uloženy ve vyrovnávací paměti (pomocí stránky Vyrovnávací paměť). Images (*.png *.jpg) Obrázky (*.png *.jpg) 10px pixels 10 px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Vybrat složku Select File Vybrat soubor PlayQueueModel Title Název Artist Umělec Album Album # Track number Č. Length Délka Disc Disk Year Rok Original Year Původní rok Genre Žánr Priority Přednost Composer Skladatel Performer Účinkující Rating Hodnocení Remove Duplicates Odstranit zdvojené Undo Zpět Redo Znovu Shuffle Zamíchat Tracks Skladby Albums Alba Sort By Třídit podle Album Artist Umělec alba Track Title Název skladby Track Number Číslo skladby # (Track Number) Číslo skladby PlayQueueView Remove Odstranit PlaybackSettings Playback Přehrávání Fa&deout on stop: Postupné &zeslabení zvuku při zastavení: None Žádné ms ms Stop playback on exit Zastavit přehrávání při ukončení Inhibit suspend whilst playing Bránit uspání při přehrávání If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Pokud stisknete a podržíte tlačítko pro zastavení, ukáže se nabídka, v níž si budete moci zvolit, zda se má přehrávání zastavit nyní, nebo po nynější skladbě. (Tlačítko pro zastavení lze povolit v části nastavení Rozhraní/Nástrojový pruh) Output Výstup <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Nepřipojeno.<br/>Záznamy níže nelze změnit, protože Cantata není připojena k MPD.</i> &Crossfade between tracks: &Prolínání mezi skladbami: s s Replay &gain: Vyrovnání &hlasitosti: About replay gain O vyrovnání hlasitosti Use the checkboxes below to control the active outputs. Použijte zaškrtávací okénka níže k ovládání činných výstupů. Track Skladba Album Album Auto Automaticky <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Připojeno k %1.<br/>Záznamy níže použít na nyní připojenou sbírku MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Vyrovnání hlasitosti (Replay Gain) je navržený standard zveřejněný v roce 2001 k normalizaci vnímané hlasitosti počítačových zvukových formátů, jako jsou MP3 a Ogg Vorbis. Pracuje na základě skladba/album, a je nyní podporován rostoucím počtem přehrávačů.<br/><br/>Je možné použít následující nastavení vyrovnání hlasitosti:<ul><li><i>Žádné</i> - Není použito žádné vyrovnání hlasitosti.</li><li><i>Skladba</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u skladby.</li><li><i>Album</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u alba.</li><li><i>Automaticky</i> - Hlasitost je upravena za použití značek pro vyrovnání hlasitosti u skladby, v případě že je zapnuto náhodné přehrávání, jinak se použijí značky u alba.</li></ul> PlaylistRule Type: Typ: Include songs that match the following: Zahrnout písně odpovídající následujícímu: Exclude songs that match the following: Vyloučit písně odpovídající následujícímu: Artist: Umělec: Artists similar to: Umělci podobní: Album Artist: Umělec alba: Composer: Skladatel: Album: Album: Title: Název: Genre Žánr From Year: Od roku: Any Jakýkoli To Year: Do roku: Comment: Poznámka: Filename / path: Název souboru/cesta: Exact match Přesná shoda Only enter values for the tags you wish to be search on. Zadejte hodnoty pouze pro značky, které si přejete hledat. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Když má žánr odpovídat různým žánrům, ukončete řetězec hvězdičkou. Např. 'rock*' odpovídá 'Hard Rock' a 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Dynamické pravidlo Smart Rule Chytré pravidlo Add Přidat <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>CHYBA</b>: 'Od roku' má být menší než 'Do roku'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>CHYBA</b>: Rozsah data je příliš velký (může být nanejvýš jen %1 roků)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>CHYBA:</b> Můžete porovnávat pouze název souboru/cesta, pokud <b>není</b> zaškrtnuto</i> Přesná shoda PlaylistRules Name of Dynamic Rules Název pro dynamická pravidla Add Přidat Edit Upravit Remove Odstranit Songs with ratings between: Písně s hodnocením mezi: - - Songs with duration between: Písně s dobou trvání mezi: seconds sekund Number of songs in play queue: Počet písní v řadě skladeb k přehrání: Order songs: Řadit písně: About Rules O pravidlech PlaylistRulesDialog Dynamic Rules Dynamická pravidla None Žádné No Limit Bez omezení About dynamic rules O dynamických pravidlech Smart Rules Chytrá pravidla Ascending Vzestupně Descending Sestupně Name of Smart Rules Název chytrých pravidel Number of songs Počet písní <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata se ve vaší knihovně poptá s využitím všech uvedených pravidel. Seznam pravidel <i>zahrnout</i> se použije k sestavení množiny písní, jež lze použít. Seznam pravidel <i>vyloučit</i> se použije k sestavení množiny písní, jež použít nelze. Pokud nejsou žádná pravidla typu <i>zahrnout</i>, Cantata bude předpokládat, že lze použít všechny písně (kromě těch s typem <i>vyloučit</i>).</p><p>Např. aby Cantata hledala "Rockové písně od Wibble NEBO písně různých umělců", je potřeba následující: <ul><li>Zahrnout Umělec alba=Wibble Žánr=Rock</li><li>Zahrnout Umělec alba=Různí umělci</li></ul> Aby Cantata hledala "Písně od Wibble ale ne ty na albu Abc", je potřeba následující: <ul><li>Zahrnout Umělec alba=Wibble</li><li>Vyloučit Umělec alba=Wibble Album=Abc</li></ul>Po vytvoření skupiny použitelných písní Cantata náhodně vybere písně, aby udržovala řadu skladeb k přehrání naplněnu určeným počtem položek (výchozí počet je 10). Pokud bylo stanoveno rozmezí hodnocení, potom budou použity jen písně s hodnocením v tomto rozmezí. Stejně tak, pokud byla stanovena doba trvání.</p> About smart rules O chytrých pravidlech <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata se ve vaší knihovně poptá s využitím všech uvedených pravidel. Seznam pravidel <i>zahrnout</i> se použije k sestavení množiny písní, jež lze použít. Seznam pravidel <i>vyloučit</i> se použije k sestavení množiny písní, jež použít nelze. Pokud nejsou žádná pravidla typu <i>zahrnout</i>, Cantata bude předpokládat, že lze použít všechny písně (kromě těch s typem <i>vyloučit</i>).</p><p>Např. aby Cantata hledala "Rockové písně od Wibble NEBO písně různých umělců", je potřeba následující: <ul><li>Zahrnout Umělec alba=Wibble Žánr=Rock</li><li>Zahrnout Umělec alba=Různí umělci</li></ul> Aby Cantata hledala "Písně od Wibble ale ne ty na albu Abc", je potřeba následující: <ul><li>Zahrnout Umělec alba=Wibble</li><li>Vyloučit Umělec alba=Wibble Album=Abc</li></ul>Po vytvoření skupiny použitelných písní Cantata přidá požadovaný počet písní do řady skladeb k přehrání. Pokud bylo stanoveno rozmezí hodnocení, potom budou použity jen písně s hodnocením v tomto rozmezí. Stejně tak, pokud byla stanovena doba trvání.</p> Failed to save %1 Nepodařilo se uložit %1 A set of rules named '%1' already exists! Overwrite? Seznam pravidel pojmenovaný '%1' již existuje! Přepsat? Overwrite Rules Přepsat pravidla Saving %1 Ukládá se %1 PlaylistsModel New Playlist... Nový seznam skladeb... Stored Playlists Uložené seznamy skladeb Standard playlists Obvyklé seznamy skladeb %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) Smart Playlist Chytrý seznam skladeb Plurals %n Track(s) Skladby: %n Skladby: %n Skladby: %n %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) %n Album(s) Alba: %n Alba: %n Alba: %n %n Artist(s) Umělci: %n Umělci: %n Umělci: %n %n Stream(s) Proudy: %n Proudy: %n Proudy: %n %n Entry(s) Položky: %n Položky: %n Položky: %n %n Rule(s) Pravidla: %n Pravidla: %n Pravidla: %n %n Podcast(s) Záznamy: %n Záznamy: %n Záznamy: %n %n Episode(s) Díly: %n Díly: %n Díly: %n %n Update(s) available Jedna aktualizace dostupná %n aktualizací dostupných %n aktualizací dostupných PodcastPage RSS: RSS: Website: Stránky: Podcast details Podrobnosti záznamu Select a podcast to display its details Vybrat záznam pro zobrazení jeho podrobností PodcastSearchDialog Subscribe Odebírat Enter URL Zadat adresu (URL) Manual podcast URL Ruční adresa zvukového záznamu (URL) Search %1 Hledat %1 Search for podcasts on %1 Hledat zvukové záznamy na %1 Add Podcast Subscription Přidat odběr zvukového záznamu Browse %1 Procházet %1 Browse %1 podcasts Procházet %1 zvukových záznamů You are already subscribed to this podcast! Již jste přihlášen k odběru tohoto záznamu! Subscription added Odběr přidán PodcastSearchPage Enter search term... Zadat hledaný pojem... Search Hledat Failed to fetch podcasts from %1 Nepodařilo se natáhnout zvukové záznamy z %1 There was a problem parsing the response from %1 Nastaly potíže se zpracováním odpovědi z %1 PodcastService Subscribe to RSS feeds Odebírat kanál RSS %n Podcast(s) Záznamy: %n Záznamy: %n Záznamy: %n %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) Díly: %n Díly: %n Díly: %n (Downloading: %1%) (Stahuje se: %1%) Failed to parse %1 Nepodařilo se zpracovat %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata podporuje pouze záznamy zvuku! %1 obsahuje jen záznamy obrazu. Failed to download %1 Nepodařilo se stáhnout %1 PodcastSettingsDialog Check for new episodes: Podívat se po nových dílech: Download episodes to: Stáhnout díly do: Download automatically: Stáhnout automaticky: Podcast Settings Nastavení záznamu Manually Ručně Every 15 minutes Každých 15 minut Every 30 minutes Každých 30 minut Every hour Každou hodinu Every 2 hours Každé 2 hodiny Every 6 hours Každých 6 hodin Every 12 hours Každých 12 hodin Every day Každý den Every week Každý týden Don't automatically download episodes Nestahovat díly automaticky Latest episode Poslední díl Latest %1 episodes Poslední %1 díly All episodes Všechny díly PodcastUrlPage URL Adresa (URL) Enter podcast URL... Zadejte adresu zvukového záznamu (URL)... Load Nahrát Enter podcast URL below, and press 'Load' Zadejte adresu zvukového záznamu (URL) níže a stiskněte Nahrát Invalid URL! Neplatná adresa (URL)! Failed to fetch podcast! Nepodařilo se natáhnout zvukový záznam! Failed to parse podcast. Nepodařilo se zpracovat zvukový záznam. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata podporuje pouze záznamy zvuku! Zadaná adresa (URL) obsahuje jen záznamy obrazu. PodcastWidget Add Subscription Přidat odběr Remove Subscription Odstranit odběr Download Episodes Stáhnout díly Delete Downloaded Episodes Smazat stažené díly Cancel Download Zrušit stahování Mark Episodes As New Označit díly jako nové Mark Episodes As Listened Označit díly jako poslechnuté Show Unplayed Only Ukázat jen nepřehrané Unsubscribe from '%1'? Odhlásit odběr z '%1'? Do you wish to download the selected podcast episodes? Chcete stáhnout vybrané díly zvukového záznamu? Cancel podcast episode downloads (both current and any that are queued)? Zrušit stahování dílů zvukového záznamu (nynější a všechny zařazené)? Do you wish to the delete downloaded files of the selected podcast episodes? Chcete smazat stažené soubory vybraných dílů zvukového záznamu? Do you wish to mark the selected podcast episodes as new? Chcete označit vybrané díly zvukového záznamu jako nové? Do you wish to mark the selected podcast episodes as listened? Chcete označit vybrané díly zvukového záznamu jako poslechnuté? Refresh all subscriptions? Obnovit všechny odběry? Refresh Obnovit Refresh All Obnovit vše Refresh all subscriptions, or only those selected? Obnovit všechny odběry nebo jen ty vybrané? Refresh Selected Obnovit vybrané PowerManagement Cantata is playing a track Cantata přehrává skladbu PreferencesDialog Collection Sbírka Collection Settings Nastavení sbírky Playback Přehrávání Playback Settings Nastavení přehrávání Downloaded Files Stažené soubory Downloaded Files Settings Nastavení pro stažené soubory Interface Rozhraní Interface Settings Nastavení rozhraní Info Informace Info View Settings Nastavení pohledu na informace Scrobbling Odesílání informací o skladbách Scrobbling Settings Nastavení odesílání informací o skladbách Audio CD Audio CD Audio CD Settings Nastavení zvukového CD Proxy Proxy Proxy Settings Nastavení proxy Shortcuts Zkratky Keyboard Shortcut Settings Nastavení klávesových zkratek Cache Vyrovnávací paměť Cached Items Položky ve vyrovnávací paměti Custom Actions Vlastní činnosti Cantata Preferences Nastavení Cantaty Configure Nastavit ProxySettings Mode: Režim: Type: Typ: HTTP Proxy Proxy HTTP SOCKS Proxy Proxy SOCKS Host: Server: Port: Přípojka: Username: Uživatelské jméno: Password: Heslo: No proxy Žádná proxy Use the system proxy settings Použít systémové nastavení proxy Manual proxy configuration Ruční nastavení proxy QObject Track listing Seznam skladeb Read more on wikipedia Číst víc na Wikipedii Open in browser Otevřít v prohlížeči <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) je patentovaný ztrátový kodek pro digitální zvuk.<br>AAC všeobecně dosahuje při podobných datových tocích lepší jakosti zvuku než MP3. Je rozumnou volbou pro iPod a některé další přenosné přehrávače hudby. Provedení není zdarma. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>AAC</b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem než ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok bitrate po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem <a href=http://www.ffmpeg.org/faq.html#SEC21>průměrného datového toku</a> kódované skladby.<br><b>150 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>120 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>200 kb/s</b> je pravděpodobně až přespříliš. Expected average bitrate for variable bitrate encoding Očekávaný datový tok pro kódování proměnlivého datového toku Smaller file Menší soubor Better sound quality Lepší kvalita zvuku <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) je patentem chráněný digitální zvukový kodek, který používá druh ztrátového zhuštění dat.<br>Navzdory svým slabinám je to běžný formát pro spotřebitelské ukládání zvuku a je široce podporován v přenosných přehrávačích hudby. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>MP3</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/MP3#VBR>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem <a href=http://wwwffmpeg.org/faq.html#>průměrného datového toku</a> kódované skladby.<br><b>160 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>120 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>205 kb/s</b> je pravděpodobně až přespříliš. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> je otevřený kodek bez poplatků za užívání pro ztrátové zhuštění zvuku.<br>Vyrábí menší soubory než MP3 při stejné nebo vyšší kvalitě. Ogg Vorbis je všestranně vynikající, skvělou, výbornou, znamenitou a prvotřídní volbou pro přenosné přehrávače hudby, které jej podporují. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>Vorbis</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor, než jaký dává stálý datový tok po celou dobu skladby.<br>Kodér Vorbis používá při hodnocení jakosti parametr "-q", což je hodnota mezi -1 a 10, aby stanovil určitou očekávanou úroveň kvality zvuku. Měřítko datového toku v tomto posuvníku je jen hrubým odhadem (obstaraným Vorbisem) průměrného datového toku kódované skladby daný hodnotou q. Vlastně je s novějšími a účinnějšími verzemi kodéru Vorbis skutečný datový tok dokonce nižší.<br><b>-q5</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>-q3</b> může být hudebně neuspokojivé a cokoli nad <b>-q8</b> je pravděpodobně až přespříliš. Quality rating Hodnocení jakosti Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> digitální audio kodek nezatížený patenty používající ztrátovou kompresi dat. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>Kodér <b>Opus</b></b> používaný Cantatou podporuje nastavení<a href=http://en.wikipedia.org/wiki/Variable_bitrate>proměnlivého datového toku (VBR)</a>, což znamená, že hodnota datového toku kolísá podle skladby vycházejíc ze složitosti zvukového obsahu. Složitější úseky dat jsou kódovány s větším datovým tokem, než jsou kódovány ty méně složité; tento přístup přináší úhrnně lepší jakost a menší soubor než stálý datový tok po celou dobu skladby.<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku jen odhadem průměrného datového toku kódované skladby.<br><b>128 kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>100 kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>256 kb/s</b> je pravděpodobně až přespříliš. Bitrate Datový tok Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) je zvukový kodek pro bezztrátové zhuštění digitální hudby.<br>Doporučováno pouze pro hudební přehrávače od firmy Apple a přehrávače nepodporující FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) je otevřený kodek bez poplatků za užívání pro bezztrátové zhuštění digitální hudby.<br>Pokud si svou hudbu přejete ukládat bez ústupků, co se týče jakosti zvuku, FLAC je prostě excelentní, tedy vynikající, skvělou, výbornou, znamenitou a prvotřídní volbou. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Úroveň zhuštění</a> je hodnota celého čísla ležící mezi 0 a 8, která představuje vyvážení mezi velikostí souboru a rychlostí zhuštění během kódování s <b>FLAC</b>.<br/> Nastavení úrovně zhuštění na <b>0</b> dává nejkratší čas zhuštění, ale způsobuje srovnatelně velký soubor<br/>Na druhou stranu úroveň zhuštění <b>8</b> dělá zhušťování docela pomalým,ale vytvoří nejmenší soubor.<br/>Uvědomte si, že vzhledem k tomu, že FLAC je ze své podstaty bezeztrátový kodek, je zvuková jakost výstupu přesně tatáž bez ohledu na úroveň zhuštění.<br/>Úrovně nad <b>5</b> kromě toho napínavě zvyšují čas zhuštění, ale vytvářejí jen nepatrně menší soubor, a nedoporučují se. Compression level Úroveň zhuštění Faster compression Rychlejší zhuštění Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) je kodek, který je patentově chráněn, vyvíjený firmou Microsoft pro ztrátové zhuštění zvuku.<br>Doporučován jen pro přenosné přehrávače hudby, jež nepodporují formát Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Datový tok je měřítkem pro množství dat použitých na vyjádření sekundy zvuku skladby.<br>V důsledku omezení patentově chráněného formátu <b>WMA</b> a obtížnosti obráceného inženýrství soukromého kodéru, kodér WMA používaný Amarokem nastavuje <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>stálý datový tok (CBR).<br>Z tohoto důvodu je měřítko datového toku v tomto posuvníku slušným odhadem datového toku kódované skladby.<br><b>136kb/s</b> je dobrou volbou pro poslech hudby v přenosném přehrávači.<br/>Cokoli pod <b>112kb/s</b> může být hudebně neuspokojivé a cokoli nad <b>182kb/s</b> je pravděpodobně až přespříliš. Empty filename. Prázdný název souboru. Invalid filename. (%1) Neplatný název souboru. (%1) Failed to save %1. Nepodařilo se uložit %1. Failed to delete rules file. (%1) Nepodařilo se smazat soubor s pravidly. (%1) Invalid command. (%1) Neplatný příkaz. (%1) Could not remove active rules link. Nepodařilo se odstranit odkaz na činná pravidla. Active rules is not a link. Činná pravidla není odkaz. Could not create active rules link. Nepodařilo se vytvořit odkaz na činná pravidla. Rules file, %1, does not exist. Soubor s pravidly, %1, neexistuje. Incorrect arguments supplied. Poskytnuty nesprávné argumenty. Unknown method called. Zavolána neznámá metoda. Unknown error Neznámá chyba Artist Umělec SimilarArtists Podobní umělci AlbumArtist Umělec alba Composer Skladatel Comment Poznámka Album Album Title Název Genre Žánr Date Datum File Soubor Include Zahrnout Exclude Vyloučit (Exact) (Přesné) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Nynější obal CoverArt Archive Archiv obalů Grouped Albums Seskupená alba Table Tabulka Parse in Library view, and show in Folders view Zpracovat v pohledu na knihovnu a ukázat v pohledu na složky Only show in Folders view Ukázat jen v pohledu na složky Do not list Neuvádět %1 Tracks Plural (N!=1) Skladby: %1 1 Track (%1) Singular Skladby: 1 (%1) %1 Tracks (%2) Plural (N!=1) Skladby: %1 (%2) %1 Albums Plural (N!=1) Alba: %1 %1 Artists Plural (N!=1) Umělci: %1 %1 Streams Plural (N!=1) Proudy: %1 %1 Entries Plural (N!=1) Položky: %1 %1 Rules Plural (N!=1) Pravidla: %1 %1 Podcasts Plural (N!=1) Záznamy: %1 %1 Episodes Plural (N!=1) Díly: %1 %1 Updates available Plural (N!=1) %1 aktualizací dostupných Previous Track Předchozí skladba Next Track Další skladba Play/Pause Přehrát/Pozastavit Stop Zastavit Stop After Current Track Zastavit po současné skladbě Stop After Track Zastavit po skladbě Increase Volume Zvýšit hlasitost Decrease Volume Snížit hlasitost Save As Uložit jako Append Připojit Append To Play Queue Připojit do řady skladeb k přehrání Append And Play Připojit a přehrát Add And Play Přidat a přehrát Append To Play Queue And Play Připojit do řady skladeb k přehrání a přehrát Insert After Current Vložit po nynější Append Random Album Připojit náhodné album Play Now (And Replace Play Queue) Přehrát nyní (a nahradit řadu skladeb k přehrání) Add With Priority Přidat s předností Set Priority Nastavit přednost Highest Priority (255) Nejvyšší přednost (255) High Priority (200) Vysoká přednost (200) Medium Priority (125) Střední přednost (125) Low Priority (50) Nízká přednost (50) Default Priority (0) Výchozí přednost (0) Custom Priority... Vlastní přednost... Add To Playlist Přidat do seznamu skladeb Organize Files Uspořádat soubory Edit Track Information Upravit informace o skladbě ReplayGain Vyrovnání hlasitosti Copy Songs To Device Kopírovat písně do zařízení Delete Songs Smazat písně Set Image Nastavit obrázek Remove Odstranit Find Najít Add To Play Queue Přidat do řady skladeb k přehrání Parse error loading cache file, please check your songs tags. Chyba ve zpracování při nahrávání souboru s vyrovnávací pamětí. Prověřte, prosím, značky vaší písně. Other Jiné Default Výchozí "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Jednotlivé skladby Personal Osobní Unknown Neznámý Various Artists Různí umělci Album artist Umělec alba Performer Účinkující Track number Číslo skladby Disc number Číslo disku Year Rok Orignal Year Původní rok Length Délka <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> na <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> od <b>%2</b> na <b>%3</b> Invalid service Neplatná služba Invalid method Neplatná metoda Authentication failed Ověření se nezdařilo Invalid format Neplatný formát Invalid parameters Neplatné parametry Invalid resource specified Zadán neplatný zdroj Operation failed Operace se nezdařila Invalid session key Neplatný klíč sezení Invalid API key Neplatný klíč API Service offline Služba není dostupná Last.fm is currently busy, please try again in a few minutes Služba Last.fm je nyní zaneprázdněna. Zkuste to, prosím, znovu za několik minut Rate-limit exceeded Překročeno omezení na počet hodnocení General Obecné Digitally Imported Digitally Imported Local and National Radio (ListenLive) Místní a národní rádio (Poslouchat živě) &OK &OK &Cancel &Zrušit &Yes &Ano &No &Ne &Discard Za&hodit &Save &Uložit &Apply &Použít &Close &Zavřít &Help Nápo&věda &Overwrite &Přepsat &Reset Nastavit &znovu &Continue &Pokračovat &Delete &Smazat &Stop &Zastavit &Remove &Odstranit &Previous &Předchozí &Next &Další Close Zavřít Error Chyba Information Informace Warning Varování Question Otázka %1 B %1 B %1 kB %1 KB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Základní strom (žádné ikony) Simple Tree Jednoduchý strom Detailed Tree Podrobný strom List Seznam Grid Mřížka %n Track(s) Skladby: %n Skladby: %n Skladby: %n %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) %n Album(s) Alba: %n Alba: %n Alba: %n %n Artist(s) Umělci: %n Umělci: %n Umělci: %n %n Stream(s) Proudy: %n Proudy: %n Proudy: %n %n Entry(s) Položky: %n Položky: %n Položky: %n %n Rule(s) Pravidla: %n Pravidla: %n Pravidla: %n %n Podcast(s) Záznamy: %n Záznamy: %n Záznamy: %n %n Episode(s) Díly: %n Díly: %n Díly: %n %n Update(s) available Jedna aktualizace dostupná %n aktualizací dostupných %n aktualizací dostupných RemoteDevicePropertiesDialog Device Properties Vlastnosti zařízení Connection Spojení Music Library Hudební knihovna Add Device Přidat zařízení A remote device named '%1' already exists! Please choose a different name. Vzdálené zařízení s názvem "'%1" již existuje! Vyberte, prosím, jiný název. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Tato nastavení jsou upravitelná, jen když není zařízení připojeno. Type: Typ: Name: Název: Options Volby Host: Server: Port: Přípojka: User: Uživatel: Domain: Doména: Password: Heslo: Share: Sdílení: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Pokud zde zadáte heslo, bude uloženo <b>nezašifrované</b> v souboru s nastavením pro Cantatu. Aby Cantata vyzvala k zadání hesla před přístupem k sdílení, nastavte heslo na '-' Service name: Název služby: Folder: Složka: Extra Options: Další volby: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. Kvůli způsobu, jakým sshfs pracuje, bude k zadání hesla vyžadován vhodný program ssh-askpass (ksshaskpass, ssh-askpass-gnome, atd.). This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Tento dialog se používá jen pro přidání vzdálených zařízení (např. přes Samba), nebo pro přístup k místně připojeným složkám. Pro běžné přehrávače záznamů, připojené přes USB, Cantata zobrazí zařízení automaticky, když je připojeno. Samba Share Sdílení Samba Samba Share (Auto-discover host and port) Sdílení Samba (automatické zjištění hostitele a přípojky) Secure Shell (sshfs) Bezpečný shell (sshfs) Locally Mounted Folder Místně připojená složka RemoteFsDevice Available Dostupné Not Available Nedostupné Failed to resolve connection details for %1 Nepodařilo se vyřešit podrobnosti připojení pro %1 Connecting... Připojuje se... Password prompting does not work when cantata is started from the commandline. Výzva k zadání hesla nepracuje, když je Cantata spuštěna z příkazového řádku. No suitable ssh-askpass application installed! This is required for entering passwords. Žádný vhodný program ssh-askpass není nainstalován! Toto je vyžadováno pro zadávání hesel. Mount point ("%1") is not empty! Bod připojení ("%1") není prázdný! "sshfs" is not installed! "sshfs" není nainstalován! Disconnecting... Odpojuje se... "fusermount" is not installed! "fusermount" není nainstalován! Failed to connect to "%1" Nepodařilo se připojit k "%1" Failed to disconnect from "%1" Nepodařilo se odpojit od "%1" Updating tracks... Obnovují se skladby... Not Connected Nepřipojeno Capacity Unknown Neznámá velikost %1 free %1 volno RgDialog ReplayGain Vyrovnání hlasitosti Show All Tracks Ukázat všechny skladby Show Untagged Tracks Ukázat skladby bez značek Remove From List Odstranit ze seznamu Artist Umělec Album Album Title Název Album Gain Zesílení alba Track Gain Zesílení skladby Album Peak Vrchol alba Track Peak Vrchol skladby Scan Prohledat Update ReplayGain tags in tracks? Obnovit značky pro vyrovnání hlasitosti ve skladbách? Update Tags Obnovit značky Abort scanning of tracks? Zrušit prohledávání značek? Abort Přerušit Abort reading of existing tags? Zrušit čtení stávajících značek? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Prohledat <b>všechny</b> skladby?<br/><br/><i>Všechny skladby mají existující značky pro vyrovnání hlasitost.</i> Do you wish to scan all tracks, or only tracks without existing tags? Chcete prohledat všechny skladby, nebo pouze soubory bez existujících značek? Untagged Tracks Skladby bez značek All Tracks Všechny skladby Scanning tracks... Prohledávají se skladby... Reading existing tags... Čtou se stávající značky... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (poškozené značky?) Failed to update the tags of the following tracks: Nepodařilo se zaktualizovat značky následujících skladeb: Device has been removed! Zařízení bylo odstraněno! Device is not connected. Zařízení není připojeno. Device is busy? Zařízení je zaneprázdněno? %1 dB %1 dB Failed Nepodařilo se Original: %1 dB Původní: %1 dB Original: %1 Původní: %1 Remove the selected tracks from the list? Odstranit vybrané skladby ze seznamu? Remove Tracks Odstranit skladby RulesPlaylists - Rating: %1..%2 - Hodnocení: %1...%2 Album Artist Umělec alba Artist Umělec Album Album Composer Skladatel Date Datum Genre Žánr Rating Hodnocení File Age Stáří souboru Random Náhodné %n Rule(s) Pravidla: %n Pravidla: %n Pravidla: %n , Rating: %1..%2 , Hodnocení: %1..%2 Ascending Vzestupně Descending Sestupně Scrobbler %1 error: %2 %1 chyba: %2 ScrobblingLove %1: Loved Current Track %1: Nynější skladba byla zařazena mezi oblíbené %1: Love Current Track %1: Zařadit nynější skladbu mezi oblíbené ScrobblingSettings Scrobble using: Odesílat informace o skladbách pomocí: Username: Uživatelské jméno: Password: Heslo: Status: Stav: Login Přihlášení Scrobble tracks Odesílat informace o skladbách Show 'Love' button Ukázat tlačítko pro ukázání náklonnosti k písničce %1 (via MPD) scrobbler name (via MPD) %1 (přes MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Pokud použijete odesílání údajů o skladbách (scrobbler), což je označeno jako '(přes MPD)' (například %1), potom musíte mít tuto službu již spuštěnu a běžící. Cantata umí skladby Zařadit mezi oblíbené jen pomocí této služby, a neumí odesílání údajů o skladbách zakázat/povolit. Authenticating... Ověřuje se pravost... Authenticated Pravost ověřena Not Authenticated Pravost neověřena ScrobblingStatus %1: Scrobble Tracks %1: Odesílat informace o skladbách SearchModel # (Track Number) Číslo skladby SearchPage Locate In Library Najít v knihovně Artist: Umělec: Composer: Skladatel: Performer: Účinkující: Album: Album: Title: Název: Genre: Žánr: Comment: Poznámka: Date: Datum: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Najít písně hledáním ve značce Datum. <br/><br/>Obvykle by mělo stačit jen zadat rok. Original Date: Původní datum: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Najít písně hledáním ve značce Původní datum. <br/><br/>Obvykle by mělo stačit jen zadat rok. Modified: Změněno: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Zadejte datum (RRRR/MM/DD - např. 2015/01/31) k vyhledání souborů změněných od toho databázi.<br/><br>Nebo zadejte počet dní pro nalezení souborů, jež byly změněny v tolika předchozích dnech. File: Soubor: Any: Jakékoli: No tracks found. Nenalezeny žádné skladby. %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) SearchWidget Search... Hledání... Close Search Bar Zavřít vyhledávací pole ServerSettings Collection: Sbírka: Name: Název: Host: Server: Password: Heslo: Music folder: Složka s hudbou: Cover filename: Název souboru obalu: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Souborový název (bez přípony), pod nímž se mají uložit stažené obaly.<br/> Jestliže bude ponecháno prázdné, použije se "cover".<br/><br/><i>%artist% bude nahrazen umělcem alba současné písně, a %album% bude nahrazeno názvem alba.</i></p> HTTP stream URL: Adresa (URL) proudu HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. Nastavení pro hudební složku je používáno k vyhledání obrázků obalů. Může být nastaveno na adresu (URL) HTTP, pokud je vaše MPD na jiném stroji a obaly jsou přístupné přes HTTP. Pokud není nastaveno na adresu (URL) HTTP a vy máte i oprávnění pro zápis do této složky (a jejích podsložek), Cantata uloží všechny stažené obaly do příslušné složky s albem. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> Pokud není nastaveno žádné nastavení pro název souboru s obalem, pak bude Cantata používat výchozí <code>obal</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. Adresa (URL) proudu HTTP se používá jen tehdy, když máte nastaveno MPD na výstup do proudu HTTP, a přejete si, aby Cantata mohla tento proud přehrávat. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. Pokud změníte nastavení pro složku s hudbou, budete muset hudební databázi obnovovat ručně. To je možné provádět stisknutím tlačítka Obnovit databázi v pohledech s umělci nebo alby. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. Tato složka se bude používat i na umísťování hudebních souborů pro upravování značek, vyrovnávání hlasitosti přehrávaných skladeb a přesunování na (a z) zařízení. This folder will also be used to locate music files for tag-editing, replay gain, etc. Tato složka se bude používat i na umísťování hudebních souborů pro upravování značek, vyrovnávání hlasitosti přehrávaných skladeb atd. Which type of collection do you wish to connect to? Který typ sbírky chcete připojit? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Obvyklý - hudební sbírku může být sdílena, je na jiném stroji, nebo je již nastavena, nebo chcete povolit přístup z jiných klientů (např. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Základní - hudební sbírka není sdílena s ostatními, a Cantata nastaví a bude řídit instanci MPD. Toto nastavení bude pro Cantatu výlučné a <b>nebude</b> přístupné pro jiné klienty MPD. <i><b>NOTE:</b> %1</i> <i><b>POZNÁMKA:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Pokud chcete mít pokročilé nastavení MPD (např. více výstupů zvuku, plnou podporu pro DSD atd.), potom <b>musíte</b> vybrat Obvyklý Add Collection Přidat sbírku Standard Obvyklý Basic Základní Delete '%1'? Smazat '%1'? Delete Smazat New Collection %1 Nová sbírka %1 Default Výchozí ServiceStatusLabel Logged into %1 Přihlášen k %1 <b>NOT</b> logged into %1 <b>NENÍ</b>přihlášen k %1 ShortcutsModel Action Činnost Shortcut Zkratka ShortcutsSettingsWidget Search: Hledání: Shortcut for Selected Action Zkratka pro vybranou činnost Default: Výchozí: None Žádné Custom: Vlastní: SinglePageWidget Refresh Obnovit View Pohled SmartPlaylists Smart Playlists Chytré seznamy skladeb Rules based playlists Seznamy skladeb založené na pravidlech SmartPlaylistsPage Add Přidat Edit Upravit Remove Odstranit Are you sure you wish to remove the selected rules? This cannot be undone. Opravdu chcete odstranit vybraná pravidla? Tento krok nelze vrátit zpět. Remove Smart Rules Odstranit chytrá pravidla Failed to locate any matching songs Nepodařilo se najít žádnou odpovídající píseň SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Nelze přistupovat k souborům s písněmi! Prověřte, prosím, nastavení složky s hudbou Cantaty a nastavení adresáře s hudbou MPD (music_directory). Cannot access song files! Please check that the device is still attached. Nelze přistupovat k souborům s písněmi! Prověřte, prosím, že zařízení je stále ještě připojeno. SongView Lyrics Slova písně Information Informace Metadata Popisná data Scroll Lyrics Projíždět text písně Refresh Lyrics Obnovit slova písně Edit Lyrics Upravit slova písně Delete Lyrics File Smazat soubor se slovy písně Refresh Track Information Obnovit informace o skladbě Cancel Zrušit Track Skladba Reload lyrics? Reload from disk, or delete disk copy and download? Nahrát text písně znovu? Nahrát znovu z disku, nebo smazat kopii na disku a stáhnout? Reload Nahrát znovu Reload From Disk Nahrát znovu z disku Download Stáhnout Current playing song has changed, still perform search? Nyní hrající píseň se změnila. Pořád ještě provést hledání? Song Changed Píseň změněna Perform Search Provést hledání Delete lyrics file? Smazat soubor se slovy písně? Delete File Smazat soubor Artist Umělec Album artist Umělec alba Composer Skladatel Lyricist Textař Conductor Dirigent Remixer Autor předělávky Album Album Subtitle Podnázev Track number Číslo skladby Disc number Číslo disku Genre Žánr Date Datum Original date Původní datum Comment Poznámka Copyright Autorské právo Label Štítek Catalogue number Katalogové číslo Title sort Třídění podle názvu Artist sort Třídění podle umělce Album artist sort Třídění podle alba a umělce Album sort Třídění podle alba Encoded by Zákodováno Encoder Kodér Mood Nálada Media Média Bitrate Datový tok Sample rate Vzorkovací kmitočet Channels Kanály Tagging time Čas značkování Performer (%1) Účinkující (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bity Performer Účinkující Year Rok Filename Název souboru Fetching lyrics via %1 Natahují se slova písně přes %1 SoundCloudService Search for tracks from soundcloud.com Hledat skladby na soundcloud.com SpaceLabel Calculating... Počítá se... Total space used: %1 Celkové použité místo: %1 SqlLibraryModel %n Artist(s) Umělci: %n Umělci: %n Umělci: %n %n Album(s) Alba: %n Alba: %n Alba: %n %n Tracks (%1) Skladby: %n (%1) Skladby: %n (%1) Skladby: %n (%1) Cue Sheet List CUE Playlist Seznam skladeb StoredPlaylistsPage Rename Přejmenovat Remove Duplicates Odstranit zdvojené Initially Collapse Albums Na začátku alba složit Are you sure you wish to remove the selected playlists? This cannot be undone. Opravdu chcete odstranit vybrané seznamy skladeb? Tento krok nelze vrátit zpět. Remove Playlists Odstranit seznamy skladeb Playlist Name Název seznamu skladeb Enter a name for the playlist: Zadejte název pro seznam skladeb: A playlist named '%1' already exists! Overwrite? Seznam skladeb pojmenovaný '%1' již existuje! Přepsat? Overwrite Playlist Přepsat seznam skladeb Rename Playlist Přejmenovat seznam skladeb Enter new name for playlist: Zadejte nový název pro seznam skladeb: Cannot add songs from '%1' to '%2' Nelze přidat písně z '%1' do '%2' StreamDialog Add stream to favourites Přidat proud do oblíbených Name: Název: URL: Adresa (URL): Add Stream Přidat proud Edit Stream Upravit proud <i><b>ERROR:</b> Invalid protocol</i> <i><b>CHYBA:</b> Neplatný protokol</i> StreamFetcher Loading %1 Nahrává se %1 StreamProviderListDialog Installed Nainstalováno Update available Aktualizace dostupná Check the providers you wish to install/update. Prověřit poskytovatele, které si přejete nainstalovat/aktualizovat. Install/Update Stream Providers Nainstalovat/Aktualizovat poskytovatele proudů Downloading list... Stahuje se seznam... Failed to download list of stream providers! Nepodařilo se stáhnout seznam poskytovatelů proudů! Installing/updating %1 Instaluje se/Aktualizuje se %1 Failed to install '%1' Nepodařilo se nainstalovat '%1' Failed to download '%1' Nepodařilo se stáhnout '%1' Install/update the selected stream providers? Nainstalovat/Aktualizovat poskytovatele vybraných proudů? Install the selected stream providers? Nainstalovat poskytovatele vybraných proudů? Update the selected stream providers? Aktualizovat poskytovatele vybraných proudů? Install/Update Instalovat/Aktualizovat Abort installation/update? Zrušit instalaci/aktualizaci? Abort Přerušit %n Update(s) available Jedna aktualizace dostupná %n aktualizací dostupných %n aktualizací dostupných Downloading %1 Stahuje se %1 Update all updateable providers Aktualizovat všechny aktualizovatelné poskytovatele StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Hledání proudu Search for radio streams Hledat rozhlasové proudy Enter string to search Zadejte hledaný řetězec Not Loaded Nenahráno Loading... Nahrává se... %n Entry(s) Položky: %n Položky: %n Položky: %n StreamSearchPage Added '%1'' to favorites Přidáno "%1'' do oblíbených StreamsBrowsePage Import Streams Into Favorites Zavést proudy do oblíbených Export Favorite Streams Vyvést oblíbené proudy Add New Stream To Favorites Přidat nový proud do oblíbených Edit Upravit Seatch For Streams Hledat rozhlasové proudy Configure Nastavit Digitally Imported Service name Digitally Imported Import Streams Zavést proudy XML Streams (*.xml *.xml.gz *.cantata) Proudy XML (*.xml *.xml.gz *.cantata) Export Streams Vyvést proudy XML Streams (*.xml.gz) Proudy XML (*.xml.gz) Failed to create '%1'! Nepodařilo se vytvořit '%1'! Stream '%1' already exists! Proud '%1' již existuje! A stream named '%1' already exists! Proud pojmenovaný '%1' již existuje! Bookmark added Záložka přidána Already bookmarked Již opatřeno záložkou Already in favorites Již v oblíbených Reload '%1' streams? Nahrát znovu '%1' proudů? Are you sure you wish to remove bookmark to '%1'? Opravdu chcete odstranit záložku k '%1'? Are you sure you wish to remove all '%1' bookmarks? Opravdu chcete odstranit všech '%1' záložek? Are you sure you wish to remove the %1 selected streams? Opravdu chcete odstranit %1 vybrané proudy? Are you sure you wish to remove '%1'? Opravdu chcete odstranit '%1'? Added '%1'' to favorites Přidáno "%1'' do oblíbených StreamsModel Bookmarks Záložky TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Oblíbené Bookmark Category Skupina záložky Add Stream To Favorites Přidat proud do oblíbených Configure Digitally Imported Nastavit Digitally Imported Reload Nahrát znovu Streams Proudy Radio stations Rozhlasové stanice Not Loaded Nenahráno Loading... Nahrává se... %n Entry(s) Položky: %n Položky: %n Položky: %n StreamsSettings Use the checkboxes below to configure the list of active providers. Použijte zaškrtávací okénka níže k nastavení seznamu činných poskytovatelů. Built-in categories are shown in italic, and these cannot be removed. Vestavěné skupiny jsou zobrazovány kurzívou, a tyto nelze odstranit. Configure Streams Nastavit proudy From File... Ze souboru... Download... Stáhnout... Configure Provider Nastavit poskytovatele Install Instalovat Remove Odstranit Install Streams Nainstalovat proudy Cantata Streams (*.streams) Proudy Cantata (*.streams) A category named '%1' already exists! Overwrite? Skupina pojmenovaná '%1' již existuje! Přepsat? Failed top open package file. Nepodařilo se otevřít soubor s balíčkem. Invalid file format! Neplatný souborový formát! Failed to create stream category folder! Nepodařilo se vytvořit složku pro skupinu proudu! Failed to save stream list! Nepodařilo se uložit seznam proudu! Are you sure you wish to remove '%1'? Opravdu chcete odstranit '%1'? Failed to remove streams folder! Nepodařilo se odstranit složku s proudy! SyncCollectionWidget Search Hledat Check Items Zaškrtnout položky Uncheck Items Zrušit zaškrtnutí položek SyncDialog Library: Knihovna: Device: Zařízení: Loading all songs from library, please wait... Nahrávají se všechny písně v knihovně. Počkejte, prosím... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Knihovna</code> uvádí pouze písně, které jsou v knihovně, ale ne na zařízení. Stejně tak <code>Zařízení</code> uvádí písně, které jsou jen na zařízení.<br/>Vyberte písně z <code>knihovny</code>, jež chcete zkopírovat do <code>zařízení</code>, a vyberte písně ze <code>zařízení</code>, jež chcete zkopírovat do <code>knihovny</code>. Potom stiskněte tlačítko <code>Seřídit</code>. Synchronize Seřídit Device and library are in sync. Zařízení a knihovna jsou vzájemně seřízeny. Loading all songs from library, please wait...%1%... Nahrávají se všechny písně z knihovny. Počkejte, prosím... %1%... Local Music Library Properties Vlastnosti místní hudební knihovny Device has been removed! Zařízení bylo odstraněno! Device has been changed? Zařízení bylo změněno? Device is busy? Zařízení je zaneprázdněno? TableView Stretch Columns To Fit Window Roztáhnout sloupce tak, aby se vešly do okna Left Vlevo Center Střed Right Vpravo Alignment Zarovnání TagEditor Track: Skladba: Title: Název: Artist: Umělec: Album artist: Umělec alba: Composer: Skladatel: Album: Album: Track number: Číslo skladby: Disc number: Číslo disku: Genre: Žánr: Year: Rok: Rating: Hodnocení: <i>(Various)</i> <i>(Různé)</i> Comment: Poznámka: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Více žánrů se musí oddělit čárkou (např. 'Rock;Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Hodnocení jsou ukládána ve vnější databázi, <b>ne</b> v souboru s písní. Tags Značky Tools Nástroje Apply "Various Artists" Workaround Použít zařazení pod Různí umělci Revert "Various Artists" Workaround Zvrátit zařazení pod Různí umělci Set 'Album Artist' from 'Artist' Nastavit Umělec alba z Umělec Capitalize Psát velkými písmeny Adjust Track Numbers Upravit čísla skladeb Read Ratings from File Načíst hodnocení ze souboru Write Ratings to File Zapsat hodnocení do souboru All tracks Všechny skladby Apply "Various Artists" workaround to <b>all</b> tracks? Použít zařazení pod Různí umělci na <b>všechny</b> skladby? Apply "Various Artists" workaround? Použít zařazení pod Různí umělci? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Toto nastaví Umělce alba a Umělce na Různí umělci, a nastaví Název na "Umělec skladby - Název skladby"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Zvrátit zařazení pod Různí umělci pro <b>všechny</b> skladby? Revert "Various Artists" workaround Zvrátit zařazení pod Různí umělci <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Tam, kde Umělec alba je stejný jako Umělec a Název je ve formátu "Umělec skladby - Název skladby", se Umělec vezme z Názvu a samotný název se nastaví na prostý Název. Např. <br/><br/>Pokud je Název "Wibble - Wobble", pak Umělec se nastaví na "Wibble" a Název na "Wobble"</i> Revert Vrátit Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Nastavit Umělec alba z Umělec (pokud je Umělec alba prázdný) pro <b>všechny</b> skladby? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Nastavit Umělec alba z Umělec (pokud je Umělec alba prázdný)? Album Artist from Artist Umělec alba z Umělec Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Psát velkým písmenem první písmeno textových polí (např. Název, Umělec, Umělec alba, Album atd.) u <b>všech</b> skladeb? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Psát velkým písmenem první písmeno textových polí (např. Název, Umělec, Umělec alba, Album atd.)? Adjust the value of each track number by: Upravit hodnotu čísla každé skladby o: Adjust track number by: Upravit číslo skladby o: Read ratings for all tracks from the music files? Načíst hodnocení u všech skladeb z hudebních souborů? Read rating from music file? Načíst hodnocení z hudebního souboru? Ratings Hodnocení Read Ratings Načíst hodnocení Read Rating Načíst hodnocení Read, and updated, ratings from the following tracks: Načíst aktualizovaná hodnocení z následujících skladeb: Not all Song ratings have been read from MPD! Ne všechna hodnocení písní byla načtena z MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Hodnocení písní nejsou uložena v souborech s písněmi, ale uvnitř databáze MPD. Pro jejich uložení do skutečného souboru je Cantata nejprve musí načíst z MPD. Song rating has not been read from MPD! Hodnocení písně nebylo načteno z MPD! Write ratings for all tracks to the music files? Načíst hodnocení u všech skladeb do hudebních souborů? Write rating to music file? Zapsat hodnocení do hudebního souboru? Write Ratings Zapsat hodnocení Write Rating Zapsat hodnocení Failed to write ratings of the following tracks: Nepodařilo se zapsat hodnocení následujících skladeb: Failed to write rating to music file! Nepodařilo se zapsat hodnocení do hudebního souboru! All tracks [modified] Všechny skladby [změněno] %1 [modified] %1 [změněno] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (poškozené značky?) Failed to update the tags of the following tracks: Nepodařilo se zaktualizovat značky následujících skladeb: Would you also like to rename your song files, so as to match your tags? Chcete také přejmenovat své soubory s písněmi tak, aby se shodovaly se značkami? Rename Files Přejmenovat soubory Rename Přejmenovat Device has been removed! Zařízení bylo odstraněno! Device is not connected. Zařízení není připojeno. Device is busy? Zařízení je zaneprázdněno? TagSpinBox (Various) (Různí) ThinSplitter Reset Spacing Nastavit znovu řádkování TitleWidget Click to go back Klepněte pro návrat zpět Add All To Play Queue Přidat vše do řady skladeb k přehrání Add All And Replace Play Queue Přidat vše a nahradit řadu skladeb k přehrání ToggleList Available: Dostupné: Selected: Vybrané: TrackOrganiser Filenames Názvy souborů Filename scheme: Schéma názvu souboru: VFAT safe VFAT bezpečný Use only ASCII characters Použít pouze znaky ASCII Replace spaces with underscores Nahradit mezery podtržítky Append 'The' to artist names Připojit The ke jménům umělců Original Name Původní název New Name Nový název Ratings will be lost if a file is renamed. Hodnocení budou ztracena, pokud bude soubor přejmenován. Organize Files Uspořádat soubory Rename Přejmenovat Remove From List Odstranit ze seznamu Abort renaming of files? Zrušit přejmenování souborů? Abort Přerušit Source file does not exist! Zdrojový soubor neexistuje! Skip Přeskočit Auto Skip Automaticky přeskočit Destination file already exists! Cílový soubor již existuje! Failed to create destination folder! Nepodařilo se vytvořit cílovou složku! Failed to rename '%1' to '%2' Nepodařilo se přejmenovat '%1' na '%2' Remove the selected tracks from the list? Odstranit vybrané skladby ze seznamu? Remove Tracks Odstranit skladby Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Hodnocení písní nejsou uložena v souborech s písněmi, ale uvnitř databáze MPD. Pokud soubor přejmenujete (nebo složku, v níž je), potom bude hodnocení spojené s písní ztraceno. Device has been removed! Zařízení bylo odstraněno! Device is not connected. Zařízení není připojeno. Device is busy? Zařízení je zaneprázdněno? TrayItem Cantata Cantata Now playing Nyní se hraje UltimateLyricsProvider (Polish Translations) Polština (Portuguese Translations) Portugalština UmsDevice Not Scanned Neprohledáno Not Connected Nepřipojeno %1 free %1 volno ValueSlider (recommended) (doporučeno) View Cancel Zrušit VolumeSlider Mute Ztlumit Unmute Zrušit ztišení Volume %1% (Muted) Hlasitost: %1% (ztlumeno) Volume %1% Hlasitost %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | umělec|skupina|pěvec|zpěvák|zpěvačka|hudebník album|score|soundtrack Search pattern for an album, separated by | album|hudba|zvukový záznam WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Vyberte jazyky na Wikipedii, které chcete použít při hledání umělců a informací o albech. Reload Nahrát znovu cantata-2.2.0/translations/cantata_de.ts000066400000000000000000022104441316350454000203120ustar00rootroot00000000000000 Refresh Album Information Aktualisiere Albuminformationen Album Album Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Stücke Refresh Artist Information Aktualisiere Künstlerinformationen Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Künstler Albums Alben Web Links Weblinks Similar Artists Ähnliche Künstler Lyrics Providers Textanbieter Wikipedia Languages Wikipedia Other Andere Reset Spacing Abstand zurücksetzen &Artist &Künstler Al&bum Al&bum Read more on last.fm Mehr auf last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Wenn Cantata keine oder falsche Texte gefunden hat diesen Dialog zum erneuten Suchen verwenden, zum Beispiel wenn das aktuelle Stück eine Coverversion ist. Die Suche nach dem Originalkünstler könnte das korrekte Ergebnis erzielen. Wenn die Suche keinen neuen Liedtext findet, wird dieser mit dem Originalkünstler und Titel assoziiert und in Cantata angezeigt. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Titel: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Künstler: Search For Lyrics Suche nach Texten Choose the websites you want to use when searching for lyrics. Bitte die Webseiten zur Liedtextsuche wählen. Images (*.png *.jpg) Bilder (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics Texte Information Information Refresh Lyrics Aktualisiere Texte Edit Lyrics Bearbeite Texte Delete Lyrics File Lösche Liedtextdatei? Cancel Abbrechen Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Stück Reload Erneut Laden Current playing song has changed, still perform search? Aktuell gespielter Titel hat sich geändert. Suche fortsetzen? Song Changed Stück geändert Perform Search Führe Suche aus Delete lyrics file? Löschen der Liedtextdatei? Delete File Lösche Datei Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Genre Date Datum Encoder Encoder Bitrate Bitrate Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Jahr Fetching lyrics via %1 Empfange Texte über %1 (Polish Translations) (Polnische Übersetzung) (Portuguese Translations) (Portugiesische Übersetzung) Track listing Stückliste Read more on wikipedia Lese mehr auf Wikipedia Open in browser Öffne im Browser artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | Künstler| Band|Sänger|Vokalist|Musiker album|score|soundtrack Search pattern for an album, separated by | Album|Bewertung|Soundtrack Choose the wikipedia languages you want to use when searching for artist and album information. Bitte wählen Sie die Sprache von Wikipedia, welche für Künstlerinformationen und Albuminformationen benutzt werden soll. Cantata is playing a track Cantata spielt ein Stück ab <b>INVALID</b> <b>UNGÜLTIG</b> <i>(When different)</i> <i>(Wenn unterschiedlich)</i> Artists:%1, Albums:%2, Songs:%3 Künstler:%1, Alben:%2, Stücke:%3 %1 free %1 frei Local Music Library Lokale Musikbibliothek Audio CD Audio CD Copy Songs To Device Kopiere Stücke auf Gerät Copy Songs Kopiere Stücke Delete Songs Lösche Stücke Not Configured Nicht konfiguriert Use Defaults Benutze Voreinstellung Are you sure you wish to stop? Wollen Sie wirklich stoppen? Stop Stopp Device has been removed! Gerät wurde entfernt. Device is not connected! Gerät ist nicht verbunden. Device is busy? Gerät aktuell beschäftig? Device has been changed? Gerät wurde geändert? Clearing unused folders Bereinige unbenutzte Ordner Calculate ReplayGain for ripped tracks? Errechne ReplayGain der Stücke? ReplayGain ReplayGain Calculate Berechne The destination filename already exists! Der Dateiname existiert bereits. Song already exists! Lied existiert bereits. Song does not exist! Titel existiert nicht. Failed to create destination folder!<br/>Please check you have sufficient permissions. Fehler beim Erstellen des Ordners auf Zielgerät.<br/>Bitte überprüfen Sie auf vorhandene Berechtigung. Source file no longer exists? Quelldatei nicht mehr verfügbar? Failed to copy. Fehler beim Kopieren. Failed to delete. Fehler beim Löschen. Not connected to device. Nicht zum Gerät verbunden. Selected codec is not available. Ausgewählter Codec ist nicht vorhanden. Transcoding failed. Umwandeln fehlgeschlagen. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Fehler beim Erstellen einer temporären Datei.<br/>(Benötigt zum Umwandeln für MTP-Geräte.) Failed to read source file. Fehler beim Lesen der Quelldatei. Failed to write to destination file. Fehler beim Erstellen der Zieldatei. No space left on device. Kein Platz auf Gerät mehr verfügbar. Failed to update metadata. Fehler beim Aktualisieren der Metadaten. Failed to download track. Fehler beim Herunterladen Failed to lock device. Fehler beim Verriegeln des Gerätes. Local Music Library Properties Lokale Bibliothekseinstellungen Error Fehler Skip Überspringen Auto Skip Automatisches Überspringen Retry wiederholen Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Album: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Stück: Source file: Quelldatei: Destination file: Zieldatei: File: Datei: Calculating... Berechne... %1 (Estimated) time (Estimated) %1 (Erwartet) Time remaining: Verbleibende Zeit: Saving cache Speichere Cache Apply "Various Artists" Workaround Hinzufügen der „Verschiedene Künstler Problembehebung Revert "Various Artists" Workaround Zurücksetzen der „Verschiedene Künstler“ Problembehebung Capitalize Großschrift Adjust Track Numbers Anpassen der Stücknummern Tools Werkzeuge Apply "Various Artists" workaround? Hinzufügen der „Verschiedene Künstler“ Problembehebung? Revert Umkehren Adjust track number by: Anpassen der Stücknummern durch: Reading disc Lese Disk CDDB CDDB MusicBrainz MusicBrainz Data Track Datentrack Failed to open CD device Fehler beim Öffnen des CD-Laufwerkes Track %1 Stück %1 Failed to create CDDB connection Fehler bei CDDB-Verbindung No matches found in CDDB Keine Treffer in CDDB CDDB error: %1 CDDB-Fehler: %1 Multiple matches were found. Please choose the relevant one from below: Mehrfach Zutreffendes gefunden. Bitte den relevanten Eintrag unten auswählen: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Titel Disc Selection Disc Auswahl %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 – %2 Disk %3 (%4) %1 - %2 (%3) artist - album (year) %1 – %2 (%3) Updating (%1)... Aktualisiere (%1)... Updating (%1%)... Aktualisiere (%1%)... Device Properties Geräteeinstellungen Don't copy covers Keine Cover kopieren Embed cover within each file Cover in Datei einfügen No maximum size Keine maximale Größe 400 pixels 400 pixel 300 pixels 300 pixel 200 pixels 200 pixel 100 pixels 100 pixel <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Beim Kopieren von Stücken auf ein Gerät wird, falls der 'Albumkünstler' auf„Verschiedene Künstler“ gesetzt ist, wird Cantata den 'Künstler'-Tag aller Stücke auf „Verschiedene Künstler“ setzen und 'Titel'-Tag auf 'Künstlername - Titelname'.<hr/> Beim Kopieren von einem Gerät wird Cantata prüfen, ob der 'Albumkünstler' und 'Künstler' auf „Verschiedene Künstler“ gesetzt ist. Sollte dies zutreffen, wird versucht den echten Künstlernamen vom 'Titel'-Tag zu lesen und den Künstlernamen vom 'Titel' Tag zu entfernen.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Wenn Sie dies aktiviert haben, wird Cantata einen Zwischenspeicher für die Musikbibliothek des Gerätes erstellen. Dies wird das Auslesen von nachfolgenden Aufrufen der Bibliothek beschleunigen (es wird der Zwischenspeicher verwendet anstatt jeden Tag neu einzulesen.)<hr/><b>Hinweis:</b> Wenn Sie ein anderes Programm zum aktualisieren der Musikbibliothek benutzen wird der Zwischenspeicher ungültig. Um dies zu korrigieren, einfach auf das "Aktualisieren"-Symbol in der Geräteliste klicken. Dadurch wird die aktuelle Zwischenspeicher-Datei entfernt und der Inhalt des Gerätes erneut eingelesen.</p> Do not transcode Nicht umwandeln Transcode to %1 Umwandeln zu %1 %1 (%2 free) name (size free) %1 (%2 frei) Copy To Library Kopiere in Bibliothek Forget Device Vergesse Gerät Add Device Gerät hinzufügen Lookup album and track details? Nach Album und Stücken suchen? Refresh Aktualisieren Via CDDB Via CDDB Via MusicBrainz Via MusicBrainz Partial Partiell Full Voll Are you sure you wish to forget '%1'? Wollen Sie wirklich '%1' vergessen? Eject Auswerfen Disconnect Verbindung beenden Please close other dialogs first. Andere Dialoge bitte zuerst schließen. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding </a> (AAC) ist ein patentierter verlustbehafteteter Codec für Digital Audio.<br> AAC besitzt in der Regel eine bessere Klangqualität als MP3 bei gleicher Bitrate. Es ist eine vernünftige Wahl für den iPod und den meisten modernen, tragbaren Musik-Playern. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Die Bitrate ist eine Einheit für die Menge an Daten um eine Sekunde des Stückes darzustellen.<br>Der <b>AAC</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>Variable Bitraten (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abstände von Daten werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>160kb/s</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>150kb/s</ b> könnte nicht zufriedenstellend sein alles über <b>200kb/s</b> ist wahrscheinlich übertrieben. Expected average bitrate for variable bitrate encoding Erwartete durchschnittliche Bitrate für die Kodierung mit variabler Bitrate Smaller file Kleinere Datei Better sound quality Bessere Qualität <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</ a> (MP3) ist ein patentierter digitaler Audio-Codec mit einer verlustbehafteten Komprimierung.<br> Trotz seiner Mängel ist es ein weit verbreitetes Format zum Speichern von Audiodateien und ist unterstützt fast alle tragbaren Musik-Player. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Die Bitrate ist eine Einheit für die Menge an Daten, um eine Sekunde des Stückes darzustellen.<br>Der <b>MP3</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>Variable Bitraten (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abstände von Daten werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>160kb/s</b> ist eine gute Wahl für Musikhörenauf einem tragbaren Player.<br/>Alles unter <b>120kb/s</b> könnte nicht zufriedenstellend sein alles über <b>205kb/s</b> ist wahrscheinlich übertrieben. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/Vorbis>Ogg Vorbis </a> ist ein offener und lizenzfreier Audio-Codec für verlustbehaftete Audio-Kompression.<br>Er produziert kleinere Dateien als MP3 bei gleicher oder höherer Qualität. Ogg Vorbis ist ein rundum gute Wahl besonders für tragbare Musik-Player welche ihn unterstützen. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Die Bitrate ist eine Einheit für die Menge an Daten, um eine Sekunde des Stückes darzustellen.<br>Der <b>Vorbis</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>Variable Bitraten (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abstände von Daten werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br/>Der Vorbis-Encoder verwendet eine Wertigkeit zwischen -1 und 10, um eine bestimmte erwartete Audioqualität zu definieren. Die Bitraten-Einstellung mit dem Schieberegler ist nur eine grobe Schätzung (bereitgestellt von Vorbis) der durchschnittlichen Bitrate des codierten Stückes bei einem Qualitätswert. Mit neueren und effizienteren Vorbis-Versionen ist die tatsächlicheBitrate ist sogar noch niedriger.<br><b>5</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>3</b> könnte nicht zufriedenstellend sein alles über <b>8</b> ist wahrscheinlich übertrieben. Quality rating Qualitätsbewertung Opus Opus The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Die Bitrate ist eine Einheit für die Menge an Daten, um eine Sekunde des Stückes darzustellen.<br>Der <b>Opus</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>Variable Bitraten (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abstände von Daten werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>128kb/s</b> ist eine gute Wahl für Musikhörenauf einem tragbaren Player.<br/>Alles unter <b>100kb/s</b> könnte nicht zufriedenstellend sein alles über <b>256kb/s</b> ist wahrscheinlich übertrieben. Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/Apple_Lossless>Apple Lossless </a> (ALAC) ist ein Audio-Codec für die verlustfreie Komprimierung digitaler Musik.<br>Empfohlen wird dies nur für Apple Geräte und Geräte welche keine Unterstützung FLAC besitzen. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec </a> (FLAC) ist ein offener und lizenzfreier Codec für verlustfreie Komprimierung von digitaler Musik. <br> Wenn Sie Ihre Musik ohne Kompromisse bei der Audioqualität speichern möchten, ist FLAC eine ausgezeichnete Wahl. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Der <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Komprimierungsgrad</a> ist ein Zahlenwert zwischen 0 and 8, welcher einen Kompromiss zwischen Dateigröße und Komprimierungsgeschwindigkeit während der Kodierung mit<b>FLAC</b> darstellt.<br/> Einstellen der Kompressionsrate auf <b>0</ b> liefert die kürzeste Zeit, aber die Kompression erzeugt eine vergleichsweise große Datei.<br/>Auf der anderen Seite arbeitet eine Kompression von <b>8</b> sehr langsam, erzeugt aber eine kleinere Datei.<br/>Beachten Sie, dass FLAC per Definition ein verlustfreier Codec ist. Die Audio-Qualität der Ausgabe ist unabhängig von der Kompression.<br/>Ein Komprimierungsgrad über <b>5</b> erhöht erheblich die Kompressionszeit wird aber kaum kleinere Dateien erstellen. Compression level Komprimierungsgrad Faster compression Schnellere Komprimierung Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://de.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) ist ein proprietärer Codec von Microsoft für verlustbehaftete Audiokompression.<br>Empfohlen nur für tragbare Geräte welche nicht Ogg Vorbis unterstützen. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Die Bitrate ist eine Einheit für die Menge an Daten um eine Sekunde des Stückes darzustellen.<br>Aufgrund der Einschränkungen des proprietären <b>WMA</b>-Formats und die Schwierigkeit eines Reverse-Engineerings des Encoders wird der WMA-Encoder, welcher von Cantata benutzt wird <a href=http://de.wikipedia.org/wiki/Windows_Media_Audio>eine konstante Bitrate (CBR)</a> benutzen.<br>Aus diesem Grund ist die Bitrateneineit in diesem Schieberegler eine ziemlich genaue Schätzung der Bitrate der codierten Spur.<br><b>136kb/s</b> ist eine gute Wahl für Musikhörenauf einem tragbaren Gerät.<br/>Alles unter <b>112kb/s</ b> könnte nicht zufriedenstellend sein, aber alles über <b>182kb/s</b> ist wahrscheinlich übertrieben. Filename Scheme Dateischema Various Artists Example album artist Verschiedene Künstler Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance Updating... Aktualisiere... Reading cache Lese den Zwischenspeicher %1 %2% Message percent %1 %2% Connecting to device... Verbinde mit Gerät... No devices found Keine Geräte gefunden Connected to device Mit Gerät verbunden Disconnected from device Vom Gerät getrennt Updating folders... Aktualisiere Ordner... Updating files... Aktualisiere Dateien... Updating tracks... Aktualisiere Titel... Not Connected Nicht verbunden %1 (Disc %2) %1 (Disk %2) No matches found in MusicBrainz Keine Treffer in MusicBrainz gefunden Connection Verbindung Music Library Musikbibliothek Samba Share Samba-Freigabe Samba Share (Auto-discover host and port) Samba-Freigabe (Automatisch Host und Port finden) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Lokal eingehängter Ordner Available Verfügbar Not Available Nicht verfügbar Failed to resolve connection details for %1 Fehler beim Auflösen der Verbindungsdetails zu %1 Connecting... Verbinde... Password prompting does not work when cantata is started from the commandline. Passwortabfrage funktioniert nicht, wenn Cantata von der Kommandozeile gestartet wird. No suitable ssh-askpass application installed! This is required for entering passwords. Kein brauchbares ssh-askpass installiert. Dies wird benötigt, um Passwörter eingeben zu können. Mount point ("%1") is not empty! Einhängepunkt ("%1") ist nicht leer. "sshfs" is not installed! "sshfs" ist nicht installiert. Disconnecting... Verbindung beenden... "fusermount" is not installed! "fusermount" ist nicht installiert. Failed to connect to "%1" Fehler beim Verbinden zu "%1" Failed to disconnect from "%1" Fehler beim Beenden der Verbindung von "%1" Capacity Unknown Größe unbekannt Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Suche Check Items Aktiviere markiere Stücke Uncheck Items Deaktiviere markierte Stücke Synchronize Synchronisieren Device and library are in sync. Gerät und Bibliothek sind synchronisiert. Not Scanned Nicht eingelesen (recommended) (benötigt) Failed to delete rules file. (%1) Fehler beim Löschen der Einstellungsdatei. (%1) Unknown error Unbekannter Fehler Start Dynamic Playlist Starte dynamische Wiedergabeliste Stop Dynamic Mode Stoppe dynamischen Modus Dynamic Playlists Dynamische Wiedergabelisten You need to install "perl" on your system in order for Cantata's dynamic mode to function. Sie müssen "perl" auf ihrem System installieren, um den dynamischen Modus von Cantata zu benutzen. Failed to locate rules file - %1 Fehler beim Finden der Einstellungsdatei – %1 Failed to remove previous rules file - %1 Fehler beim Entfernen der bestehenden Regeldatei – %1 Failed to install rules file - %1 -> %2 Fehler beim Erstellen der Einstellungsdatei – %1 → %2 Dynamizer has been terminated. Dynamizer wurde beendet Saving rule Speichere Regel Deleting rule Lösche Regel Awaiting response for previous command. (%1) Warte auf Antwort vom vorherigen Kommando. (%1) Failed to control dynamizer state. (%1) Fehler beim Setzen des Dynamizer-Status. (%1) Failed to set the current dynamic rules. (%1) Fehler beim Setzen der dynamischen Regeln. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Hinzufügen Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Bearbeiten Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Entfernen Remote dynamizer is not running. Entfernter Dynamizer ist nicht aktiv. Remove Dynamic Rules Entferne dynamische Regeln Dynamic Rule Dynamische Regel <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>FEHLER:</b> 'Von Jahr' muss kleiner als 'Zu Jahr' sein</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>Fehler:</b> Zeitraum ist zu lang (kann maximal nur %1 Jahre betragen)</i> SimilarArtists Ähnliche Künstler AlbumArtist AlbumKünstler Include Einbeziehen Exclude Ausschließen (Exact) (Exakt) Dynamic Rules Dynamische Regeln None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Keine Failed to save %1 Fehler beim Speichern von %1 Overwrite Rules Regel überschreiben: Saving %1 Speichere %1 Deleting... Lösche... Name Name Item Count Gezählte Stücke Space Used Verbrauchter Speicherplatz Covers Cover Scaled Covers Skalierte Cover Backdrops Hintergrundbilder Artist Information Künstlerinformationen Album Information Albuminformation Delete All Lösche alle Delete all '%1' items? Lösche alle '%1' Einträge? Delete Cache Items Löschen Zwischenspeicher %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Aktuelles Cover Image Bild Downloading... Lade herunter... Image (%1 x %2 %3%) Image (width x height zoom%) Bild (%1 x %2 %3%) Failed to download image! Kann Bild nicht herunterladen. Load Local Cover Lade lokales Cover File is already in list! Datei ist bereits in Liste. Failed to read image! Fehler beim Lesen des Bildes. Display Anzeige Searching... Suche... Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Name: Open In File Manager Öffne im Dateimanager Connection Established Verbindung hergestellt Connection Failed Verbindung fehlgeschlagen Grouped Albums Gruppierte Alben Table Tabelle Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Warteschlange Library Bibliothek Folders Ordner Playlists Wiedergabelisten Large Groß Small Klein Left Links Right Rechts Top Oben Bottom Unten System default Benutze Systemeinstellung Configure Cantata... Cantata einrichten... Quit Beenden About Cantata... Qt-only Über Cantata Show Window Zeige Fenster Server information... Serverinformationen ... Refresh Database Aktualisiere Datenbank Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Verbinde Collection Sammlung Outputs Ausgaben Stop After Track Stoppe nach Stück Add To Stored Playlist Hinzufügen zur gespeicherten Wiedergabeliste Add Stream URL Stream-URL hinzufügen Clear Leeren Expanded Interface Ansicht erweitern Show Current Song Information Zeige aktuelle Songinformation Full Screen Vollbild Random Zufall Repeat Wiederholen Single Einzeln When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Wenn 'Single' aktiviert ist, wird die Wiedergabe nach dem aktuell abgespielten Stück gestoppt oder wiederholt, falls 'Repeat' aktiviert ist. Consume Entfernen nach dem Abspielen When consume is activated, a song is removed from the play queue after it has been played. Wenn "Consume" aktiviert ist wird ein Stück von der Warteschlange entfernt, sobald es abgespielt wurde. Play Stream Spiele Stream ab Locate In Library Finde in Musikbibliothek Expand All Alles erweitern Collapse All Alles einklappen Devices Geräte Info Information &Edit &Bearbeiten &Settings &Einstellungen &Help &Hilfe Failed to locate any songs matching the dynamic playlist rules. Keine Stücke gefunden, welche auf den dynamischen Regeln der Wiedergabeliste entsprechen. Connecting to %1 Verbinde zu %1 Refresh MPD Database? Die MPD-Datenbank aktualisieren? About Cantata Qt-only Über Cantata Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Kontextansicht-Hintergründe mit freundlicher Genehmigung von <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Kontextansicht-Hintergründe mit freundlicher Genehmigung von<a href="http://www.wikipedia.org">Wikipedia</a> und <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Bitte laden Sie ihren eigenen Content zu <a href="http://www.fanart.tv">FanArt.tv</a> hoch. Server Information Serverinformationen Cantata (%1) Cantata (%1) Cantata Cantata Priority Priorität Enter priority (0..255): Eingabe der Priorität (0..255) Playlist Name Name der Wiedergabeliste Enter a name for the playlist: Bitte einen Namen für die Wiedergabeliste angeben Existing Playlist Wiedergabeliste existiert bereits Auto Automatisch <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Verbindung zu %1<br/>Die unten angezeigten Einträge beziehen sich auf die aktuelle MPD-Instanz.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Nicht verbunden.<br/>Einige Einträge können nicht bearbeitet werden solange Cantata nicht mit MPD verbunden wird.</i> Rename Umbenennen Remove Playlists Entferne Wiedergabelisten? Overwrite Playlist Wiedergabeliste überschreiben? Rename Playlist Wiedergabeliste umbenennen Enter new name for playlist: Bitte neuen Namen für die Wiedergabeliste angeben: 1 Track Ein Stück %1 Stücke %1 Tracks 1 Track (%2) Ein Stück (%2) %1 Stücke (%2) %1 Tracks (%2) 1 Album Ein Album %1 Alben %1 Albums 1 Artist Ein Künstler %1 Künstler %1 Artists 1 Stream Ein Stream %1 Streams %1 Streams 1 Entry Ein Eintrag %1 Einträge %1 Entries 1 Rule Eine Regel %1 Regeln %1 Rules 1 Podcast Ein Podcast %1 Podcasts %1 Podcasts 1 Episode Eine Episode %1 Episoden %1 Episodes Collection Settings Einstellungen zur Sammlung Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Wiedergabe Playback Settings Wiedergabeeinstellungen Interface Oberfläche Interface Settings Einstellungen zur Oberfläche Audio CD Settings Audio-CD-Einstellungen Proxy Proxy Proxy Settings Qt-only Proxyeinstellungen Shortcuts Qt-only Tastaturkürzel Keyboard Shortcut Settings Qt-only Tastaturkürzel-Einstellungen Cache Zwischenspeicher Cached Items Zwischengespeicherte Daten Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Einstellungen Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Genre: Date: Datum: No tracks found. Keine Texte gefunden. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Host: Add Collection Füge Sammlung hinzu Standard Standard Basic Einfach Delete Lösche New Collection %1 Neue Sammlung %1 Default Voreinstellung Previous Track Vorheriges Stück Next Track Nächstes Stück Play/Pause Abspielen/Pause Stop After Current Track Stoppe nach aktuellem Stück Increase Volume Lautstärke anheben Decrease Volume Lautstärke verringern Save As Speichern unter Add With Priority Hinzufügen mit Priorität Set Priority Setze Priorität Highest Priority (255) Höchste Priorität (255) High Priority (200) Hohe Priorität (200) Medium Priority (125) Mittlere Priorität (125) Low Priority (50) Niedrige Priorität (50) Default Priority (0) Normale Priorität (0) Custom Priority... Angepasste Priorität Add To Playlist Zur Wiedergabeliste hinzufügen Organize Files Dateien organisieren Set Image Setze Bild Add To Play Queue In Warteschlange einreihen Now playing Spiele jetzt Cue Sheet Cue Sheet Playlist Playlist Configure Device Geräteeinstellungen Refresh Device Aktualisiere Gerät Connect Device Mit Gerät verbinden Disconnect Device Vom Gerät trennen Edit CD Details Bearbeite CD-Details No Devices Attached Keine Geräte verbunden Not logged in Nicht eingeloggt Logged in Eingeloggt No subscriptions Keine Abonnements You do not have an active subscription Sie haben noch ein aktives Abonnement Logged in (expiry:%1) Eingeloggt seit (expiry:%1) Session expired Sitzung abgelaufen %1 by %2 Album by Artist %1 von %2 New Playlist... Neue Wiedergabeliste… Smart Playlist Intelligente Wiedergabeliste Length Länge Disc Disk Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Albumkünstler Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Titel TuneIn TuneIn ShoutCast ShoutCast Not Loaded Nicht geladen Loading... Lade... Bookmarks Lesezeichen IceCast IceCast Favorites Favoriten Bookmark Category Kategorie zu Lesezeichen hinzufügen Add Stream To Favorites Füge Stream den Favoriten hinzu Streams Streams Unknown Unbekannt "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed Verbindung zu %1 fehlgeschlagen Connection to %1 failed - incorrect password Verbindung zu %1 fehlgeschlagen – falsches Passwort Failed to send command to %1 - not connected Fehler beim Senden von Kommando zu %1 – nicht verbunden Failed to load. Please check user "mpd" has read permission. Fehler beim Laden. Bitte prüfen ob "mpd" Leseberechtigung besitzt. Failed to load. MPD can only play local files if connected via a local socket. Fehler beim Laden. MPD kann nur lokale Dateien abspielen, wenn eine Verbindung über einen lokalen Socket besteht. Failed to send command. Disconnected from %1 Fehler beim Senden des Kommandos. Verbindung zu %1 unterbrochen Failed to rename <b>%1</b> to <b>%2</b> Fehler beim Umbenennen von <b>%1</b> nach <b>%2</b> Failed to save <b>%1</b> Fehler beim Speichern von <b>%1</b> You cannot add parts of a cue sheet to a playlist! Sie können keine Teile eines Cue-Sheets zu einer Wiedergabeliste hinzufügen. You cannot add a playlist to another playlist! Sie können eine Wiedergabeliste nicht zu einer anderen Wiedergabeliste hinzufügen. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Einzelne Lieder Personal Persönlich Various Artists Verschiedene Künstler: No proxy Kein Proxy Use the system proxy settings Benutze Proxy-Einstellung des Systems Manual proxy configuration Manuelle Proxy-Einstellung Jamendo Settings Jamendo-Einstellungen MP3 MP3 Ogg Ogg Streaming format: Streaming-Format: Streaming Streaming MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Magnatune Settings Magnatune-Einstellungen Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Benutzername: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Kennwort: Membership: Mitgliedschaft: Downloads: Downloads: Failed to parse Fehler beim Analysieren Failed to download Fehler beim Herunterladen RSS: RSS: Website: Webseite: Podcast details Podcastdetails Failed to fetch podcasts from %1 Fehler beim Herunterladen des Podcasts von %1 URL URL Search %1 Suche %1 %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Lädt herunter: %1%) Add Subscription Abonnements hinzufügen Remove Subscription Abonnement entfernen Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Künstlerbilder 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Dunkler Hintergrund Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Zeige nur einfachen Wikipedia-Text Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Verfügbar: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Ausgewählt: Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Kopiere Titel von: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Einstellungen benötigt) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Kopiere Titel nach: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Zielformat: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Lieder überschreiben To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Zum Kopieren: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Album Details Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Jahr: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Disk: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Einzelner Künstler Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Album- und Stück-Informationen Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Zunächst schauen via: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) CDDB-Host: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) CDDB-Port: Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Audio-Extraktion Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Voller Paranoia-Modus (beste Qualität) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Niemals Lesefehler überspringen Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Musikverzeichnis: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Kopiere Albencovers als: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Maximale Größe des Albumcovers: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Voreinstellung der Lautstärke: Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Automatisch nach Musik suchen, wenn verbunden Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Benutze Zwischenspeicher Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Dateinamen Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Dateinamenschema VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) VFAT-sicher Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Nur ASCII-Zeichen verwenden Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Ersetze Leerzeichen mit Unterstrichen Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Umwandeln Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Nur umwandeln, wenn Quelldatei ein unterschiedliches Format besitzt Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Beispiel: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) Hinweis zum Dateinamenschema Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Albumname The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) Der Künstler der jeweiligen Stücke Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Künstler Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Name des Stückes (+Künstler) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Stücknummer. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Titel # CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Type: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Optionen Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Port: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Benutzer: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Domain: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Freigabe: Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Servicename: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Ordner: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Weitere Optionen: Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Name der dynamischen Regeln seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) Sekunden About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) Über Regeln Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Einbeziehung von Stücken, auf welche folgendes zutrifft: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Ausschluss von Stücken, auf welche folgendes zutrifft: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Künstler ähnlich zu: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Albumkünstler: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) Von Jahr: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Jegliches To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) Zu Jahr: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Exakter Treffer Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Speichere heruntergeladene Songtexte ins Musikverzeichnis Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Speichere heruntergeladene Hintergrundbilder ins Musikverzeichnis Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Cantata erster Start Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Willkommen bei Cantata <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Willkommen bei Cantata</p></body></html> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Standard Mehrbenutzer/Server-Konfiguration Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Einfache Konfiguration für Einzelnutzer Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Verbindungdetails The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Die folgenden Einstellungen sind die grundlegenden Einstellungen, welche Cantata benötigt. Bitte die nötigen Details eingeben und die „Verbinden“-Schaltfläche zum Testen der Verbindung drücken. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Musikverzeichnis Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Bitte wählen Sie den Ordner mit ihrem Musikverzeichnis. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Fertig. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>Achtung:</b> Sie sind aktuell nicht Mitglied der 'users'-Gruppe. Cantata wird besser funktionieren (Albencover speichern, Texte usw. mit den richtigen Berechtigungen) wenn Sie (oder der Systemverwalter) sich selbst dieser Gruppe hinzufügen. Wenn Sie sich selbst hinzufügen, müssen Sie sicherneut anmelden, damit die Änderungen aktiv werden. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Seitenleiste Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Stil: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Position: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Zeige nur Symbole, keinen Text Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Seitenleiste automatisch ausblenden Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Alben standardmäßig einklappen Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Automatisch aktuelles Album erweitern Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Zum aktuellen Stück blättern Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Aktuelles Albencover External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Externe Funktionen Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Zeige Hinweis beim Liedwechsel Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Zeige Symbol in der Systemleiste Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Beim Schließen in die Systemleiste minimieren Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Zeige Hauptfenster General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Allgemein Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Zeige Löschfunktion im Kontextmenü Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Erzwinge Einzelklick zur Aktivierung von Elementen Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Sprache: [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dynamik] Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Stoppe Wiedergabe beim Beenden Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Ausgabe s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) Über ReplayGain Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Sammlung: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Dateiname des Covers: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Dateiname (ohne Endung) zum Speichern der Cover.<br/>Sollte der Dateiname leer sein wird 'cover' benutzt<br/><br/><i>%artist% wird ersetzt durch den Albumkünstler des aktuellen Stückes und %album% wird ersetzt durch den Albumnamen.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) HTTP-Stream-URL: Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Modus: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy HTTP SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy SOCKS Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Dienst einrichten Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Status: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Logge ein You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Sie können auch hören ohne einen Account zu besitzen. Mit einem Premium-Konto können Sie aber in höherer Qualität und ohne Werbung hören. Besuchen Sie <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> um ihr Konto auf Premium aufzuwerten. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Premium-Konto Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Streamtyp: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Sitzung abgelaufen: Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Suche: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Tastaturkürzel für gewählte Aktion Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Voreinstellung: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Eigene Einstellung: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Albumkünstler: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Stücknummer: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Disk-Nummer: Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Ursprünglicher Name New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Neuer Name: Your names NAME OF TRANSLATORS Markus Slopianka Your emails EMAIL OF TRANSLATORS kamikazow@web.de Show All Tracks Zeige alle Stücke Show Untagged Tracks Zeige nicht getaggte Stücke Remove From List Von Liste entfernen Album Gain Album Gain Track Gain Track Gain Album Peak Album Peak Track Peak Track Peak Scan Untersuche Update ReplayGain tags in tracks? ReplayGain-Informationen in den Stücken aktualisieren? Update Tags Aktualisiere Tags Abort scanning of tracks? Überprüfung der Stücke abbrechen? Abort Abbrechen Abort reading of existing tags? Lesen der Dateien auf vorhandene Tags abbrechen? Do you wish to scan all tracks, or only tracks without existing tags? Wollen Sie alle Dateien untersuchen oder nur die ohne Tags? Untagged Tracks Nicht getaggte Stücke All Tracks Alle Stücke Scanning tracks... Untersuche Stücke... Reading existing tags... Lese vorhandene Tags... Failed to update the tags of the following tracks: Fehler beim aktualisieren der Tags aus den folgenden Stücken: Device is not connected. Gerät ist nicht verbunden. %1 dB %1 dB Failed Fehler Original: %1 Original: %1 Remove the selected tracks from the list? Das ausgewählte Stück von der Liste entfernen? Remove Tracks Stücke entfernen? Invalid service Ungültiger Dienst Invalid method Ungültige Methode Authentication failed Authentifizierung fehlgeschlagen Invalid format Ungültiges Format Invalid parameters Ungültige Parameter Invalid resource specified Ungültige Ressource angegeben Operation failed Operation fehlgeschlagen Invalid session key Ungültiger Sitzungsschlüssel Invalid API key Ungültiger API-Schlüssel Service offline Dienst offline %1 error: %2 %1 Fehler: %2 Digitally Imported Settings Einstellungen für Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Ausloggen URL: URL: Add Stream Stream hinzufügen Edit Stream Stream bearbeiten <i><b>ERROR:</b> Invalid protocol</i> <i><b>FEHLER:</b> Ungültiges Protokoll</i> Digitally Imported Digitally Imported Import Streams Into Favorites Stream importieren und den Favoriten hinzufügen Export Favorite Streams Favorisierte Streams exportieren Add New Stream To Favorites Füge neuen Stream zu den Favoriten hinzu Digitally Imported Service name Digitally Imported Import Streams Stream importieren XML Streams (*.xml *.xml.gz *.cantata) XML-Streams (*.xml *.xml.gz *.cantata) Export Streams Stream exportieren Bookmark added Lesezeichen hinzugefügt Already bookmarked Bereits in den Lesezeichen Already in favorites Existiert bereits in Favoriten Are you sure you wish to remove the %1 selected streams? Wollen Sie wirklich die gewählten %1 Streams entfernen? Are you sure you wish to remove '%1'? Wollen Sie wirklich '%1' entfernen? Configure Streams Konfiguriere Streams Cantata Streams (*.streams) Cantata-Streams (*.streams) &OK &OK &Cancel &Abbrechen &Yes &Ja &No &Nein &Discard &Verwerfen &Save &Speichern &Apply &Anwenden &Close S&chließen &Overwrite &Überschreiben: &Reset &Zurücksetzen &Continue &Fortsetzen &Delete &Löschen &Stop &Stopp &Remove &Entfernen &Previous &Vorheriges &Next &Nächstes Configure... Einstellungen… Password Kennwort Please enter password: Bitte Kennwort eingeben: Warning Warnung Question Frage Select Folder Gewählter Ordner Select File Wähle Datei %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags Tags Set 'Album Artist' from 'Artist' Setze 'Albumkünstler' von 'Künstler' All tracks Alle Stücke Apply "Various Artists" workaround to <b>all</b> tracks? Hinzufügen der „Verschiedene Künstler“ Problembehebung bei <b>allen</b> Stücken? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Dies wird den 'Albumkünstler' und 'Künstler' zu „Verschiedene Künstler“ gesetzt, und der 'Titel' zu "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Zurücksetzen der „Verschiedene Künstler“ Problembehebung bei <b>allen</b> Stücken? Revert "Various Artists" workaround Zurücksetzen der „Verschiedene Künstler“ Problembehebung <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Wenn der 'Albumkünstler' der gleiche ist wie 'Künstler' und der 'Titel' ist – im Format "TrackArtist - TrackTitle" –, wird der 'Künstler' von 'Titel' und der 'Titel' selbst nur zu Titel gesetzt.<br/><br/>Wenn z. B. 'Titel' "Wibble - Wobble" lautet, wird der 'Künstler' zu "Wibble" gesetzt und der 'Titel' zu "Wobble" gesetzt</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Setze 'Albumkünstler' mit 'Künstler' (wenn 'Albumkünstler' leer ist) bei <b>allen</b> Stücken? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Setze 'Albumkünstler' mit 'Künstler' (wenn 'Albumkünstler' leer ist)? Album Artist from Artist Setze Albumkünstler von Künstler Adjust the value of each track number by: Angleichen des Wertes einer jeden Stücknummer durch: All tracks [modified] Alle Stücke [geändert] %1 [modified] %1 [geändert] Would you also like to rename your song files, so as to match your tags? Möchten Sie Ihre Song Dateien so umbenennen, dass Sie zu Ihren Tags passen? Rename Files Dateien umbenennen Abort renaming of files? Umbenennen der Dateien abbrechen? <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right">Komponist:</td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Künstler:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Jahr:</b></td><td>%3</td></tr> All Genres Alle Genre Go Back Zurück Menu Menü (Stream) (Stream) Search... Suche... Close Search Bar Schließe Suchleiste Logged into %1 Angemeldet bei %1 <b>NOT</b> logged into %1 <b>NICHT</b> eingeloggt bei %1 Simple Tree Einfacher Dateibaum Detailed Tree Detaillierter Dateibaum List Liste (Various) (Verschiedene) Mute Stumm Volume %1% (Muted) Lautstärke %1% (Stumm) Volume %1% Lautstärke %1% 1 Track Singular Ein Stück %1 Tracks Plural (N!=1) %1 Stücke 1 Track (%1) Singular Ein Stück (%1) %1 Tracks (%2) Plural (N!=1) %1 Stücke (%2) 1 Album Singular Ein Album %1 Albums Plural (N!=1) %1 Alben 1 Artist Singular Ein Künstler %1 Artists Plural (N!=1) %1 Künstler 1 Stream Singular Ein Stream %1 Streams Plural (N!=1) %1 Streams 1 Entry Singular Ein Eintrag %1 Entries Plural (N!=1) %1 Einträge 1 Rule Singular Eine Regel %1 Rules Plural (N!=1) %1 Regeln 1 Podcast Singular Ein Podcast %1 Podcasts Plural (N!=1) %1 Podcasts 1 Episode Singular Eine Episode %1 Episodes Plural (N!=1) %1 Episoden ActionDialog Calculating size of files to be copied, please wait... Berechne Größe der zu kopierenden Dateien, bitte warten … Copy songs from: Kopiere Stücke von: Configure Konfigurieren (Needs configuring) (Einstellungen benötigt) Copy songs to: Kopiere Stücke nach: Destination format: Zielformat: Overwrite songs Stücke überschreiben To copy: Zu kopieren: <b>INVALID</b> <b>UNGÜLTIG</b> <i>(When different)</i> <i>(Wenn verschieden)</i> Artists:%1, Albums:%2, Songs:%3 Künstler:%1, Alben:%2, Stücke:%3 %1 free %1 frei Local Music Library Lokale Musikbibliothek Audio CD Audio-CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Auf dem Zielgerät ist nicht genügend Speicherplatz vorhanden. Die ausgewählten Stücke benötigen %1, aber es sind nur %2 frei. Die Stücke müssen in ein Format mit kleinerer Dateigröße umgewandelt werden, damit sie kopiert werden können. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Am Zielort ist nicht genügend Speicherplatz vorhanden. Die ausgewählten Stücke benötigen %1, aber es sind nur %2 frei. Copy Songs To Library Kopiere Stücke in die Bibliothek Copy Songs To Device Kopiere Stücke auf Gerät Copy Songs Kopiere Stücke Delete Songs Lösche Stücke You have not configured the destination device. Continue with the default settings? Du hast das Zielgerät nicht konfiguriert. Mit Standardeinstellungen fortfahren? Not Configured Nicht konfiguriert Use Defaults Benutze Standardeinstellung You have not configured the source device. Continue with the default settings? Du hast das Quellgerät nicht konfiguriert. Mit Standardeinstellungen fortfahren? Are you sure you wish to stop? Willst du wirklich stoppen? Stop Stopp Device has been removed! Gerät wurde entfernt. Device is not connected! Gerät ist nicht verbunden. Device is busy? Gerät aktuell beschäftigt? Device has been changed? Gerät wurde geändert? Clearing unused folders Bereinige ungenutzte Ordner Calculate ReplayGain for ripped tracks? Replay Gain der gerippten Stücke berechnen? ReplayGain Replay Gain Calculate Berechnen The destination filename already exists! Der Dateiname existiert bereits. Song already exists! Das Stück existiert bereits. Song does not exist! Das Stück existiert nicht. Failed to create destination folder!<br/>Please check you have sufficient permissions. Erstellen des Zielordners fehlgeschlagen.<br/>Bitte überprüfe deine Berechtigungen. Source file no longer exists? Quelldatei existiert nicht mehr? Failed to copy. Kopieren fehlgeschlagen. Failed to delete. Löschen fehlgeschlagen. Not connected to device. Keine Verbindung zum Gerät. Selected codec is not available. Der ausgewählte Codec ist nicht vorhanden. Transcoding failed. Umwandeln fehlgeschlagen. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Erstellen einer temporären Datei fehlgeschlagen.<br/>(Benötigt zum Umwandeln für MTP-Geräte.) Failed to read source file. Lesen der Quelldatei fehlgeschlagen. Failed to write to destination file. Erstellen der Zieldatei fehlgeschlagen. No space left on device. Auf dem Gerät ist kein Platz mehr verfügbar. Failed to update metadata. Aktualisieren der Metadaten fehlgeschlagen. Failed to download track. Herunterladen des Stücks fehlgeschlagen. Failed to lock device. Sperren des Geräts fehlgeschlagen. Local Music Library Properties Eigenschaften der lokalen Bibliothek Error Fehler Skip Überspringen Auto Skip Automatisch überspringen Retry Wiederholen Artist: Interpret: Album: Album: Track: Stück: Source file: Quelldatei: Destination file: Zieldatei: File: Datei: Saving cache Speichere Cache Calculating... Berechne... Time remaining: Verbleibende Zeit: AlbumDetails Album Details Album-Details Artist: Interpret: Composer: Komponist: Title: Titel: Genre: Genre: Year: Jahr: Disc: Disc: Single artist Einzelner Interpret Tracks Stücke Track Stück Artist Interpret Title Titel AlbumDetailsDialog Audio CD Audio-CD Apply "Various Artists" Workaround ‚Diverse Interpreten‘-Workaround anwenden Revert "Various Artists" Workaround ‚Diverse Interpreten‘-Workaround rückgängig machen Capitalize Große Anfangsbuchstaben Adjust Track Numbers Stücknummern anpassen Tools Werkzeuge Apply "Various Artists" workaround? ‚Diverse Interpreten‘-Workaround anwenden? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Dies setzt die Felder Album-Interpret und Interpret auf „Diverse Interpreten“ und den Titel auf ‚Stück-Interpret - Stück-Titel‘ Revert "Various Artists" workaround? ‚Diverse Interpreten‘-Workaround rückgängig machen? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Wo die Felder Album-Interpret und Interpret übereinstimmen und das Titel-Feld vom Format ‚Stück-Interpret - Stück-Titel‘ ist, wird das Interpreten-Feld vom Titel-Feld übernommen und das Titel-Feld wird auf den Stück-Titel gesetzt; z. B.: Ist das Titel-Feld„Wibble - Wobble“, so wird der Interpret auf „Wibble“ und der Titel auf „Wobble“ gesetzt. Revert Rückgängig machen Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Große Anfangsbuchstaben für die Felder Titel, Interpret, Album-Interpret und Album? Adjust track number by: Stücknummern anpassen um: AlbumView Refresh Album Information Album-Informationen aktualisieren Album Album Tracks Stücke ArtistView Refresh Artist Information Interpreten-Informationen aktualisieren Artist Interpret Albums Alben Web Links Weblinks Similar Artists Ähnliche Künstler AudioCdDevice Reading disc Lese CD %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) AudioCdSettings Album and Track Information Retrieval Album- und Stück-Informationen beziehen Initially look up via: Zunächst nachschlagen via: CDDB Host: CDDB-Host: CDDB Port: CDDB-Port: Lookup information as soon as CD is inserted Informationen nachschlagen, sobald CD eingelegt ist Audio Extraction Audio-Extraktion Full paranoia mode (best quality) Voller Paranoia-Modus (beste Qualität) Never skip on read error Bei Lesefehlern niemals überspringen CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Cue-Sheet Playlist Playlist CacheItem Deleting... Lösche … Calculating... Berechne … CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata speichert verschiedene Informationen (Coverbilder, Liedtexte etc.) in seinem Zwischenspeicher. Dies ist eine Übersicht zur aktuellen Zwischenspeichernutzung von Cantata. Covers Coverbilder Scaled Covers Skalierte Coverbilder Backdrops Hintergrundbilder Lyrics Liedtexte Artist Information Interpreten-Informationen Album Information Album-Informationen Track Information Stück-Informationen Stream Listings Stream-Listen Podcast Directories Podcast-Verzeichnisse Wikipedia Languages Wikipedia Scrobble Tracks Scrobbles Delete All Alles löschen Delete all '%1' items? Alle ‚%1‘-Einträge löschen? Delete Cache Items Zwischenspeicher-Einträge löschen Delete items from all selected categories? Einträge aller ausgewählten Kategorien löschen? CacheTree Name Name Item Count Anzahl der Einträge Space Used Belegter Speicherplatz CddbInterface Data Track Datentrack Failed to open CD device Öffnen des CD-Laufwerks fehlgeschlagen. Track %1 Stück %1 Failed to create CDDB connection CDDB-Verbindung fehlgeschlagen. Failed to contact CDDB server, please check CDDB and network settings Verbindung zum CDDB-Server fehlgeschlagen, bitte überprüfe deine CDDB- und Netzwerkeinstellungen. No matches found in CDDB Keine Treffer in CDDB. CDDB error: %1 CDDB-Fehler: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Es wurden mehrere passende Einträge gefunden. Bitte den relevanten auswählen: Artist Interpret Title Titel Disc Selection Disc-Auswahl %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 – %2, Disc %3 (%4) %1 - %2 (%3) artist - album (year) %1 – %2 (%3) ContextSettings Lyrics Providers Liedtext-Anbieter Wikipedia Languages Wikipedia Other Sonstiges ContextWidget &Artist &Interpret Al&bum &Album &Track &Stück CoverDialog Search Suche Add a local file Lokale Datei hinzufügen Configure Einstellungen This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. Hier kann die Datei gewählt werden, die als Coverbild verwendet wird; die Einstellung hat keinen Einfluss auf in die Audiodateien eingebettete Coverbilder. CoverArt Archive An image already exists for this artist, and the file is not writeable. Für diesen Interpreten existert bereits ein Bild, und die Datei ist nicht überschreibbar. A cover already exists for this album, and the file is not writeable. Für dieses Album existert bereits ein Coverbild, und die Datei ist nicht überschreibbar. '%1' Artist Image Bild für ‚%1‘ '%1 - %2' Album Cover 'Artist - Album' Album Cover Coverbild für ‚%1 – %2‘ Failed to set cover! Could not download to temporary file! Setzen des Coverbilds fehlgeschlagen. Konnte nicht in temporäre Datei herunterladen. Failed to download image! Herunterladen des Bilds fehlgeschlagen. Load Local Cover Lokales Coverbild laden Images (*.png *.jpg) Bilder (*.png *.jpg) File is already in list! Datei ist bereits in Liste. Failed to read image! Lesen der Bilddatei fehlgeschlagen. Display Anzeigen Remove Entfernen Failed to set cover! Could not make copy! Setzen des Coverbilds fehlgeschlagen. Konnte keine Kopie erstellen. Failed to set cover! Could not backup original! Setzen des Coverbilds fehlgeschlagen. Konnte keine Sicherungskopie des Originals erstellen. Failed to set cover! Could not copy file to '%1'! Setzen des Coverbilds fehlgeschlagen. Konnte die Datei nicht nach ‚%1‘ kopieren. Searching... Suche … CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right">Komponist:</td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Ausführender Künstler:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Interpret:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Jahr:</b></td><td>%3</td></tr> CoverPreview Image Bild Downloading... Lade herunter … Image (%1 x %2 %3%) Image (width x height zoom%) Bild (%1 × %2, %3 %) CustomActionDialog Name: Name: Command: Befehl: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. In obiger Kommandozeile wird %f durch die Dateiliste und %d durch die Ordnerliste ersetzt. Wird beides nicht angegeben, so wird hinten an den Befehl die Dateiliste angehängt. Add New Command Neuen Befehl hinzufügen Edit Command Befehl bearbeiten CustomActions Custom Actions Benutzerdefinierte Aktionen CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Um Cantata externe Befehle ausführen zu lassen (z. B. um mit einer anderen Anwendungen Tags zu bearbeiten), füge unten einen Eintrag für den Befehl hinzu. Wenn mindestens ein Befehl definiert ist, wird ein Eintrag ‚Benutzerdefinierte Aktionen‘ zum Kontextmenü in der Bibliothek, der Ordneransicht sowie der Wiedergabelistenansicht hinzugefügt. Add Hinzufügen Edit Bearbeiten Remove Entfernen Name Name Command Befehl Remove the selected commands? Ausgewählte Befehle entfernen? Device Updating (%1)... Aktualisiere (%1) … Updating (%1%)... Aktualisiere (%1 %) … DevicePropertiesDialog Device Properties Geräteeigenschaften DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Diese Einstellungen sind nur gültig und bearbeitbar, wenn das Gerät verbunden ist. Name: Name: Music folder: Musikverzeichnis: Copy album covers as: Kopiere Coverbilder als: Maximum cover size: Maximale Größe des Coverbilds: Default volume: Voreinstellung der Lautstärke: 'Various Artists' workaround ‚Diverse Interpreten‘-Workaround Automatically scan music when attached Automatisch nach Musik suchen, wenn verbunden Use cache Zwischenspeicher benutzen Filenames Dateinamen Filename scheme: Dateinamen-Schema: VFAT safe VFAT-sicher Use only ASCII characters Nur ASCII-Zeichen verwenden Replace spaces with underscores Leerzeichen durch Unterstriche ersetzen Append 'The' to artist names „The“ zu Interpretennamen hinzufügen If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Wenn ein Interpretenname mit „The“ beginnt, hänge dies im Ordnernamen hinten an, z. B. „The Beatles“ wird zu „Beatles, The“ Transcoding Umwandeln Only transcode if source file is of a different format Nur umwandeln, wenn Quelldatei ein anderes Format besitzt Only transcode if source is FLAC/WAV Don't copy covers Keine Coverbilder kopieren Embed cover within each file Coverbild in jede Datei einbetten No maximum size Keine maximale Größe 400 pixels 400 Pixel 300 pixels 300 Pixel 200 pixels 200 Pixel 100 pixels 100 Pixel <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Wenn beim Kopieren von Stücken auf ein Gerät der ‚Album-Interpret‘-Tag auf „Diverse Interpreten“ gesetzt ist, wird Cantata den ‚Interpret‘-Tag aller Stücke auf „Diverse Interpreten“ setzen und den ‚Titel‘-Tag auf ‚Interpret - Titel‘.<hr/> Beim Kopieren von einem Gerät wird Cantata prüfen, ob ‚Album-Interpret‘ und ‚Interpret‘ auf „Diverse Interpreten“ gesetzt ist. Sollte dies zutreffen, wird versucht, den eigentlichen Interpreten vom ‚Titel‘-Tag zu lesen und den Interpretennamen vom ‚Titel‘ Tag zu entfernen.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Wenn du dies aktivierst, wird Cantata einen Zwischenspeicher für die Musikbibliothek des Geräts erstellen. Dies wird künftiges Einlesen der Bibliothek beschleunigen (da der Zwischenspeicher verwendet wird, anstatt jeden Tag neu einzulesen).<hr/><b>Hinweis:</b> Wenn du ein anderes Programm zum Aktualisieren der Musikbibliothek verwendest, bleibt der Zwischenspeicher nicht aktuell. Um dies zu korrigieren, klicke einfach auf das ‚Aktualisieren‘-Symbol in der Geräteliste. Dadurch wird die aktuelle Zwischenspeicher-Datei entfernt und der Inhalt des Geräts neu eingelesen.</p> Do not transcode Nicht umwandeln Encoder Encoder Transcode to %1 In %1 umwandeln %1 (%2 free) name (size free) %1 (%2 frei) DevicesModel Configure Device Geräteeinstellungen Refresh Device Gerät aktualisieren Connect Device Gerät verbinden Disconnect Device Gerät trennen Edit CD Details CD-Details bearbeiten Not Connected Nicht verbunden No Devices Attached Keine Geräte verbunden DevicesPage Copy To Library In Bibliothek kopieren Synchronise Synchronisieren Forget Device Gerät vergessen Add Device Gerät hinzufügen Lookup album and track details? Alben- und Stück-Details nachschlagen? Refresh Aktualisieren Via CDDB Via CDDB Via MusicBrainz Via MusicBrainz Which type of refresh do you wish to perform? Welche Art von Aktualisierung möchtest du ausführen? Partial - Only new songs are scanned (quick) Partiell: nur neue Stücke werden eingelesen (schnell) Full - All songs are rescanned (slow) Vollständig: alle Stücke werden neu eingelesen (langsam) Partial Partiell Full Vollständig Are you sure you wish to delete the selected songs? This cannot be undone. Bist du sicher, dass du die ausgewählten Stücke löschen möchtest? Dies kann nicht rückgängig gemacht werden. Delete Songs Stücke löschen Are you sure you wish to forget '%1'? Bist du sicher, dass du ‚%1‘ vergessen möchtest? Are you sure you wish to eject Audio CD '%1 - %2'? Bist du sicher, dass du die Audio-CD ‚%1 – %2‘ auswerfen möchtest? Eject Auswerfen Are you sure you wish to disconnect '%1'? Bist du sicher, dass du ‚%1‘ trennen möchtest? Disconnect Trennen Please close other dialogs first. Bitte schließe zuerst die anderen Dialoge. DigitallyImported Not logged in Nicht eingeloggt Logged in Eingeloggt Unknown error Unbekannter Fehler No subscriptions Keine Abonnements You do not have an active subscription Du hast kein aktives Abonnement Logged in (expiry:%1) Eingeloggt (Ablauffrist:%1) Session expired Sitzung abgelaufen DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Du kannst umsonst hören, ohne ein Konto zu besitzen, aber Premium-Mitglieder können Streams in höherer Qualität und ohne Werbung hören. Besuche <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> für ein Premium-Konto. Premium Account Premium-Konto Username: Benutzername: Password: Kennwort: Stream type: Streamtyp: Status: Status: Login Einloggen Session expiry: Ablauffrist der Sitzung: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Diese Einstellungen gelten für Digitally Imported, JazzRadio.com, RockRadio.com und Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Wenn du Kontodaten eingibst, erscheint ein ‚DI‘-Statussymbol unter der Streamliste. Dadurch wird angezeigt, ob du eingeloggt bist oder nicht. Digitally Imported Settings Einstellungen für Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated Nicht authentifiziert Authenticating... Authentifiziere … Authenticated Authentifiziert Logout Ausloggen DockMenu Play Play Pause Pause Dynamic Start Dynamic Playlist Dynamische Wiedergabeliste starten Stop Dynamic Mode Dynamischen Modus stoppen Dynamic Playlists Dynamische Wiedergabelisten Dynamically generated playlists Dynamisch generierte Wiedergabelisten - Rating: %1..%2 – Bewertung: %1..%2 %n Rule(s) Eine Regel %n Regeln You need to install "perl" on your system in order for Cantata's dynamic mode to function. Du musst ‚perl‘ auf deinem System installieren, um den dynamischen Modus von Cantata benutzen zu können. Failed to locate rules file - %1 Finden der Regeldatei fehlgeschlagen – %1 Failed to remove previous rules file - %1 Entfernen der bestehenden Regeldatei fehlgeschlagen – %1 Failed to install rules file - %1 -> %2 Installieren der Regeldatei fehlgeschlagen – %1 → %2 Dynamizer has been terminated. Dynamizer wurde beendet. Awaiting response for previous command. (%1) Warte auf Antwort vom vorherigen Befehl. (%1) Saving rule Speichere Regel Deleting rule Lösche Regel Failed to save %1. (%2) Speichern von %1 fehlgeschlagen. (%2) Failed to delete rules file. (%1) Löschen der Regeldatei fehlgeschlagen. (%1) Failed to control dynamizer state. (%1) Setzen des Dynamizer-Status fehlgeschlagen. (%1) Failed to set the current dynamic rules. (%1) Setzen der dynamischen Regeln fehlgeschlagen. (%1) DynamicPage Add Hinzufügen Edit Bearbeiten Remove Entfernen Remote dynamizer is not running. Entfernter Dynamizer ist nicht aktiv. Are you sure you wish to remove the selected rules? This cannot be undone. Bist du sicher, dass du die ausgewählten Regeln entfernen möchtest? Dies kann nicht rückgängig gemacht werden. Remove Dynamic Rules Dynamische Regeln entfernen DynamicPlaylists Start Dynamic Playlist Stop Dynamic Mode Dynamic Playlists Dynamische Wiedergabelisten Dynamically generated playlists Dynamisch generierte Wiedergabelisten You need to install "perl" on your system in order for Cantata's dynamic mode to function. Failed to locate rules file - %1 Failed to remove previous rules file - %1 Failed to install rules file - %1 -> %2 Dynamizer has been terminated. Awaiting response for previous command. (%1) Saving rule Speichere Regel Deleting rule Lösche Regel Failed to save %1. (%2) Speichern von %1 fehlgeschlagen. (%2) Failed to delete rules file. (%1) Failed to control dynamizer state. (%1) Failed to set the current dynamic rules. (%1) DynamicPlaylistsPage Add Hinzufügen Edit Bearbeiten Remove Entfernen Remote dynamizer is not running. Entfernter Dynamizer ist nicht aktiv. Are you sure you wish to remove the selected rules? This cannot be undone. Bist du sicher, dass du die ausgewählten Regeln entfernen möchtest? Dies kann nicht rückgängig gemacht werden. Remove Dynamic Rules DynamicRule Type: Typ: Include songs that match the following: Stücke einbeziehen, auf welche folgendes zutrifft: Exclude songs that match the following: Stücke ausschließen, auf welche folgendes zutrifft: Artist: Interpret: Artists similar to: Interpreten ähnlich zu: Album Artist: Album-Interpret: Composer: Komponist: Album: Album: Title: Titel: Genre Genre From Year: Von Jahr: Any Egal To Year: Bis Jahr: Comment: Kommentar: Filename / path: Dateiname/Pfad Exact match Exakter Treffer Only enter values for the tags you wish to be search on. Gib nur für die Tags etwas ein, über die du suchen willst. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Schließe den Suchbegriff für das Genre mit einem Sternchen ab, um mehrere Genres zu finden; z. B: trifft ‚rock*‘ ‚Hard Rock‘ und ‚Rock and Roll‘. DynamicRuleDialog Dynamic Rule Dynamische Regel Add Hinzufügen <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>Fehler:</b> ‚Von Jahr‘ muss kleiner sein als ‚Bis Jahr‘</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>Fehler:</b> Zeitraum ist zu lang (kann nur maximal %1 Jahre betragen)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>Fehler:</b> Du kannst nur über Dateiname/Pfad suchen, wenn ‚exakter Treffer‘ <b>nicht</b> gewählt ist</i> DynamicRules Name of Dynamic Rules Name der dynamischen Regeln Add Hinzufügen Edit Bearbeiten Remove Entfernen Songs with ratings between: Stücke mit Bewertung zwischen: - Songs with duration between: Stücke mit Länge zwischen: seconds Sekunden About Rules Über Regeln DynamicRulesDialog Dynamic Rules Dynamische Regeln None Keine No Limit Keine Grenze About dynamic rules Über dynamische Regeln <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with 10 entries. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata wird deine Bibliothek nach allen gelisteten Regeln abfragen. Die Liste der <i>Einbeziehen</i>-Regeln wird verwendet, um eine Sammlung von Stücken zu generieren, die verwendet werden können. Die Liste der <i>Ausschließen</i>-Regeln wird verwendet, um eine Sammlung von Stücken zu generieren, die nicht verwendet werden können. Sind keine <i>Einschließen</i>-Regeln gesetzt, so wird Cantata annehmen, dass alle Stücke (außer denen nach <i>Ausschließen</i>-Regeln) verwendet werden können.</p><p>Um z. B. Cantata nach ‚Rock-Stücke von Wibble ODER Stücke von „Diverse Interpreten“‘ suchen zu lassen, bräuchtest du Folgendes: <ul><li>Einschließen Album-Interpret=Wibble Genre=Rock</li><li>Einschließen Album-Interpret=Diverse Interpreten</li></ul> Um Cantata nach Stücke von Wibble aber nicht aus dem Album Abc‘ such zu lassen, bräuchtest du Folgendes: <ul><li>Einschließen Album-Interpret=Wibble</li><li>Ausschließen Album-Interpret=Wibble Album=Abc</li></ul>Nachdem die Sammlung verwendbarer Stücke generiert worden ist, wird Cantata zufällig Stücke auswählen, um die aktuelle Wiedergabeliste stets mit 10 Einträgen gefüllt zu halten. Sind Grenzen für die Bewertung angegeben, so werden nur Stücke ausgewählt, deren Bewertung innerhalb dieser Grenzen liegt. Entsprechend, wenn Grenzen für die Stücklänge gesetzt sind.</p> Failed to save %1 Speichern von %1 fehlgeschlagen. A set of rules named '%1' already exists! Overwrite? Eine Regelliste mit dem Namen ‚%1‘ existiert bereits. Überschreiben? Overwrite Rules Regeln überschreiben Saving %1 Speichere %1 FancyTabWidget Configure... Einstellungen… FileSettings Save downloaded covers, artist, and composer images, in music folder Speichere heruntergeladene Coverbilder, Interpreten- und Komponistenbilder im Musikverzeichnis Save downloaded lyrics in music folder Speichere heruntergeladene Liedtexte im Musikverzeichnis Save downloaded backdrops in music folder Speichere heruntergeladene Hintergrundbilder im Musikverzeichnis If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Wenn du wählst, dass Cantata Coverbilder, Liedtexte oder Hintergrundbilder im Musikverzeichnis speichern soll, du aber keine Schreibrechte für dieses Verzeichnis hast, so wird Cantata die Dateien in deinem persönlichen Zwischenspeicherverzeichnis speichern. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata kann Hintergrund-, Interpreten- und Komponistenbilder nur dann in der Musikverzeichnis-Hierarchie speichern, wenn diese 2 Ebenen tief ist, d. h. ‚Interpret/Album/Stücke‘. FilenameSchemeDialog Example: Beispiel: About filename schemes Hinweis zum Dateinamenschema The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Der Interpret des Albums. Bei den meisten Alben ist dieser identisch mit <i>Stück-Interpret</i>. Bei Kompilationen ist er oft <i>Diverse Interpreten</i> bzw. <i>Various Artists</i>. Album Artist Album-Interpret The name of the album. Der Name des Albums. Album Title Albumtitel The composer. Der Komponist. Composer Komponist The artist of each track. Der Interpret des jeweiligen Stücks Track Artist Stück-Interpret The track title (without <i>Track Artist</i>). Der Titel des Stücks (ohne <i>Stück-Interpret</i>). Track Title Stücktitel The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Der Stücktitel (mit <i>Stück-Interpret</i>, sofern verschieden von <i>Album-Interpret</i>). Track Title (+Artist) Stücktitel (+ Interpret) The track number. Die Stücknummer. Track # Stück # The album number of a multi-album album. Often compilations consist of several albums. Die Albumnummer eines Multi-Album-Albums. Kompilationen bestehen oft aus mehreren Alben. CD # CD # The year of the album's release. Das Jahr der Veröffentlichung des Albums. Year Jahr The genre of the album. Das Genre des Albums. Genre Genre Filename Scheme Dateinamenschema Various Artists Example album artist Diverse Interpreten Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Die folgenden Variablen werden für jedes Stück durch ihre entsprechende Bedeutung ersetzt. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variable</em></th><th><em>Knopf</em></th><th><em>Beschreibung</em></th></tr> FolderPage Open In File Manager Im Dateimanager öffnen Are you sure you wish to delete the selected songs? This cannot be undone. Bist du sicher, dass du die ausgewählten Stücke löschen möchtest? Dies kann nicht rückgängig gemacht werden. Delete Songs Stücke löschen FsDevice Updating... Aktualisiere … Reading cache Lese Zwischenspeicher Saving cache Schreibe Zwischenspeicher %1 %2% Message percent %1 %2 % GenreCombo Filter On Genre Nach Genre filtern All Genres Alle Genres GroupedViewDelegate Audio CD Audio-CD Streams Streams %n Track(s) Ein Stück %n Stücke InitialSettingsWizard Cantata First Run Erster Start von Cantata Welcome to Cantata Willkommen bei Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata ist ein funktionsreicher, benutzerfreundlicher Client für Music Player Daemon (MPD). MPD ist eine flexible, mächtige Server-Anwendung für Musikwiedergabe.</p><p>Für mehr Informationen über MPD an sich wende dich bitte an die MPD-Website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Dieser ‚Wizard‘ wird dich durch die grundlegenden Einstellungen führen, die für ein Funktionieren von Cantata notwendig sind.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Willkommen bei Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>Cantata ist ein funktionsreicher, benutzerfreundlicher Client für Music Player Daemon (MPD). MPD ist eine flexible, mächtige Server-Anwendung für Musikwiedergabe. MPD kann entweder systemweit oder auf User-Basis gestartet werden.<br/><br/>Bitte wähle aus, wie Cantata die anfängliche Verbindung zu MPD herstellen (bzw. MPD starten) soll:</p> Standard multi-user/server setup Standard-Mehrbenutzer-/Server-Konfiguration <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>Wähle diese Option, wenn deine Musiksammlung zwischen Benutzern geteilt wird, deine MPD-Instanz auf einem anderen Rechner läuft, du bereits eine persönliche MPD-Konfiguration eingerichtet hast oder du den Zugriff von anderen Clients (z. B: MPDroid) ermöglichen möchtest. Wenn du diese Option wählst, kann Cantata selbst den MPD-Server nicht starten und stoppen. Du musst daher sicherstellen, dass MPD bereits eingerichtet ist und läuft.</i> Basic single user setup Einfache Einzelnutzer-Konfiguration <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>Wähle diese Option, wenn deine Musiksammlung nicht mit anderen geteilt wird und du möchtest, dass Cantata die MPD-Instanz einrichtet und kontrolliert. Diese Konfiguration ist dann exklusiv für Cantata und wird <b>nicht</b> für andere MPD-Clients (z. B. MPDroid) zugänglich sein.</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Wenn du eine ausgefeiltere MPD-Konfiguration (z. B. mehrere Audioausgänge, volle DSD-Unterstützung etc.) haben möchtest, <b>musst</b> du ‚Standard‘ wählen. For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Für mehr Informationen über MPD an sich wende dich bitte an die MPD-Website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Dieser ‚Wizard‘ wird dich durch die grundlegenden Einstellungen führen, die für ein Funktionieren von Cantata notwendig sind. Connection details Verbindungdetails The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Die folgenden Einstellungen sind die grundlegenden Einstellungen, welche Cantata benötigt. Bitte gib die nötigen Details ein und klicke die „Verbinden“-Schaltfläche, um die Verbindung zu testen. Host: Host: Password: Kennwort: Music folder: Musikverzeichnis: Connect Verbinden The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Die „Musikverzeichnis“-Einstellung wird verwendet, um Coverbilder, Liedtexte etc. nachzuschlagen. Wenn deine MPD-Instanz auf einem entfernten Rechner läuft, kannst du eine HTTP-URL angeben. Music folder Musikverzeichnis Please choose the folder containing your music collection. Bitte wähle das Verzeichnis, das deine Musiksammlung enthält. Covers and Lyrics Coverbilder und Liedtexte <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata wird fehlende Coverbilder und Liedtexte aus dem Internet herunterladen.</p><p>Bitte entscheide für beide jeweils, ob Cantata die entsprechenden Dateien im Musikverzeichnis oder in deinem persönlichen Zwischenspeicher-/Konfigurationsverzeichnis ablegen soll.</p> Save downloaded covers, artist, and composer images, in music folder Speichere heruntergeladene Cover-, Interpreten- und Komponistenbilder im Musikverzeichnis Save downloaded lyrics in music folder Speichere heruntergeladene Liedtexte im Musikverzeichnis Save downloaded backdrops in music folder Speichere heruntergeladene Hintergrundbilder im Musikverzeichnis If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Wenn du wählst, dass Cantata Coverbilder, Liedtexte oder Hintergrundbilder im Musikverzeichnis speichern soll, du aber keine Schreibrechte für dieses Verzeichnis hast, so wird Cantata die Dateien in deinem persönlichen Zwischenspeicherverzeichnis speichern. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata kann Hintergrund-, Interpreten- und Komponistenbilder nur dann in der Musikverzeichnis-Hierarchie speichern, wenn diese 2 Ebenen tief ist, d. h. ‚Interpret/Album/Stücke‘. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Das Musikverzeichnis ist auf eine HTTP-Adresse gesetzt und Cantata kann derzeit Dateien nicht auf externe HTTP-Server hochladen. Die obige Einstellung sollte daher nicht gesetzt werden. Finished! Fertig! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata ist nun eingerichtet!<br/><br/>Der Konfigurationsdialog von Cantata kann benutzt werden, um das Erscheinungsbild anzupassen, sowie um weitere MPD-Server hinzuzufügen, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantata wird Stücke zu Alben gruppieren, indem es den ‚Album-Interpret‘-Tag verwendet, sofern er gesetzt ist, und andernfalls den ‚Interpret‘-Tag verwenden. Falls du Alben mit mehreren Interpreten hast, <b>musst</b> du den ‚Album-Interpret‘-Tag setzen, damit das Gruppieren richtig funktioniert. Es empfiehlt sich, hier „Diverse Interpreten“ o. ä. zu setzen. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Achtung:</b> Du bist aktuell nicht Mitglied der ‚users‘-Gruppe. Cantata wird besser funktionieren (Albencover, Texte usw. mit den richtigen Berechtigungen speichern), wenn du (oder der Systemverwalter) dich dieser Gruppe hinzufügst. Wenn du dich selbst hinzufügst, musst du dich erneut anmelden, damit die Änderungen aktiv werden. Not Connected Nicht verbunden Connection Established Verbindung hergestellt Connection Failed Verbindung fehlgeschlagen Cantata will now terminate Cantata wird sich jetzt beenden InputDialog Password Kennwort Please enter password: Bitte Kennwort eingeben: InterfaceSettings Sidebar Seitenleiste Views Ansichten Use the checkboxes below to configure which views will appear in the sidebar. Verwende die Kästchen unten, um auszuwählen, welche Ansichten in der Seitenleiste erscheinen. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Wenn ‚Warteschlange‘ oben nicht ausgewählt ist, dann wir diese neben den anderen Ansichten angezeigt. Wenn ‚Info‘ nicht ausgewählt ist, dann wird der Werkzeugleiste eine Schaltfläche für Informationen über das laufende Stück hinzugefügt. Options Optionen Style: Stil: Position: Position: Only show icons, no text Zeige nur Symbole, keinen Text Auto-hide Automatisch ausblenden Play Queue Warteschlange Initially collapse albums Alben standardmäßig einklappen Automatically expand current album Automatisch aktuelles Album erweitern Scroll to current track Zum aktuellen Stück scrollen Prompt before clearing Vor dem Leeren nachfragen Separate action (and shortcut) for play queue search Suche in der Warteschlange als separate Aktion (und Tastenkrürzel) Background Image Hintergrundbild None Keins Current album cover Aktuelles Coverbild Custom image: Benutzerdefiniertes Bild: Blur: Unschärfe: 10px 10 px Opacity: Transparenz: 40% 40 % Toolbar Werkzeugleiste Show stop button ‚Stopp‘-Knopf anzeigen Show cover of current track Aktuelles Coverbild anzeigen Show track rating Bewertung des Stücks anzeigen External Extern Enable MPRIS D-BUS interface MPRIS-D-Bus-Schnittstelle aktivieren Show popup messages when changing tracks Bei Liedwechseln Benachrichtigungen einblenden Show icon in notification area Symbol in der Systemlesite anzeigen Minimize to notification area when closed Beim Schließen in die Systemleiste minimieren On Start-up Bei Programmstart Show main window Hauptfenster anzeigen Hide main window Hauptfenster verstecken Restore previous state Letzten Zustand wiederherstellen Tweaks Besondere Anpassungen Artist && Album Sorting Sortierung von Interpreten und Alben Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Gib eine (kommaseparierte) Liste von Präfixen an, die beim Sortieren ignoriert werden sollen. Ist z. B. ‚The‘ gesetzt, so wird ‚The Beatles‘ als ‚Beatles‘ einsortiert. Enter comma separated list of prefixes... Kommaseparierte Liste von Präfixen eingeben … Composer Support Unterstützung für den ‚Komponist‘-Tag By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Um Alben und Stücke zu gruppieren, verwendet Cantata standardmäßig den ‚Album-Interpret‘-Tag, falls dieser existert, und sonst den ‚Interpret‘-Tag. Für gewisse Genres, z. B. Klassische Musik, kann es wünschenswert sein, zum Gruppieren den ‚Komponist‘-Tag zu verwenden. Bitte gib eine (kommaseparierte) Liste von Genres an, für die Cantata den ‚Komponist‘-Tag verwenden soll. Enter comma separated list of genres... Kommaseparierte Liste von Genres eingeben … Single Tracks Einzelne Stücke If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Falls du in deiner Sammlung viele Interpreten mit nur einem Stück hast, kann es lästig sein, für jeden von diesen einen eigenen Eintrag in der Interpretenliste zu haben. Um das zu umgehen, kannst du solche Einzelstücke in einen gemeinsamen Ordner stecken und dessen Name unten angeben; Cantata gruppiert diese Stücke dann unter einem Album namens „Einzelne Stücke“ mit dem Album-Interpret „Diverse Interpreten“. Folder that contains single track files... Ordner mit Einzelstücken … CUE Files Cue-Dateien A cue file is a metadata file which describes how the tracks of a CD are laid out. Eine Cue-Datei ist eine Metadaten-Datei, die die Aufteilung der Stücke einer CD beschreibt. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. Die Veränderung obiger Einstellungen macht eine Aktualisierung der Datenbank (und möglicherweise einen Neustart von Cantata) notwendig, um wirksam zu werden. General Allgemein Fetch missing covers from Last.fm Fehlende Coverbilder von Last.fm herunterladen Show delete action in context menus Löschfunktion im Kontextmenü anzeigen Enforce single-click activation of items Einzelklick zur Aktivierung von Elementen erzwingen Show song information tooltips Informationen über Stücke als Tooltip anzeigen Language: Sprache: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Das Erzwingen der Einzelklick-Aktivierung macht einen Neustart von Cantata notwendig. Changing the language setting will require a re-start of Cantata. Das Ändern der Sprache macht einen Neustart von Cantata notwendig. Changing the style setting will require a re-start of Cantata. Library Bibliothek Folders Ordner Playlists Wiedergabelisten Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet – Streams, Jamendo, Maganatune, SoundCloud und Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Geräte – UMS, MTP (z. B. Android) und Audio-CDs Search (via MPD) Suche (via MPD) Info - Current song information (artist, album, and lyrics) Info – Informationen zum aktuellen Stück (Interpret, Album und Liedtext) Large Groß Small Klein Tab-bar Tableiste Left Links Right Rechts Top Oben Bottom Unten Images (*.png *.jpg) Bilder (*.png *.jpg) 10px pixels 10 px Notifications Benachrichtigungen English (en) English (en) System default Systemeinstellung %1% value% %1 % %1 px pixels %1 px ItemView Go Back Zurück Updating... Aktualisiere … JamendoService The world's largest digital service for free music Der größte Anbieter der Welt für digitale Musik JamendoSettingsDialog Jamendo Settings Jamendo-Einstellungen MP3 MP3 Ogg Ogg Streaming format: Streaming-Format: KeySequenceButton The key you just pressed is not supported by Qt. Die Taste, die du gerade gedrückt hast, wird nicht von Qt unterstützt. Unsupported Key Nicht unterstützte Taste KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Klicke auf die Schaltfläche und gib dann ein Tastenkürzel ein, wie du es sonst im Programm tun würdest. Beispiel für Strg+a: Halte Strg und drücke dann a. Meta Meta key Meta Ctrl Ctrl key Strg Alt Alt key Alt Shift Shift key Shift Input What the user inputs now will be taken as the new shortcut Eingabe None No shortcut defined Keins Shortcut Conflict Tastenkürzel-Konflikt The "%1" shortcut is already in use, and cannot be configured. Please choose another one. Das Tastenkürzel ‚%1‘ ist bereits in Verwendung und kann nicht gewählt werden. Bitte wähle ein anderes. The "%1" shortcut is ambiguous with the shortcut for the following action: Das Tastenkürzel ‚%1‘ ist bereits für die folgende Aktion in Verwendung: Do you want to reassign this shortcut to the selected action? Willst du das Tastenkürzel auf die gewählte Aktion neu setzen? Reassign Neu setzen LastFmEngine Read more on last.fm Mehr lesen auf Last.fm LibraryDb Database error - please check Qt SQLite driver is installed Datenbankfehler – bitte überprüfe, ob der Qt-SQLite-Treiber installiert ist. LibraryPage Show Artist Images Interpreten-Bilder anzeigen Sort Albums Alben sortieren Name Name Year Jahr Album, Artist, Year Album, Interpret, Jahr Album, Year, Artist Album, Jahr, Interpret Artist, Album, Year Interpret, Album, Jahr Artist, Year, Album Interpret, Jahr, Album Year, Album, Artist Jahr, Album, Interpret Year, Artist, Album Jahr, Interpret, Album Modified Date Änderungsdatum Group By Gruppieren nach Genre Genre Artist Interpret Album Album Are you sure you wish to delete the selected songs? This cannot be undone. Bist du sicher, dass du die ausgewählten Stücke löschen möchtest? Dies kann nicht rückgängig gemacht werden. Delete Songs Stücke löschen LyricSettings Choose the websites you want to use when searching for lyrics. Wähle die Websites zur Liedtext-Suche. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Wenn Cantata keine oder falsche Liedtexte gefunden hat, nutze diesen Dialog, um neue Suchdaten einzugeben. Zum Beispiel könnte das aktuelle Stück eine Coverversion sein – dann hilft vielleicht die Suche nach dem Originalkünstler. Wenn die Suche einen neuen Liedtext findet, dann wird dieser mit dem ursprünglichen Interpreten und Titel, wie sie von Cantata angezeigt werden, assoziiert. Title: Titel: Artist: Interpret: Search For Lyrics Nach Liedtext suchen MPDConnection Unknown Unbekannt Connection to %1 failed Verbindung zu %1 fehlgeschlagen Connection to %1 failed - please check your proxy settings Verbindung zu %1 fehlgeschlagen – bitte überprüfe deine Proxy-Einstellungen Connection to %1 failed - incorrect password Verbindung zu %1 fehlgeschlagen – falsches Kennwort Connecting to %1 Verbinde zu %1 Failed to send command to %1 - not connected Senden des Befehls zu %1 fehlgeschlagen – nicht verbunden Failed to load. Please check user "mpd" has read permission. Laden fehlgeschlagen. Bitte überprüfe, ob der Benutzer „mpd“ Leserechte besitzt. Failed to load. MPD can only play local files if connected via a local socket. Laden fehlgeschlagen. MPD kann nur lokale Dateien abspielen, wenn eine Verbindung über einen lokalen Socket besteht. MPD reported the following error: %1 MPD meldet folgenden Fehler: %1 Failed to send command. Disconnected from %1 Senden des Befehls fehlgeschlagen. Verbindung zu %1 unterbrochen. Failed to rename <b>%1</b> to <b>%2</b> Umbenennen von <b>%1</b> zu <b>%2</b> fehlgeschlagen. Failed to save <b>%1</b> Speichern von <b>%1</b> fehlgeschlagen. You cannot add parts of a cue sheet to a playlist! Du kannst nicht Teile eines Cue-Sheets zu einer Wiedergabeliste hinzufügen. You cannot add a playlist to another playlist! Du kannst eine Wiedergabeliste nicht zu einer anderen Wiedergabeliste hinzufügen. Failed to send '%1' to %2. Please check %2 is registered with MPD. Senden von ‚%1‘ zu %2 fehlgeschlagen. Bitte stelle sicher, dass %2 bei MPD registriert ist. Cannot store ratings, as the 'sticker' MPD command is not supported. Bewertung kann nicht gespeichert werden, da der MPD-Befehl ‚sticker‘ nicht unterstützt wird. MagnatuneService None Keine Streaming Streaming MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com Online-Musik von magnatune.com MagnatuneSettingsDialog Magnatune Settings Magnatune-Einstellungen Username: Benutzername: Password: Kennwort: Membership: Mitgliedschaft: Downloads: Downloads: MainWindow [Dynamic] [Dynamisch] Exit Full Screen Vollbild verlassen Configure Cantata... Cantata einrichten … Preferences Einstellungen Quit Beenden About Cantata... Über Cantata … Show Window Zeige Fenster Server information... Serverinformationen … Refresh Database Datenbank aktualisieren Refresh Aktualisieren Connect Verbinden Collection Sammlung Outputs Ausgaben Stop After Track Stoppe nach Stück Seek forward (%1 seconds) Im Stück vorwärts springen (%1 Sekunden) Seek backward (%1 seconds) Im Stück rückwärts springen (%1 Sekunden) Add To Stored Playlist Zu gespeicherter Wiedergabeliste hinzufügen Crop Others Alles außer Auswahl entfernen Add Stream URL Stream-URL hinzufügen Clear Leeren Center On Current Track Auf aktuelles Stück zentrieren Expanded Interface Erweiterte Ansicht Show Current Song Information Zeige Informationen zum aktuellen Stück Full Screen Vollbild Random Zufall Repeat Wiederholen Single Einzeln When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Wenn ‚Einzeln‘ aktiviert ist, wird die Wiedergabe nach dem aktuell abgespielten Stück gestoppt, oder wiederholt, falls ‚Wiederholen‘ aktiviert ist. Consume Verkonsumieren When consume is activated, a song is removed from the play queue after it has been played. Wenn ‚Verkonsumieren‘ aktiviert ist, wird ein Stück von der Warteschlange entfernt, sobald es abgespielt wurde. Find in Play Queue In Warteschlage suchen Play Stream Stream abspielen Locate In Library In Musikbibliothek lokalisieren Play next Als nächstes abspielen Edit Track Information (Play Queue) Informationen zum Stück bearbeiten (Warteschlange) Expand All Alles erweitern Collapse All Alles einklappen Cancel Abbrechen Play Queue Warteschlange Library Bibliothek Folders Ordner Playlists Wiedergabelisten Internet Internet Devices Geräte Search Suche Info Info Show Menubar Menüleiste anzeigen &Music &Musik &Edit &Bearbeiten &View &Ansicht &Queue &Warteschlange &Settings &Einstellungen &Help &Hilfe Set Rating Bewertung setzen No Rating Keine Bewertung Failed to locate any songs matching the dynamic playlist rules. Keine Stücke gefunden, die den Regeln der dynamischen Wiedergabeliste entsprechen. Connecting to %1 Verbinde zu %1 Refresh MPD Database? MPD-Datenbank aktualisieren? About Cantata Über Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>MPD-Client.<br/><br/>&copy; 2011–2017 Craig Drummond<br/>Veröffentlicht unter der <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Basiert auf <a href="http://lowblog.nl">QtMPC</a> – &copy; 2007–2010 Die QtMPC-Autoren<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Kontextansicht-Hintergründe mit freundlicher Genehmigung von <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Kontextansicht-Hintergründe mit freundlicher Genehmigung von<a href="http://www.wikipedia.org">Wikipedia</a> und <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Lade eigene Musik-Fan-Art bei <a href="http://www.fanart.tv">FanArt.tv</a> hoch! A Podcast is currently being downloaded Quiting now will abort the download. Es wird gerade ein Podcast heruntergeladen. Wenn du jetzt das Programm beendest, wird der Download abgebrochen. Abort download and quit Download abbrechen und beenden Please close other dialogs first. Bitte schließe zuerst die anderen Dialoge. Enabled: %1 Aktiviert: %1 Disabled: %1 Deaktiviert: %2 Server Information Serverinformationen <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protokoll:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Wiedergabezeit:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handler:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Datenbank</b></td></tr><tr><td align="right">Interpreten:&nbsp;</td><td>%1</td></tr><tr><td align="right">Alben:&nbsp;</td><td>%2</td></tr><tr><td align="right">Stücke:&nbsp;</td><td>%3</td></tr><tr><td align="right">Dauer:&nbsp;</td><td>%4</td></tr><tr><td align="right">Aktualisiert:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD meldet folgenden Fehler: %1 Cantata Cantata Playback stopped Wiedergabe gestoppt Remove all songs from play queue? Alle Stücke aus der Warteschlange entfernen? Priority Priorität Enter priority (0..255): Priorität eingeben (0..255) Decrease priority for each subsequent track Playlist Name Name der Wiedergabeliste Enter a name for the playlist: Gib einen Namen für die Wiedergabeliste ein: '%1' is used to store favorite streams, please choose another name. ‚%1‘ wird für Stream-Favoriten verwendet, bitte wähle einen anderen Namen. A playlist named '%1' already exists! Add to that playlist? Eine Wiedergabeliste mit dem Namen ‚%1‘ existiert bereits. Zu dieser hinzufügen? Existing Playlist Existierende Wiedergabeliste %n Track(s) Ein Stück %n Stücke %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) MenuButton Menu Menü MessageOverlay Cancel Abbrechen Mpris (Stream) (Stream) MtpConnection Connecting to device... Verbinde mit Gerät … No devices found Keine Geräte gefunden Connected to device Mit Gerät verbunden Disconnected from device Vom Gerät getrennt Updating folders... Aktualisiere Ordner … Updating files... Aktualisiere Dateien... Updating tracks... Aktualisiere Stücke … MtpDevice Not Connected Nicht verbunden %1 free %1 frei MusicBrainz Failed to open CD device Öffnen des CD-Laufwerkes fehlgeschlagen Track %1 Stück %1 %1 (Disc %2) %1 (Disc %2) No matches found in MusicBrainz Keine Treffer bei MusicBrainz gefunden MusicLibraryModel Cue Sheet Cue-Sheet Playlist Wiedergabeliste %n Track(s) Ein Stück %n Stücke %n Artist(s) Ein Interpret %n Interpreten %n Album(s) Ein Album %n Alben %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) %1 by %2 Album by Artist %1 von %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>HINWEIS:</b> %1</i> NowPlayingWidget (Stream) (Stream) OSXStyle &Window &Fenster Close Schließen Minimize Minimieren Zoom Zoom OnlineDbService Downloading...%1% Lade herunter … %1 % Parsing music list.... Verarbeite Musikliste … Failed to download Herunterladen fehlgeschlagen %n Artist(s) Ein Interpret %n Interpreten OnlineDbWidget Group By Gruppieren nach Genre Genre Artist Interpret Configure Konfigurieren The music listing needs to be downloaded, this can consume over %1Mb of disk space Die Musikliste muss heruntergeladen werden; dies kann bis zu %1 MB Speicherplatz benötigen. Dowload music listing? Musikliste herunterladen? Download Herunterladen Re-download music listing? Musikliste neu herunterladen? OnlineSearchService Searching... Suche … OnlineSearchWidget No tracks found. Keine Stücke gefunden. %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Verwende die Kästchen unten, um die Liste aktiver Dienste zu konfigurieren. Configure Service Dienst einrichten OnlineView Song Information Informationen zum Stück OnlineXmlParser Failed to parse Verarbeiten fehlgeschlagen OpmlBrowsePage Reload Neu laden Failed to download directory listing Herunterladen des Verzeichnisses fehlgeschlagen Failed to parse directory listing Verarbeiten des Verzeichnisses fehlgeschlagen OtherSettings Background Image Hintergrundbild None Keins Artist image Interpretenbild Custom image: Benutzerdefiniertes Bild: Blur: Unschärfe: 10px 10 px Opacity: Transparenz: 40% 40 % Automatically switch to view after: Automatisch zur Info-Ansicht wechseln nach: Do not auto-switch Nicht automatisch wechseln ms ms Dark background Dunkler Hintergrund Darken background, and use white text, regardless of current color palette. Hintergrund abdunkeln und weißen Text verwenden unabhängig von aktueller Farbpalette. Always collapse into a single pane Immer nur eine Info-Kategorie anzeigen Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Nur ‚Interpret‘-, ‚Album‘- oder ‚Stück‘-Informationen anzeigen, auch wenn genug Platz für alle drei wäre. Only show basic wikipedia text Vereinfachten Wikipedia-Text anzeigen Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantata zeigt nur eine gestutzte Version der Wikipedia-Artikel (keine Bilder, Links etc.). Diese Stutzung is nicht immer 100 % akkurat, weshalb Cantata standardäßig nur den Einleitungsabschnitt anzeigt. Wenn du die Anzeige des vollen Artikels auswählst, kann es zu Verarbeitungsfehlern kommen; außerdem musst du zwischengespeicherte Artikel entfernen (über den ‚Zwischenspeicher‘-Dialog). Images (*.png *.jpg) Bilder (*.png *.jpg) 10px pixels 10 px %1% value% %1 % %1 px pixels %1 px PathRequester Select Folder Ordner wählen Select File Datei wählen PlayQueueModel Title Titel Artist Interpret Album Album # Track number # Length Länge Disc Disc Year Jahr Original Year Original-Jahr Genre Genre Priority Priorität Composer Komponist Performer Ausführender Künstler Rating Bewertung Remove Duplicates Duplikate entfernen Undo Rückgängig Redo Rückgängig rückgängig Shuffle Durchmischen Tracks Stücke Albums Alben Sort By Sortieren nach Album Artist Album-Interpret Track Title Stücktitel Track Number Stücknummer # (Track Number) # (Stücknummer) PlayQueueView Remove Entfernen PlaybackSettings Playback Wiedergabe Fa&deout on stop: Beim Stoppen aus&blenden None Deaktiviert ms ms Stop playback on exit Beim Beenden Wiedergabe stoppen Inhibit suspend whilst playing Während der Wiedergabe Ruhemodus unterbinden If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Wenn du den Stopp-Knopf klickst und hältst, erscheint ein Menü, in dem du auswählen kannst, ob du gleich stoppen willst oder nach dem laufenden Stück. (Der Stopp-Knopf kann im Anschnitt ‚Oberfläche/Werkzeugleiste‘ aktiviert werden.) Output Ausgabe <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Nicht verbunden.<br/>Unten stehende Einträge können nicht bearbeitet werden, solange Cantata nicht mit MPD verbunden ist.</i> &Crossfade between tracks: Zwischen Stücken &überblenden: s s Replay &gain: Replay &Gain About replay gain Über Replay Gain Use the checkboxes below to control the active outputs. Verwende die Kästchen unten, um die aktiven Ausgaben zu bestimmen. Track Stück Album Album Auto Automatisch <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Verbunden zu %1.<br/>Die unten angezeigten Einträge beziehen sich auf die aktuelle MPD-Instanz.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gain ist ein 2001 vorgeschlagener Standard für die Normalisiierung der wahrgenommenen Lautstärke von Computer-Audioformaten wie MP3 und Ogg Vorbis. Es arbeitet auf Stück/Album-Basis und wird heute von einer wachsenden Anzahl an Playern unterstützt.<br/><br/>Folgende Replay-Gain-Einstellungen stehen zur Auswahl:<ul><li><i>Deaktiviert</i> – Replay Gain wird nicht verwendet.</li><li><i>Stück</i> – Die Lautstärke wird entsprechend den Replay-Gain-Tags des Stücks angepasst.</li><li><i>Album</i> – Die Lautstärke wird entsprechend desn Replay-Gain-Tags des Albums angepasst.</li><li><i>Auto</i> – Die Lautstärke wird entsprechend den Replay-Gain-Tags des Stücks angepasst, wenn Zufallswiedergabe aktiv ist, und sonst entsprechend desn Replay-Gain-Tags des Albums. PlaylistRule Type: Include songs that match the following: Exclude songs that match the following: Artist: Artists similar to: Album Artist: Composer: Komponist: Album: Album: Title: Titel: Genre Genre From Year: Von Jahr: Any To Year: Comment: Kommentar: Filename / path: Dateiname/Pfad Exact match Exakter Treffer Only enter values for the tags you wish to be search on. Gib nur für die Tags etwas ein, über die du suchen willst. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Schließe den Suchbegriff für das Genre mit einem Sternchen ab, um mehrere Genres zu finden; z. B: trifft ‚rock*‘ ‚Hard Rock‘ und ‚Rock and Roll‘. PlaylistRuleDialog Dynamic Rule Dynamische Regel Smart Rule Add Hinzufügen <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>Fehler:</b> Du kannst nur über Dateiname/Pfad suchen, wenn ‚exakter Treffer‘ <b>nicht</b> gewählt ist</i> PlaylistRules Name of Dynamic Rules Name der dynamischen Regeln Add Hinzufügen Edit Bearbeiten Remove Entfernen Songs with ratings between: Stücke mit Bewertung zwischen: - Songs with duration between: Stücke mit Länge zwischen: seconds Number of songs in play queue: Order songs: About Rules Über Regeln PlaylistRulesDialog Dynamic Rules Dynamische Regeln Smart Rules No Limit Keine Grenze Ascending Descending Name of Smart Rules Number of songs About dynamic rules Über dynamische Regeln <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 Speichern von %1 fehlgeschlagen. {1?} A set of rules named '%1' already exists! Overwrite? Eine Regelliste mit dem Namen ‚%1‘ existiert bereits. Überschreiben? Overwrite Rules Saving %1 Speichere %1 PlaylistsModel New Playlist... Neue Wiedergabeliste … Stored Playlists Gespeicherte Wiedergabelisten Standard playlists Standard-Wiedergabelisten %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) Smart Playlist Intelligente Wiedergabeliste Plurals %n Track(s) Ein Stück %n Stücke %n Tracks (%1) Ein Stück (%1) %n Tracks (%1) %n Album(s) Ein Album %n Alben %n Artist(s) Ein Künstler %n Künstler %n Stream(s) Ein Stream %n Streams %n Entry(s) Ein Eintrag %n Einträge %n Rule(s) Eine Regel %n Regeln %n Podcast(s) Ein Podcast %n Podcasts %n Episode(s) Eine Episode %n Episoden %n Update(s) available 1 Update available %n Updates available PodcastPage RSS: RSS: Website: Website: Podcast details Podcast-Details Select a podcast to display its details Wähle einen Podcast, um seine Details anzuzeigen PodcastSearchDialog Subscribe Abonnieren Enter URL URL eingeben Manual podcast URL Manuelle Podcast-URL Search %1 Suche %1 Search for podcasts on %1 Suche nach Podcasts auf %1 Add Podcast Subscription Podcast-Abonnement hinzufügen Browse %1 %1 durchsehen Browse %1 podcasts ‚%1‘-Podcasts durchsehen You are already subscribed to this podcast! Du hast diesen Podcast bereits abonniert. Subscription added Abonnement hinzugefügt. PodcastSearchPage Enter search term... Suchbegriff eineben … Search Suchen Failed to fetch podcasts from %1 Herunterladen des Podcasts von %1 fehgeschlagen There was a problem parsing the response from %1 Es gab ein Problem beim Verarbeiten der Antwort von %1 PodcastService Subscribe to RSS feeds RSS-Feeds abonnieren %n Podcast(s) Ein Podcast %n Podcasts %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) Eine Episode %n Episoden (Downloading: %1%) (Lädt herunter: %1 %) Failed to parse %1 Verarbeiten von %1 fehlgeschlagen Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata unterstützt nur Audio-Podcasts; %1 enthält aber nur Video-Podcasts. Failed to download %1 Herunterladen von %1 fehlgeschlagen. PodcastSettingsDialog Check for new episodes: Auf neue Episoden prüfen: Download episodes to: Episoden herunterladen nach: Download automatically: Automatisch herunterladen: Podcast Settings Podcast-Einstellungen Manually Manuell Every 15 minutes Alle 15 Minuten Every 30 minutes Alle 30 Minuten Every hour Jede Stunde Every 2 hours Alle 2 Stunden Every 6 hours Alle 6 Stunden Every 12 hours Alle 12 Stunden Every day Jeden Tag Every week Jede Woche Don't automatically download episodes Episoden nicht automatisch herunterladen Latest episode Letzte Episode Latest %1 episodes Letzte %1 Episoden All episodes Alle Episoden PodcastUrlPage URL URL Enter podcast URL... Podcast-URL eingeben … Load Laden Enter podcast URL below, and press 'Load' Gib unten eine Podcast-URL ein und klicke ‚Laden‘ Invalid URL! Ungültige URL. Failed to fetch podcast! Abrufen des Podcasts fehlgeschlagen. Failed to parse podcast. Verarbeiten des Podcasts fehlgeschlagen. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata unterstützt nur Audio-Podcasts; die eingegebene URL enthält aber nur Video-Podcasts. PodcastWidget Add Subscription Abonnement hinzufügen Remove Subscription Abonnement entfernen Download Episodes Episoden herunterladen Delete Downloaded Episodes Heruntergeladene Episoden löschen Cancel Download Download abbrechen Mark Episodes As New Episoden als neu markieren Mark Episodes As Listened Episoden als gehört markieren Show Unplayed Only Nur ungehörte anzeigen Unsubscribe from '%1'? ‚%1‘-Abonnement entfernen? Do you wish to download the selected podcast episodes? Willst du die ausgewählten Podcast-Episoden herunterladen? Cancel podcast episode downloads (both current and any that are queued)? Downloads von Podcast-Episoden abbrechen (sowohl laufende als auch ausstehende)? Do you wish to the delete downloaded files of the selected podcast episodes? Willst du die heruntergeladenen Dateien der ausgewählten Podcast-Episoden löschen? Do you wish to mark the selected podcast episodes as new? Willst du die ausgewählten Podcast-Episoden als neu markieren? Do you wish to mark the selected podcast episodes as listened? Willst du die ausgewählten Podcast-Episoden als gehört markieren? Refresh all subscriptions? Alle Abonnements aktualisieren? Refresh Aktualisieren Refresh All Alle aktualisieren Refresh all subscriptions, or only those selected? Alle Abonnements aktualisieren, oder nur die ausgewählten? Refresh Selected Ausgewählte aktualisieren PowerManagement Cantata is playing a track Cantata spielt ein Stück ab PreferencesDialog Collection Sammlung Collection Settings Einstellungen zur Sammlung Playback Wiedergabe Playback Settings Einstellungen zur Wiedergabe Downloaded Files Heruntergeladene Dateien Downloaded Files Settings Einstellungen zu heruntergeladenen Dateien Interface Oberfläche Interface Settings Einstellungen zur Oberfläche Info Info Info View Settings Einstellungen zur Info-Ansicht Scrobbling Scrobbling Scrobbling Settings Scrobbling-Einstellungen Audio CD Audio-CD Audio CD Settings Audio-CD-Einstellungen Proxy Proxy Proxy Settings Proxyeinstellungen Shortcuts Tastaturkürzel Keyboard Shortcut Settings Tastaturkürzel-Einstellungen Cache Zwischenspeicher Cached Items Zwischengespeicherte Daten Custom Actions Benutzerdefinierte Aktionen Cantata Preferences Cantata-Einstellungen Configure Einstellungen ProxySettings Mode: Modus: Type: Typ: HTTP Proxy HTTP-Proxy SOCKS Proxy SOCKS-Proxy Host: Host: Port: Port: Username: Benutzername: Password: Kennwort: No proxy Kein Proxy Use the system proxy settings Proxy-Einstellung des Systems verwenden Manual proxy configuration Manuelle Proxy-Einstellung QObject Track listing Stückliste Read more on wikipedia Mehr lesen auf Wikipedia Open in browser Im Browser öffnen <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://de.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding </a> (AAC) ist ein patentierter verlustbehafteteter Audio-Codec.<br> AAC besitzt in der Regel eine bessere Klangqualität als MP3 bei gleicher Bitrate. Er ist eine zumutbare Wahl für den iPod und manche andere tragbare Musik-Player. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Die Bitrate ist ein Maß für die Menge an Daten, die verwendet wird, um eine Sekunde des Stückes darzustellen.<br>Der <b>AAC</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>variable Bitrate (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abschnitte werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist daher nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>150 kb/s</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>120 kb/s</ b> könnte nicht zufriedenstellend sein, alles über <b>200 kb/s</b> ist wahrscheinlich übertrieben. Expected average bitrate for variable bitrate encoding Erwartete durchschnittliche Bitrate für die Kodierung mit variabler Bitrate Smaller file Kleinere Datei Better sound quality Bessere Klangqualität <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://de.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</ a> (MP3) ist ein patentierter Audio-Codec mit verlustbehafteter Kompression.<br> Trotz seiner Mängel ist MP3 ein weit verbreitetes Format zum Speichern von Audiodateien und wird von praktisch allen tragbaren Musik-Playern unterstützt. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Die Bitrate ist ein Maß für die Menge an Daten, die verwendet wird, um eine Sekunde des Stückes darzustellen.<br>Der <b>MP3</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>variable Bitrate (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abschnitte werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist daher nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>160 kb/s</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>120 kb/s</ b> könnte nicht zufriedenstellend sein, alles über <b>200 kb/s</b> ist wahrscheinlich übertrieben. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://de.wikipedia.org/wiki/Vorbis>Ogg Vorbis </a> ist ein offener und lizenzfreier Audio-Codec für verlustbehaftete Audio-Kompression.<br>Er produziert kleinere Dateien als MP3 bei gleicher oder höherer Qualität. Ogg Vorbis ist eine rundum ausgezechnete Wahl, besonders für tragbare Musik-Player, die den Codec unterstützen. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Die Bitrate ist ein Maß für die Menge an Daten, die verwendet wird, um eine Sekunde des Stückes darzustellen.<br>Der <b>Vorbis</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>variable Bitrate (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abschnitte werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Der Vorbis-Encoder verwendet eine Skala von 1 bis 10, um eine erwartete Klangqualiät zu bestimmen. Die Bitraten-Einstellung mit dem Schieberegler ist nur eine grobe Schätzung (von Vorbis) der durchschnittlichen Bitrate bei gegebenem Qualitätswert. Bei neueren Vorbis-Versionen ist die resultierende Bitrate tatsächlich noch niedriger.<br/><b>5</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>3</ b> könnte nicht zufriedenstellend sein, alles über <b>8</b> ist wahrscheinlich übertrieben. Quality rating Qualität Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://de.wikipedia.org/wiki/Opus_(Audioformat)>Opus</a> ist ein patentfreier verlustbehafteter Audio-Codec. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Die Bitrate ist ein Maß für die Menge an Daten, die verwendet wird, um eine Sekunde des Stückes darzustellen.<br>Der <b>Opus</b>-Encoder unterstützt <a href=http://de.wikipedia.org/wiki/Bitrate#Variable_Bitrate>variable Bitrate (VBR)</a>,was bedeutet, dass der Bitraten-Wert eines Stückes basierend auf der Komplexität des Audio-Inhalts schwankt. Komplexere Abschnitte werden mit einer höheren Bitrate als weniger komplexe kodiert. Dieser Ansatz führt zu einer insgesamt besseren Qualität und einer kleineren Datei als mit einer konstanten Bitrate über das gesamte Stück.<br> Die Bitraten-Einstellung mit dem Schieberegler ist daher nur eine grobe Schätzung der durchschnittlichen Bitrate des kodierten Stückes bei einem Qualitätswert.<br/><b>128 kb/s</b> ist eine gute Wahl für Musikhören auf einem tragbaren Player.<br/>Alles unter <b>100 kb/s</ b> könnte nicht zufriedenstellend sein, alles über <b>256 kb/s</b> ist wahrscheinlich übertrieben. Bitrate Bitrate Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://de.wikipedia.org/wiki/Apple_Lossless>Apple Lossless </a> (ALAC) ist ein Audio-Codec für verlustfreie Kompression digitaler Musik.<br>Zu empfohlen nur für Apple-Geräte und Geräte, die keine Unterstützung für FLAC besitzen. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://de.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec </a> (FLAC) ist ein offener und lizenzfreier Codec für verlustfreie Kompression digitaler Musik. <br> Wenn du deine Musik ohne Kompromisse bei der Audioqualität speichern möchtest, ist FLAC eine ausgezeichnete Wahl. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Der <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Kompressionsgrad</a> ist ein Zahlenwert zwischen 0 and 8, welcher einen Kompromiss zwischen Dateigröße und Kompressionsgeschwindigkeit während der Kodierung mit<b>FLAC</b> darstellt.<br/> Einstellen der Kompressionsrate auf <b>0</ b> liefert die kürzeste Zeit, aber die Kompression erzeugt eine vergleichsweise große Datei.<br/>Auf der anderen Seite arbeitet eine Kompression von <b>8</b> sehr langsam, erzeugt aber eine kleinere Datei.<br/>Beachten Sie, dass FLAC per Definition ein verlustfreier Codec ist. Die Audio-Qualität der Ausgabe ist unabhängig von der Kompression.<br/>Ein Komprimierungsgrad über <b>5</b> erhöht erheblich die Kompressionszeit, wird aber kaum kleinere Dateien erstellen. Compression level Kompressionsgrad Faster compression Schnellere Kompression Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://de.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) ist ein proprietärer Codec von Microsoft für verlustbehaftete Audiokompression.<br>Empfohlen nur für tragbare Geräte, die nicht Ogg Vorbis unterstützen. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Die Bitrate ist ein Maß für die Menge an Daten, die verwendet wird, um eine Sekunde des Stückes darzustellen.<br>Aufgrund der Einschränkungen des proprietären <b>WMA</b>-Formats und der Schwierigkeit eines Reverse-Engineerings des Encoders wird der WMA-Encoder, der von Cantata benutzt wird, <a href=http://de.wikipedia.org/wiki/Windows_Media_Audio>eine konstante Bitrate (CBR)</a> benutzen.<br>Aus diesem Grund ist die Bitrateneineit in diesem Schieberegler eine ziemlich genaue Schätzung der Bitrate der codierten Spur.<br><b>136 kb/s</b> ist eine gute Wahl für Musikhören auf einem tragbaren Gerät.<br/>Alles unter <b>112 kb/s</ b> könnte nicht zufriedenstellend sein, aber alles über <b>182 kb/s</b> ist wahrscheinlich übertrieben. Empty filename. Leerer Dateiname. Invalid filename. (%1) Ungültiger Dateiname. (%1) Failed to save %1. Speichern von %1 fehlgeschlagen. Failed to delete rules file. (%1) Löschen der Einstellungsdatei fehlgeschlagen. (%1) Invalid command. (%1) Ungültiger Befehl. (%1) Could not remove active rules link. Verknüpfung der aktiven Regeln konnte nicht entfernt werden. Active rules is not a link. Aktive Regeln sind keine Verknüpfung. Could not create active rules link. Verknüpfung der aktiven Regeln konnte nicht erstellt werden. Rules file, %1, does not exist. Die Regeldatei, %1, existiert nicht. Incorrect arguments supplied. Fehlherhafte Argumente angegeben. Unknown method called. Unbekannte Methode aufgerufen. Unknown error Unbekannter Fehler Artist Interpret SimilarArtists Ähnliche Interpreten AlbumArtist Album-Interpret Composer Komponist Comment Kommentar Album Album Title Titel Genre Genre Date Datum File Datei Include Einschließen Exclude Ausschließen (Exact) (Exakt) %1 %2 x %3 (%4) name width x height (file size) %1 %2 × %3 (%4) %1 %2 x %3 name width x height %1 %2 × %3 Current Cover Aktuelles Coverbild CoverArt Archive CoverArt Archive Grouped Albums Gruppierte Alben Table Tabelle Parse in Library view, and show in Folders view In Bibliotheksansicht verarbeiten, in Ordneransicht anzeigen Only show in Folders view Nur in Ordneransicht anzeigen Do not list Nicht aufführen %1 Tracks Plural (N!=1) %1 Stücke 1 Track (%1) Singular Ein Stück (%1) %1 Tracks (%2) Plural (N!=1) %1 Stücke (%2) %1 Albums Plural (N!=1) %1 Alben %1 Artists Plural (N!=1) %1 Künstler %1 Streams Plural (N!=1) %1 Streams %1 Entries Plural (N!=1) %1 Einträge %1 Rules Plural (N!=1) %1 Regeln %1 Podcasts Plural (N!=1) %1 Podcasts %1 Episodes Plural (N!=1) %1 Episoden Previous Track Vorheriges Stück Next Track Nächstes Stück Play/Pause Abspielen/Pause Stop Stopp Stop After Current Track Nach aktuellem Stück stoppen Stop After Track Nach Stück stoppen Increase Volume Lautstärke anheben Decrease Volume Lautstärke verringern Save As Speichern unter Append Anhängen Append To Play Queue An die Warteschlange anhängen Append And Play Anhängen und abspielen Add And Play Hinzufügen und abspielen Append To Play Queue And Play Zur Warteschlange hinzufügen und abspielen Insert After Current Hinter aktuellem Stück einfügen Append Random Album Zufälliges Album anhängen Play Now (And Replace Play Queue) Jetzt abspielen (und Warteschlange ersetzen) Add With Priority Mit Priorität hinzufügen Set Priority Priorität setzen Highest Priority (255) Höchste Priorität (255) High Priority (200) Hohe Priorität (200) Medium Priority (125) Mittlere Priorität (125) Low Priority (50) Niedrige Priorität (50) Default Priority (0) Normale Priorität (0) Custom Priority... Benutzerdefinierte Priorität … Add To Playlist Zur Wiedergabeliste hinzufügen Organize Files Dateien organisieren Edit Track Information Informationen über das Stück bearbeiten ReplayGain Replay Gain Copy Songs To Device Stücke auf Gerät kopieren Delete Songs Stücke löschen Set Image Bild setzen Remove Entfernen Find Suchen Add To Play Queue An Warteschlange anhängen Parse error loading cache file, please check your songs tags. Verarbeitungsfehler beim Laden der Zwischenspeicherdatei. Bitte überprüfe deine Tags. Other Anderes Default Voreinstellung "%1" name (host) "%1" "%1" (%2:%3) name (host:port) „%1“ (%2:%3) Single Tracks Einzelne Stücke Personal Persönlich Unknown Unbekannt Various Artists Diverse Interpreten Album artist Album-Interpret Performer Ausführender Künstler Track number Stücknummer Disc number Disc-Nummer Year Jahr Orignal Year Original-Jahr Length Länge <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> auf <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> von <b>%2</b> auf <b>%3</b> Invalid service Ungültiger Dienst Invalid method Ungültige Methode Authentication failed Authentifizierung fehlgeschlagen Invalid format Ungültiges Format Invalid parameters Ungültige Parameter Invalid resource specified Ungültige Ressource angegeben Operation failed Operation fehlgeschlagen Invalid session key Ungültiger Sitzungsschlüssel Invalid API key Ungültiger API-Schlüssel Service offline Dienst offline Last.fm is currently busy, please try again in a few minutes Last.fm ist derzeit überlastet, bitte versuche es in ein paar Minuten noch einmal. Rate-limit exceeded Ratengrenze überschritten General Allgemein Digitally Imported Digitally Imported Local and National Radio (ListenLive) Lokales und nationales Radio (ListenLive) &OK &OK &Cancel &Abbrechen &Yes &Ja &No &Nein &Discard &Verwerfen &Save &Speichern &Apply &Anwenden &Close S&chließen &Help &Hilfe &Overwrite &Überschreiben &Reset &Zurücksetzen &Continue &Fortfahren &Delete &Löschen &Stop &Stopp &Remove &Entfernen &Previous &Zurück &Next &Weiter Close Schließen Error Fehler Information Information Warning Warnung Question Frage %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Einfacher Dateibaum (keine Symbole) Simple Tree Simpler Dateibaum Detailed Tree Detaillierter Dateibaum List Liste Grid Raster %n Track(s) Ein Stück %n Stücke %n Tracks (%1) Ein Stück (%1) %n Tracks (%1) %n Album(s) Ein Album %n Alben %n Artist(s) Ein Künstler %n Künstler %n Stream(s) Ein Stream %n Streams %n Entry(s) Ein Eintrag %n Einträge %n Rule(s) Eine Regel %n Regeln %n Podcast(s) Ein Podcast %n Podcasts %n Episode(s) Eine Episode %n Episoden %n Update(s) available 1 Update available %n Updates available RemoteDevicePropertiesDialog Device Properties Geräteeigenschaften Connection Verbindung Music Library Musikbibliothek Add Device Gerät hinzufügen A remote device named '%1' already exists! Please choose a different name. Ein externes Gerät mit dem Namen ‚%1‘ existiert bereits. Bitte wähle einen anderen Namen. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Diese Einstellungen sind nur änderbar, wenn das Gerät nicht verbunden ist. Type: Typ: Name: Name: Options Optionen Host: Host: Port: Port: User: Benutzer: Domain: Domain: Password: Kennwort: Share: Freigabe: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Wenn du hier ein Kennwort eingibst, wird es <b>unverschlüsselt</b> in der Cantata-Konfigurationsdatei gespeichert. Damit Cantata nach dem Kennwort fragt, bevor es die Freigabe öffnet, setze das Kennwort auf „-“. Service name: Name des Diensts: Folder: Ordner: Extra Options: Weitere Optionen: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. Aufgrund des Funktionsprinzips von sshfs wird eine passende ssh-askpass-Anwendung (ksshaskpass, ssh-askpass-gnome etc.) benötigt. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Dieser Dialog dient nur dem Hinzufügen externer Geräte (z. B. via Samba) und dem Öffnen local eingehängter Verzeichnisse. Gewöhnliche über USB verbundene Media-Player zeigt Cantata automatisch an, sobald sie verbunden sind. Samba Share Samba-Freigabe Samba Share (Auto-discover host and port) Samba-Freigabe (Host und Port automatisch finden) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Lokal eingehängter Ordner RemoteFsDevice Available Verfügbar Not Available Nicht verfügbar Failed to resolve connection details for %1 Auflösen der Verbindungsdetails zu %1 fehlgeschlagen. Connecting... Verbinde … Password prompting does not work when cantata is started from the commandline. Passwortabfrage funktioniert nicht, wenn Cantata von der Kommandozeile gestartet wird. No suitable ssh-askpass application installed! This is required for entering passwords. Keine passende ssh-askpass-Anwendung installiert. Dies wird benötigt, um Passwörter eingeben zu können. Mount point ("%1") is not empty! Einhängepunkt (‚%1‘) ist nicht leer. "sshfs" is not installed! ‚sshfs‘ ist nicht installiert. Disconnecting... Trenne Verbindung … "fusermount" is not installed! ‚fusermount‘ ist nicht installiert. Failed to connect to "%1" Verbinden zu ‚%1‘ fehlgeschlagen. Failed to disconnect from "%1" Trennen der Verbindung zu ‚%1‘ fehlgeschlagen. Updating tracks... Aktualisiere Stücke … Not Connected Nicht verbunden Capacity Unknown Größe unbekannt %1 free %1 frei RgDialog ReplayGain Replay Gain Show All Tracks Alle Stücke anzeigen Show Untagged Tracks Nicht getaggte Stücke anzeigen Remove From List Von Liste entfernen Artist Interpret Album Album Title Titel Album Gain Album-Gain Track Gain Stück-Gain Album Peak Album-Peak Track Peak Stück-Peak Scan Einlesen Update ReplayGain tags in tracks? Replay-Gain-Tags in den Stücken aktualisieren? Update Tags Tags aktualisieren Abort scanning of tracks? Einlesen der Stücke abbrechen? Abort Abbrechen Abort reading of existing tags? Lesen vorhandener Tags abbrechen? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> <b>Alle</b> Stücke einlesen?<br/><br/><i>Alle Stücke enhalten Replay-Gain-Tags.</i> Do you wish to scan all tracks, or only tracks without existing tags? Willst du alle Dateien untersuchen oder nur die ohne Tags? Untagged Tracks Nicht getaggte Stücke All Tracks Alle Stücke Scanning tracks... Lese Stücke ein … Reading existing tags... Lese vorhandene Tags … %1 (Corrupt tags?) filename (Corrupt tags?) %1 (korrupte Tags?) Failed to update the tags of the following tracks: Aktualisieren der Tags in den folgenden Stücken fehlgeschlagen: Device has been removed! Gerät wurde entfernt. Device is not connected. Gerät ist nicht verbunden. Device is busy? Gerät aktuell beschäftigt? %1 dB %1 dB Failed Fehlgeschlagen Original: %1 dB Original: %1 dB Original: %1 Original: %1 Remove the selected tracks from the list? Ausgewählte Stücke von der Liste entfernen? Remove Tracks Stücke entfernen RulesPlaylists Album Artist Artist Album Album Composer Komponist Date Datum Genre Genre Rating Bewertung File Age Random Zufall %n Rule(s) Eine Regel %n Regeln , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 Fehler: %2 ScrobblingLove %1: Loved Current Track %1: Aktuelles Stück gelovet %1: Love Current Track %1: Aktuelles Stücke loven ScrobblingSettings Scrobble using: Scrobbeln über: Username: Benutzername: Password: Kennwort: Status: Status: Login Einloggen Scrobble tracks Stücke scrobbeln Show 'Love' button ‚Love‘-Knopf anzeigen %1 (via MPD) scrobbler name (via MPD) %1 (via MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Wenn du einen Scrobbler verwendest, der mit „(via MPD)“ markiert ist (wie z. B. %1), dann muss dieser Scrobbler bereits laufen. Cantata kann über ihn nur Stücke ‚loven‘, nicht aber das Scrobbling aktivieren und deaktiveren. Authenticating... Authentifiziere … Authenticated Authentifiziert Not Authenticated Nicht authentifiziert ScrobblingStatus %1: Scrobble Tracks %1: Stücke scrobbeln SearchModel # (Track Number) # (Stücknummer) SearchPage Locate In Library In Musikbibliothek lokalisieren Artist: Interpret: Composer: Komponist: Performer: Ausführender Künstler: Album: Album: Title: Titel: Genre: Genre: Comment: Kommentar: Date: Datum: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Finde Stücke über den ‚Datum‘-Tag.<br/><br/>Normalerweise sollte es genügen, das Jahr anzugeben. Original Date: Original-Datum Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Finde Stücke über den Tag ‚Original-Datum‘.<br/><br/>Normalerweise sollte es genügen, das Jahr anzugeben. Modified: Verändert: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Gib ein Datum ein (JJJJ/MM/TT – z. B. 2015/01/31), um Dateien zu finden, die seit diesem Datum verändert wurden.<br/><br>Oder gib eine Anzahl von Tagen ein, um Dateien zu finden, die in dieser Anzahl vergangener Tage verändert wurden. File: Datei: Any: Beliebig: No tracks found. Keine Stücke gefunden. %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) SearchWidget Search... Suche … Close Search Bar Suchleiste schließen ServerSettings Collection: Sammlung: Name: Name: Host: Host: Password: Kennwort: Music folder: Musikverzeichnis: Cover filename: Dateiname für Coverbilder: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Dateiname (ohne Endung) zum Speichern der Coverbilder.<br/>Ist der Dateiname leer, wird „cover“ verwendet.<br/><br/><i>%artist% wird ersetzt durch den Album-Interpreten des aktuellen Stücks, %album% durch den Titel des Albums.</i></p> HTTP stream URL: HTTP-Stream-URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. Die Einstellung ‚Musikverzeichnis‘ wird verwendet, um Coverbilder nachzuschlagen. Es kann auf eine HTTP-URL gesetzt werden, wenn MPD auf einem anderen Rechner läuft und die Coverbilder über HTTP verfügbar sind. Wenn die Einstellung nicht auf eine HTTP-URL gesetzt ist und du Schreibrechte für dieses Verzeichnis (und seine Unterverzeichnisse) besitzt, dann wird Cantata heruntergeladene Coverbilder in dem entsprechenden Verzeichnis des Albums speichern. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> Wenn ‚Dateiname für Coverbilder‘ nicht gesetzt ist, dann wird Cantata die Statndardeinstellung <code>cover</code> verwenden. 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. ‚HTTP-Stream-URL‘ ist nur nützlich, wenn dein MPD so konfiguriert ist, dass er einen HTTP-Stream ausgibt, und du mit Cantata diesen Stream wiedergeben möchtest. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. Wenn du die Einstellung ‚Musikverzeichnis‘ änderst, musst du manuell die Musikdatenbank aktualisieren. Dies geschieht über die Schlatfläche „Datenbank aktualisieren“ in der Interpreten- oder Albenansicht. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. Dieses Verzeichnis wird ebenso verwendet, um Musikdateien zur Tag-Bearbeitung zu finden, für Replay Gain und zum Dateitransfer von und zu externen Geräten. This folder will also be used to locate music files for tag-editing, replay gain, etc. Dieses Verzeichnis wird ebenso verwendet, um Musikdateien zur Tag-Bearbeitung zu finden, für Replay Gain etc. Which type of collection do you wish to connect to? Welche Art von Sammlung möchtest du einbinden? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Standard – die Musiksammlung kann freigegeben sein, auf einem anderen Rechner, bereits eingerichtet, oder du möchtest den Zugriff von anderen Clients (z. B. MPDroid) ermöglichen. Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Einfach – Die Musiksammlung wird nicht für andere freigegeben, und Cantata wird die MPD-Instaz einrichten und steuern. Diese Konfiguration wird nur für Cantata und <b>nicht</b> für andere MPD-Clients zugänglich sein. <i><b>NOTE:</b> %1</i> <i><b>HINWEIS:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Wenn du eine ausgefeiltere MPD-Konfiguration (z. B. mehrere Audioausgänge, volle DSD-Unterstützung etc.) haben möchtest, <b>musst</b> du ‚Standard‘ wählen. Add Collection Sammlung hinzufügen Standard Standard Basic Einfach Delete '%1'? ‚%1‘ löschen? Delete Löschen New Collection %1 Neue Sammlung %1 Default Voreinstellung ServiceStatusLabel Logged into %1 Eingeloggt bei %1 <b>NOT</b> logged into %1 <b>NICHT</b> eingeloggt bei %1 ShortcutsModel Action Aktion Shortcut Tastenkürzel ShortcutsSettingsWidget Search: Suche: Shortcut for Selected Action Tastaturkürzel für gewählte Aktion Default: Voreinstellung: None Keine Custom: Benutzerdefiniert: SinglePageWidget Refresh Aktualisieren View Ansicht SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Hinzufügen Edit Bearbeiten Remove Entfernen Are you sure you wish to remove the selected rules? This cannot be undone. Bist du sicher, dass du die ausgewählten Regeln entfernen möchtest? Dies kann nicht rückgängig gemacht werden. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Kann nicht auf Musikdateien zugreifen. Bitte überprüfe die ‚Musikverzeichnis‘-Einstellung in Cantata und die ‚music_directory‘-Einstellung in MPD. Cannot access song files! Please check that the device is still attached. Kann nicht auf Musikdateien zugreifen. Bitte überprüfe, ob das Gerät noch verbunden ist. SongView Lyrics Liedtext Information Information Metadata Metadaten Scroll Lyrics Liedtext scrollen Refresh Lyrics Liedtext aktualisieren Edit Lyrics Liedtext bearbeiten Delete Lyrics File Liedtextdatei löschen Refresh Track Information Informationen über das Stück aktualisieren Cancel Abbrechen Track Stück Reload lyrics? Reload from disk, or delete disk copy and download? Liedtext neu laden? Von der Platte neu laden, oder die lokale Version löschen und neu herunterladen? Reload Neu laden Reload From Disk Von der Platte neu laden Download Herunterladen Current playing song has changed, still perform search? Aktuell gespieltes Stück hat sich geändert. Trotzdem suchen? Song Changed Stück geändert Perform Search Suche ausführen Delete lyrics file? Liedtextdatei löschen? Delete File Datei löschen Artist Interpret Album artist Album-Interpret Composer Komponist Lyricist Textautor Conductor Dirigent Remixer Remixer Album Album Subtitle Untertitel Track number Stücknummer Disc number Disc-Nummer Genre Genre Date Datum Original date Original-Datum Comment Kommentar Copyright Copyright Label Label Catalogue number Katalognummer Title sort Titel-Sortierung Artist sort Interpret-Sortierung Album artist sort Album-Interpret-Sortierung Album sort Album-Sortierung Encoded by Enkodiert von Encoder Encoder Mood Stimmung Media Medien Bitrate Bitrate Sample rate Samplerate Channels Kanäle Tagging time Tag-Zeitpunkt Performer (%1) Ausführender Künstler (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bits Performer Ausführender Künstler Year Jahr Filename Dateiname Fetching lyrics via %1 Empfange Texte über %1 SoundCloudService Search for tracks from soundcloud.com Suche Stücke auf soundcloud.com SpaceLabel Calculating... Berechne … Total space used: %1 Insgesamt genutzter Speicherplatz: %1 SqlLibraryModel %n Artist(s) Ein Interpret %n Interpreten %n Album(s) Ein Album %n Alben %n Tracks (%1) Ein Stück (%1) %n Stücke (%1) Cue Sheet Cue-Sheet Playlist Wiedergabeliste StoredPlaylistsPage Rename Umbenennen Remove Duplicates Duplikate entfernen Initially Collapse Albums Alben standardmäßig einklappen Are you sure you wish to remove the selected playlists? This cannot be undone. Bist du sicher, dass du die ausgewählten Wiedergabelisten entfernen möchtest? Dies kann nicht rückgängig gemacht werden. Remove Playlists Wiedergabelisten entfernen Playlist Name Name der Wiedergabeliste Enter a name for the playlist: Gib einen Namen für die Wiedergabeliste ein: A playlist named '%1' already exists! Overwrite? Eine Wiedergabeliste mit dem Namen ‚%1‘ existiert bereits. Überschreiben? Overwrite Playlist Wiedergabeliste überschreiben Rename Playlist Wiedergabeliste umbenennen Enter new name for playlist: Gib einen neuen Namen für die Wiedergabeliste ein: Cannot add songs from '%1' to '%2' Kann Stücke von ‚%1‘ nicht zu ‚%2‘ hinzufügen StreamDialog Add stream to favourites Stream zu Favoriten hinzufügen Name: Name: URL: URL: Add Stream Stream hinzufügen Edit Stream Stream bearbeiten <i><b>ERROR:</b> Invalid protocol</i> <i><b>FEHLER:</b> Ungültiges Protokoll</i> StreamFetcher Loading %1 Lade %1 StreamProviderListDialog Installed Installiert Update available Aktualisierung verfügbar Check the providers you wish to install/update. Wähle die Anbieter, die du installieren/aktualisieren möchtest. Install/Update Stream Providers Stream-Anbieter installieren/aktualisieren Downloading list... Lade Liste herunter … Failed to download list of stream providers! Download der Stream-Anbieter-Liste fehlgeschlagen. Installing/updating %1 Installere/aktualisiere %1 Failed to install '%1' Installieren von ‚%1‘ fehlgeschlagen Failed to download '%1' Herunterladen von ‚%1‘ fehlgeschlagen Install/update the selected stream providers? Ausgewählte Stream-Anbieter installieren/aktualisieren? Install the selected stream providers? Ausgewählte Stream-Anbieter installieren? Update the selected stream providers? Ausgewählte Stream-Anbieter aktualisieren? Install/Update Installieren/aktualisieren Abort installation/update? Installation/Aktualisierung abbrechen? Abort Abbrechen %n Update(s) available 1 Aktualisierung verfügbar %n Aktualisierungen verfügbar Downloading %1 Lade %1 herunter Update all updateable providers Alle aktualisierbaren Anbieter aktualisieren StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Stream-Suche Search for radio streams Nach Radio-Streams suchen Enter string to search Gib einen Suchbegriff ein Not Loaded Nicht geladen Loading... Lade … %n Entry(s) Ein Eintrag %n Einträge StreamSearchPage Added '%1'' to favorites ‚%1‘ zu Favoriten hinzugefügt StreamsBrowsePage Import Streams Into Favorites Stream in die Favoriten importieren Export Favorite Streams Stream-Favoriten exportieren Add New Stream To Favorites Neuen Stream zu den Favoriten hinzufügen Edit Bearbeiten Seatch For Streams Nach Streams suchen Configure Einstellungen Digitally Imported Service name Digitally Imported Import Streams Streams importieren XML Streams (*.xml *.xml.gz *.cantata) XML-Streams (*.xml *.xml.gz *.cantata) Export Streams Streams exportieren XML Streams (*.xml.gz) XML-Streams (*.xml.gz) Failed to create '%1'! Erstellen von ‚%1‘ fehlgeschlagen. Stream '%1' already exists! Stream ‚%1‘ existiert bereits. A stream named '%1' already exists! Ein Stream mit dem Namen ‚%1‘ exisitert bereits. Bookmark added Lesezeichen hinzugefügt Already bookmarked Bereits in den Lesezeichen Already in favorites Existiert bereits in den Favoriten Reload '%1' streams? ‚%1‘-Streams neu laden? Are you sure you wish to remove bookmark to '%1'? Bist du sicher, dass du das Lesezeichen zu ‚%1‘ entfernen möchtest? Are you sure you wish to remove all '%1' bookmarks? Bist du sicher, dass du alle ‚%1‘-Lesezeichen entfernen möchtest? Are you sure you wish to remove the %1 selected streams? Bist du sicher, dass du die ausgewählten %1 Streams entfernen möchtest? Are you sure you wish to remove '%1'? Bist du sicher, dass du ‚%1‘ entfernen möchtest? Added '%1'' to favorites ‚%1‘ zu Favoriten hinzugefügt StreamsModel Bookmarks Lesezeichen TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Favoriten Bookmark Category Kategorie zu Lesezeichen hinzufügen Add Stream To Favorites Stream zu Favoriten hinzufügen Configure Digitally Imported Digitally Imported konfigurieren Reload Neu laden Streams Streams Radio stations Radiosender Not Loaded Nicht geladen Loading... Lade … %n Entry(s) Ein Eintrag %n Einträge StreamsSettings Use the checkboxes below to configure the list of active providers. Verwende die Kästchen unten, um die Liste aktiver Anbieter zu konfigurieren. Built-in categories are shown in italic, and these cannot be removed. Feste Kategorien sind kursiv dargestellt und können nicht entfernt werden. Configure Streams Streams konfigurieren From File... Aus Datei … Download... Herunterladen … Configure Provider Anbieter konfigurieren Install Installieren Remove Entfernen Install Streams Streams installieren Cantata Streams (*.streams) Cantata-Streams (*.streams) A category named '%1' already exists! Overwrite? Eine Kategorie mit dem Namen ‚%1‘ existiert bereits. Überschreiben? Failed top open package file. Öffnen der Paketdatei fehlgeschlagen. Invalid file format! Ungültiges Dateiformat. Failed to create stream category folder! Erstellen des Stream-Kategorie-Ordners fehlgeschlagen. Failed to save stream list! Speichern der Stream-Liste fehlgeschlagen. Are you sure you wish to remove '%1'? Bist du sicher, dass du ‚%1‘ entfernen möchtest? Failed to remove streams folder! Entfernen des Stream-Ordners fehlgeschlagen. SyncCollectionWidget Search Suche Check Items Aktiviere markierte Stücke Uncheck Items Deaktiviere markierte Stücke SyncDialog Library: Bibliothek: Device: Gerät: Loading all songs from library, please wait... Lade alle Stücke aus der Bibliothek, bitte warten … <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Bibliothek</code> listet nur Stücke, die sich in deiner Bibliothek befinden, nicht aber auf dem Gerät. Enstprechend listet <code>Gerät</code> Stücke, die sich nur auf dem Gerät befinden.<br/>Wähle Stücke aus der <code>Bibliothek</code>, die du auf das <code>Gerät</code> kopieren möchtest, und Stücke vom <code>Gerät</code>, die du in die <code>Bibliothek</code> kopieren möchtest. Klicke dann auf <code>Synchronisieren</code>. Synchronize Synchronisieren Device and library are in sync. Gerät und Bibliothek sind synchronisiert. Loading all songs from library, please wait...%1%... Lade alle Stücke aus der Bibliothek, bitte warten … %1 % Local Music Library Properties Lokale Bibliothekseinstellungen Device has been removed! Gerät wurde entfernt. Device has been changed? Gerät wurde geändert? Device is busy? Gerät aktuell beschäftigt? TableView Stretch Columns To Fit Window Spalten strecken, um Fensterbreite zu füllen Left Links Center Mitte Right Rechts Alignment Ausrichtung TagEditor Track: Stück: Title: Titel: Artist: Interpret: Album artist: Album-Interpret: Composer: Komponist: Album: Album: Track number: Stücknummer: Disc number: Disc-Nummer Genre: Genre: Year: Jahr: Rating: Bewertung: <i>(Various)</i> <i>(Unterschiedlich)</i> Comment: Kommentar: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Mehrfache Genres sollten durch Komma getrennt werden (z. B. „Rock,Hard Rock“) Ratings are stored in an external database, and <b>not</b> in the song's file. Bewertungen werden in einer externen Datenbank gespeichert, <b>nicht</b> in der Musikdatei. Tags Tags Tools Werkzeuge Apply "Various Artists" Workaround ‚Diverse Interpreten‘-Workaround anwenden Revert "Various Artists" Workaround ‚Diverse Interpreten‘-Workaround rückgängig machen Set 'Album Artist' from 'Artist' Setze ‚Album-Interpret‘ aus ‚Interpret‘ Capitalize Große Anfangsbuchstaben Adjust Track Numbers Stücknummern anpassen Read Ratings from File Bewertungen aus Datei lesen Write Ratings to File Bewertungen in Datei schreiben All tracks Alle Stücke Apply "Various Artists" workaround to <b>all</b> tracks? ‚Diverse Interpreten‘-Workaround auf <b>alle</b> Stücke anwenden? Apply "Various Artists" workaround? ‚Diverse Interpreten‘-Workaround anwenden? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Dies setzt die Felder Album-Interpret und Interpret auf „Diverse Interpreten“ und den Titel auf ‚Stück-Interpret - Stück-Titel‘</i> Revert "Various Artists" workaround on <b>all</b> tracks? ‚Diverse Interpreten‘-Workaround für <b>alle</b> Stücke rückgängig machen? Revert "Various Artists" workaround ‚Diverse Interpreten‘-Workaround rückgängig machen <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Wo die Felder Album-Interpret und Interpret übereinstimmen und das Titel-Feld vom Format ‚Stück-Interpret - Stück-Titel‘ ist, wird das Interpreten-Feld vom Titel-Feld übernommen und das Titel-Feld wird auf den Stück-Titel gesetzt; z. B.:<br/><br/>Ist das Titel-Feld„Wibble - Wobble“, so wird der Interpret auf „Wibble“ und der Titel auf „Wobble“ gesetzt.</i> Revert Rückgängig machen Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? ‚Album-Interpret‘ für <b>alle</b> Stücke aus ‚Interpret‘ setzen (falls ‚Album-Interpret‘ leer ist)? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? ‚Album-Interpret‘ aus ‚Interpret‘ setzen (falls ‚Album-Interpret‘ leer ist)? Album Artist from Artist ‚Album-Interpret‘ aus ‚Interpret‘ Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Große Anfangsbuchstaben in Textfeldern (z. B: ‚Titel‘, ‚Interpret‘ etc.) für <b>alle</b> Stücke? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Große Anfangsbuchstaben in Textfeldern (z. B: ‚Titel‘, ‚Interpret‘ etc.)? Adjust the value of each track number by: Anpassen des Werts jeder Stücknummer durch: Adjust track number by: Stücknummern anpassen um: Read ratings for all tracks from the music files? Bewertungen für alle Stücke aus den Musikdateien lesen? Read rating from music file? Bewertung aus der Musikdatei lesen? Ratings Bewertungen Read Ratings Bewertungen lesen Read Rating Bewertung lesen Read, and updated, ratings from the following tracks: Für folgende Stücke Bewertungen lesen und aktualisieren: Not all Song ratings have been read from MPD! Nicht alle Stück-Bewertungen wurden von MPD gelesen. Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Stück-Bewertungen werden nicht in den Musikdateien gespeichert, sondern in der MPD-‚Sticker‘-Datenbank. Um sie in der Datei selbst zu speichern, muss Cantata sie zuerst von MPD lesen. Song rating has not been read from MPD! Stück-Bewertung wurde nicht von MPD gelesen. Write ratings for all tracks to the music files? Bewertungen für alle Stücke in Musikdateien speichern? Write rating to music file? Bewertung in Musikdatei speichern? Write Ratings Bewertungen speichern Write Rating Bewertung speichern Failed to write ratings of the following tracks: Speichern der Bewertung für folgende Stücke fehlgeschlagen: Failed to write rating to music file! Speichern der Bewertung in der Musikdatei fehlgeschlagen. All tracks [modified] Alle Stücke [geändert] %1 [modified] %1 [geändert] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (korrupte Tags?) Failed to update the tags of the following tracks: Aktualisieren der Tags in den folgenden Stücken fehlgeschlagen: Would you also like to rename your song files, so as to match your tags? Möchtest du auch deine Musikdateien umbenennen, sodass die Dateinamen den Tags entsprechen? Rename Files Dateien umbenennen Rename Umbenennen Device has been removed! Gerät wurde entfernt. Device is not connected. Gerät ist nicht verbunden. Device is busy? Gerät aktuell beschäftigt? TagSpinBox (Various) (Unterschiedlich) ThinSplitter Reset Spacing Abstand zurücksetzen TitleWidget Click to go back Klicken um zurückzukehren Add All To Play Queue Alle an die Warteschlange anhängen Add All And Replace Play Queue Alle hinzufügen und die Warteschlange ersetzen ToggleList Available: Verfügbar: Selected: Ausgewählt: TrackOrganiser Filenames Dateinamen Filename scheme: Dateinamen-Schema: VFAT safe VFAT-sicher Use only ASCII characters Nur ASCII-Zeichen verwenden Replace spaces with underscores Leerzeichen durch Unterstriche ersetzen Append 'The' to artist names „The“ zu Interpretennamen hinzufügen Original Name Ursprünglicher Name New Name Neuer Name: Ratings will be lost if a file is renamed. Bewertungen gehen verloren, wenn eine Datei umbenannt wird. Organize Files Dateien organisieren Rename Umbenennen Remove From List Von Liste entfernen Abort renaming of files? Umbenennen der Dateien abbrechen? Abort Abbrechen Source file does not exist! Quelldatei existiert nicht. Skip Überspringen Auto Skip Automatisch überspringen Destination file already exists! Zieldatei existiert bereits. Failed to create destination folder! Fehler beim Erstellen des Zielordners. Failed to rename '%1' to '%2' Fehler beim Umbenennen von ‚%1‘ nach ‚%2‘ Remove the selected tracks from the list? Ausgewählte Stücke von der Liste entfernen? Remove Tracks Stücke entfernen Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Stück-Bewertungen werden nicht in den Musikdateien gespeichert, sondern in der MPD-‚Sticker‘-Datenbank. Wenn du eine Datei (oder den Ordner, der sie enthält) umbenennst, geht die mit dem Stück assoziierte Bewertung verloren. Device has been removed! Gerät wurde entfernt. Device is not connected. Gerät ist nicht verbunden. Device is busy? Gerät aktuell beschäftigt? TrayItem Cantata Cantata Now playing Spielt gerade UltimateLyricsProvider (Polish Translations) (Polnische Übersetzung) (Portuguese Translations) (Portugiesische Übersetzung) UmsDevice Not Scanned Nicht eingelesen Not Connected Nicht verbunden %1 free %1 frei ValueSlider (recommended) (empfohlen) View Cancel Abbrechen VolumeSlider Mute Ton aus Unmute Ton an Volume %1% (Muted) Lautstärke %1 % (Ton aus) Volume %1% Lautstärke %1 % WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | Interpret|Band|Sänger|Vokalist|Musiker album|score|soundtrack Search pattern for an album, separated by | Album|Bewertung|Soundtrack WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Wähle die Wikipedia-Sprache, welche für Interpreten- und Alben-Informationen benutzt werden soll. Reload Neu laden cantata-2.2.0/translations/cantata_en_GB.ts000066400000000000000000013153341316350454000206770ustar00rootroot00000000000000 Capitalize Capitalise Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Capitalise the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Synchronize Synchronise Dynamizer has been terminated. Dynamiser has been terminated. Failed to control dynamizer state. (%1) Failed to control dynamiser state. (%1) Remote dynamizer is not running. Remote dynamiser is not running. Organize Files Organise Files Favorites Favourites Add Stream To Favorites Add Stream To Favourites Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Darken background, and use white text, regardless of current colour palette. Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Minimise to notification area when closed Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Stop playback on exit Import Streams Into Favorites Import Streams Into Favourites Export Favorite Streams Export Favourite Streams Add New Stream To Favorites Add New Stream To Favourites Already in favorites Already in favourites Added '%1'' to favorites Added '%1'' to favourites Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalise the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Capitalise the first letter of text fields (e.g. 'Title', 'Artist', etc)? ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Configure (Needs configuring) Copy songs to: Destination format: Overwrite songs To copy: <b>INVALID</b> <i>(When different)</i> Artists:%1, Albums:%2, Songs:%3 %1 free Local Music Library Audio CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Copy Songs To Library Copy Songs To Device Copy Songs Delete Songs You have not configured the destination device. Continue with the default settings? Not Configured Use Defaults You have not configured the source device. Continue with the default settings? Are you sure you wish to stop? Stop Device has been removed! Device is not connected! Device is busy? Device has been changed? Clearing unused folders Calculate ReplayGain for ripped tracks? ReplayGain Calculate The destination filename already exists! Song already exists! Song does not exist! Failed to create destination folder!<br/>Please check you have sufficient permissions. Source file no longer exists? Failed to copy. Failed to delete. Not connected to device. Selected codec is not available. Transcoding failed. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Failed to read source file. Failed to write to destination file. No space left on device. Failed to update metadata. Failed to download track. Failed to lock device. Local Music Library Properties Error Skip Auto Skip Retry Artist: Album: Track: Source file: Destination file: File: Saving cache AlbumDetails Album Details Artist: Composer: Title: Genre: Year: Disc: Single artist Tracks Track Artist Title AlbumDetailsDialog Audio CD Apply "Various Artists" Workaround Revert "Various Artists" Workaround Capitalize Capitalise Adjust Track Numbers Tools Apply "Various Artists" workaround? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Revert Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Capitalise the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Adjust track number by: AlbumView Refresh Album Information Album Tracks ArtistView Refresh Artist Information Artist Albums Web Links Similar Artists AudioCdDevice Reading disc %n Tracks (%1) %n Track (%1) %n Tracks (%1) AudioCdSettings Album and Track Information Retrieval Initially look up via: CDDB Host: CDDB Port: Lookup information as soon as CD is inserted Audio Extraction Full paranoia mode (best quality) Never skip on read error CDDB MusicBrainz BrowseModel Cue Sheet Playlist CacheItem Deleting... Calculating... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Scaled Covers Backdrops Lyrics Artist Information Album Information Track Information Stream Listings Podcast Directories Wikipedia Languages Scrobble Tracks Delete All Delete all '%1' items? Delete Cache Items Delete items from all selected categories? CacheTree Name Item Count Space Used CddbInterface Data Track Failed to open CD device Track %1 Failed to create CDDB connection Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB CDDB error: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Artist Title Disc Selection %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 (%3) artist - album (year) ContextSettings Lyrics Providers Wikipedia Languages Other ContextWidget &Artist Al&bum &Track CoverDialog Search Add a local file Configure This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive An image already exists for this artist, and the file is not writeable. A cover already exists for this album, and the file is not writeable. '%1' Artist Image '%1 - %2' Album Cover 'Artist - Album' Album Cover Failed to set cover! Could not download to temporary file! Failed to download image! Load Local Cover Images (*.png *.jpg) File is already in list! Failed to read image! Display Remove Failed to set cover! Could not make copy! Failed to set cover! Could not backup original! Failed to set cover! Could not copy file to '%1'! Searching... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> CoverPreview Image Downloading... Image (%1 x %2 %3%) Image (width x height zoom%) CustomActionDialog Name: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Edit Remove Name Command Remove the selected commands? Device Updating (%1)... Updating (%1%)... DevicePropertiesDialog Device Properties DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Name: Music folder: Copy album covers as: Maximum cover size: Default volume: 'Various Artists' workaround Automatically scan music when attached Use cache Filenames Filename scheme: VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Only transcode if source file is of a different format Only transcode if source is FLAC/WAV Don't copy covers Embed cover within each file No maximum size 400 pixels 300 pixels 200 pixels 100 pixels <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> Do not transcode Encoder Transcode to %1 %1 (%2 free) name (size free) DevicesModel Configure Device Refresh Device Connect Device Disconnect Device Edit CD Details Not Connected No Devices Attached DevicesPage Copy To Library Synchronise Forget Device Add Device Lookup album and track details? Refresh Via CDDB Via MusicBrainz Which type of refresh do you wish to perform? Partial - Only new songs are scanned (quick) Full - All songs are rescanned (slow) Partial Full Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs Are you sure you wish to forget '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Eject Are you sure you wish to disconnect '%1'? Disconnect Please close other dialogs first. DigitallyImported Not logged in Logged in Unknown error No subscriptions You do not have an active subscription Logged in (expiry:%1) Session expired DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Premium Account Username: Password: Stream type: Status: Login Session expiry: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Digitally Imported Settings MP3 256k AAC 64k AAC 128k Not Authenticated Authenticating... Authenticated Logout DockMenu Play Pause DynamicPlaylists Start Dynamic Playlist Stop Dynamic Mode Dynamic Playlists Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Failed to locate rules file - %1 Failed to remove previous rules file - %1 Failed to install rules file - %1 -> %2 Dynamizer has been terminated. Dynamiser has been terminated. Awaiting response for previous command. (%1) Saving rule Deleting rule Failed to save %1. (%2) Failed to delete rules file. (%1) Failed to control dynamizer state. (%1) Failed to control dynamiser state. (%1) Failed to set the current dynamic rules. (%1) DynamicPlaylistsPage Add Edit Remove Remote dynamizer is not running. Remote dynamiser is not running. Are you sure you wish to remove the selected rules? This cannot be undone. Remove Dynamic Rules FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: About filename schemes The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Album Artist The name of the album. Album Title The composer. Composer The artist of each track. Track Artist The track title (without <i>Track Artist</i>). Track Title The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Track Title (+Artist) The track number. Track # The album number of a multi-album album. Often compilations consist of several albums. CD # The year of the album's release. Year The genre of the album. Genre Filename Scheme Various Artists Example album artist Wibble Example artist Vivaldi Example composer Now 5001 Example album Wobble Example song name Dance Example genre The following variables will be replaced with their corresponding meaning for each track name. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> FolderPage Open In File Manager Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs FsDevice Updating... Reading cache Saving cache %1 %2% Message percent GenreCombo Filter On Genre All Genres GroupedViewDelegate Audio CD Streams %n Track(s) %n Track %n Tracks InitialSettingsWizard Cantata First Run Welcome to Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Host: Password: Music folder: Connect The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Music folder Please choose the folder containing your music collection. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Finished! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. Not Connected Connection Established Connection Failed Cantata will now terminate InputDialog Password Please enter password: InterfaceSettings Sidebar Views Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Style: Position: Only show icons, no text Auto-hide Play Queue Initially collapse albums Automatically expand current album Scroll to current track Prompt before clearing Separate action (and shortcut) for play queue search Background Image None Current album cover Custom image: Blur: 10px Opacity: 40% Toolbar Show stop button Show cover of current track Show track rating External Enable MPRIS D-BUS interface Show popup messages when changing tracks Show icon in notification area Minimize to notification area when closed Minimise to notification area when closed On Start-up Show main window Hide main window Restore previous state Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General Fetch missing covers from Last.fm Show delete action in context menus Enforce single-click activation of items Show song information tooltips Language: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Changing the style setting will require a re-start of Cantata. Library Folders Playlists Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Search (via MPD) Info - Current song information (artist, album, and lyrics) Large Small Tab-bar Left Right Top Bottom Images (*.png *.jpg) 10px pixels Notifications English (en) System default %1% value% %1 px pixels ItemView Go Back Updating... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings MP3 Ogg Streaming format: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Year Album, Artist, Year Album, Year, Artist Artist, Album, Year Artist, Year, Album Year, Album, Artist Year, Artist, Album Modified Date Group By Genre Artist Album Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs LyricSettings Choose the websites you want to use when searching for lyrics. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Title: Artist: Search For Lyrics MPDConnection Unknown Connection to %1 failed Connection to %1 failed - please check your proxy settings Connection to %1 failed - incorrect password Connecting to %1 Failed to send command to %1 - not connected Failed to load. Please check user "mpd" has read permission. Failed to load. MPD can only play local files if connected via a local socket. MPD reported the following error: %1 Failed to send command. Disconnected from %1 Failed to rename <b>%1</b> to <b>%2</b> Failed to save <b>%1</b> You cannot add parts of a cue sheet to a playlist! You cannot add a playlist to another playlist! Failed to send '%1' to %2. Please check %2 is registered with MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Streaming MP3 128k MP3 VBR Ogg Vorbis FLAC WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Username: Password: Membership: Downloads: MainWindow [Dynamic] Exit Full Screen Configure Cantata... Preferences Quit About Cantata... Show Window Server information... Refresh Database Refresh Connect Collection Outputs Stop After Track Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist Crop Others Add Stream URL Clear Center On Current Track Expanded Interface Show Current Song Information Full Screen Random Repeat Single When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Consume When consume is activated, a song is removed from the play queue after it has been played. Find in Play Queue Play Stream Locate In Library Play next Edit Track Information (Play Queue) Expand All Collapse All Cancel Play Queue Library Folders Playlists Internet Devices Search Info &Music &Edit &View &Queue &Help Set Rating No Rating Failed to locate any songs matching the dynamic playlist rules. Connecting to %1 Refresh MPD Database? About Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Abort download and quit Please close other dialogs first. Enabled: %1 Disabled: %1 Server Information <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> Cantata (%1) MPD reported the following error: %1 Cantata Playback stopped Remove all songs from play queue? Priority Enter priority (0..255): Decrease priority for each subsequent track Playlist Name Enter a name for the playlist: '%1' is used to store favorite streams, please choose another name. A playlist named '%1' already exists! Add to that playlist? Existing Playlist %n Track(s) %n Track %n Tracks %n Tracks (%1) %n Track (%1) %n Tracks (%1) MenuButton Menu MessageOverlay Cancel Mpris (Stream) MtpConnection Connecting to device... No devices found Connected to device Disconnected from device Updating folders... Updating tracks... MtpDevice Not Connected %1 free MusicBrainz Failed to open CD device Track %1 %1 (Disc %2) No matches found in MusicBrainz MusicLibraryModel Cue Sheet Playlist %n Track(s) %n Track %n Tracks %n Artist(s) %n Artist %n Artists %n Album(s) %n Album %n Albums %n Tracks (%1) %n Track (%1) %n Tracks (%1) %1 by %2 Album by Artist NoteLabel <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) OSXStyle &Window Close Minimize Zoom OnlineDbService Downloading...%1% Parsing music list.... Failed to download %n Artist(s) %n Artist %n Artists OnlineDbWidget Group By Genre Artist Configure The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Re-download music listing? OnlineSearchService Searching... OnlineSearchWidget No tracks found. %n Tracks (%1) %n Track (%1) %n Tracks (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Configure Service OnlineView Song Information OnlineXmlParser Failed to parse OpmlBrowsePage Reload Failed to download directory listing Failed to parse directory listing OtherSettings Background Image None Artist image Custom image: Blur: 10px Opacity: 40% Automatically switch to view after: Do not auto-switch ms Dark background Darken background, and use white text, regardless of current color palette. Darken background, and use white text, regardless of current colour palette. Always collapse into a single pane Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Images (*.png *.jpg) 10px pixels %1% value% %1 px pixels PathRequester Select Folder Select File PlayQueueModel Title Artist Album # Track number Length Disc Year Original Year Genre Priority Composer Performer Rating Remove Duplicates Undo Redo Shuffle Tracks Albums Sort By Album Artist Track Title Track Number # (Track Number) PlayQueueView Remove PlaybackSettings Playback Fa&deout on stop: None ms Stop playback on exit Stop playback on exit Inhibit suspend whilst playing If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Output <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> &Crossfade between tracks: s Replay &gain: About replay gain Use the checkboxes below to control the active outputs. Track Album Auto <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> PlaylistRule Type: Include songs that match the following: Exclude songs that match the following: Artist: Artists similar to: Album Artist: Composer: Album: Title: Genre From Year: Any To Year: Comment: Filename / path: Exact match Only enter values for the tags you wish to be search on. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Smart Rule Add <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules Add Edit Remove Songs with ratings between: - Songs with duration between: seconds Number of songs in play queue: Order songs: About Rules PlaylistRulesDialog Dynamic Rules Smart Rules No Limit Ascending Descending Name of Smart Rules Number of songs About dynamic rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 A set of rules named '%1' already exists! Overwrite? Overwrite Rules Saving %1 PlaylistsModel New Playlist... Stored Playlists Standard playlists %n Tracks (%1) %n Track (%1) %n Tracks (%1) Smart Playlist PodcastPage RSS: Website: Podcast details Select a podcast to display its details PodcastSearchDialog Subscribe Enter URL Manual podcast URL Search %1 Search for podcasts on %1 Add Podcast Subscription Browse %1 Browse %1 podcasts You are already subscribed to this podcast! Subscription added PodcastSearchPage Enter search term... Search Failed to fetch podcasts from %1 There was a problem parsing the response from %1 PodcastService Subscribe to RSS feeds %n Podcast(s) %n Podcast %n Podcasts %1 (%2) podcast name (num unplayed episodes) %n Episode(s) %n Episode %n Episodes (Downloading: %1%) Failed to parse %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Failed to download %1 PodcastSettingsDialog Check for new episodes: Download episodes to: Download automatically: Podcast Settings Manually Every 15 minutes Every 30 minutes Every hour Every 2 hours Every 6 hours Every 12 hours Every day Every week Don't automatically download episodes Latest episode Latest %1 episodes All episodes PodcastUrlPage URL Enter podcast URL... Load Enter podcast URL below, and press 'Load' Invalid URL! Failed to fetch podcast! Failed to parse podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. PodcastWidget Add Subscription Remove Subscription Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Do you wish to download the selected podcast episodes? Cancel podcast episode downloads (both current and any that are queued)? Do you wish to the delete downloaded files of the selected podcast episodes? Do you wish to mark the selected podcast episodes as new? Do you wish to mark the selected podcast episodes as listened? Refresh all subscriptions? Refresh Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track PreferencesDialog Collection Collection Settings Playback Playback Settings Downloaded Files Downloaded Files Settings Interface Interface Settings Info Info View Settings Scrobbling Scrobbling Settings Audio CD Audio CD Settings Proxy Proxy Settings Shortcuts Keyboard Shortcut Settings Cache Cached Items Custom Actions Cantata Preferences Configure ProxySettings Mode: Type: HTTP Proxy SOCKS Proxy Host: Port: Username: Password: No proxy Use the system proxy settings Manual proxy configuration QObject Track listing Read more on wikipedia Open in browser <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Expected average bitrate for variable bitrate encoding Smaller file Better sound quality <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Quality rating Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Bitrate Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Compression level Faster compression Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Empty filename. Invalid filename. (%1) Failed to save %1. Failed to delete rules file. (%1) Invalid command. (%1) Could not remove active rules link. Active rules is not a link. Could not create active rules link. Rules file, %1, does not exist. Incorrect arguments supplied. Unknown method called. Unknown error Artist SimilarArtists AlbumArtist Composer Comment Album Title Genre Date File Include Exclude (Exact) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 name width x height Current Cover CoverArt Archive Grouped Albums Table Parse in Library view, and show in Folders view Only show in Folders view Do not list Previous Track Next Track Play/Pause Stop Stop After Current Track Stop After Track Increase Volume Decrease Volume Save As Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Set Priority Highest Priority (255) High Priority (200) Medium Priority (125) Low Priority (50) Default Priority (0) Custom Priority... Add To Playlist Organize Files Organise Files Edit Track Information ReplayGain Copy Songs To Device Delete Songs Set Image Remove Find Add To Play Queue Parse error loading cache file, please check your songs tags. Other Default "%1" (%2:%3) name (host:port) Single Tracks Personal Unknown Various Artists Album artist Performer Track number Disc number Year Orignal Year Length <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album Invalid service Invalid method Authentication failed Invalid format Invalid parameters Invalid resource specified Operation failed Invalid session key Invalid API key Service offline Last.fm is currently busy, please try again in a few minutes Rate-limit exceeded General Digitally Imported Local and National Radio (ListenLive) &OK &Cancel &Yes &No &Discard &Save &Apply &Close &Help &Overwrite &Reset &Continue &Delete &Stop &Remove &Previous &Next Close Error Information Warning Question %1 B %1 kB %1 MB %1 GB %1 KiB %1 MiB %1 GiB Basic Tree (No Icons) Simple Tree Detailed Tree List Grid %n Track(s) %n Track %n Tracks %n Tracks (%1) %n Track (%1) %n Tracks (%1) %n Album(s) %n Album %n Albums %n Artist(s) %n Artist %n Artists %n Stream(s) %n Stream %n Streams %n Entry(s) %n Entry %n Entries %n Rule(s) %n Rule %n Rules %n Podcast(s) %n Podcast %n Podcasts %n Episode(s) %n Episode %n Episodes %n Update(s) available %n Update available %n Updates available RemoteDevicePropertiesDialog Device Properties Connection Music Library Add Device A remote device named '%1' already exists! Please choose a different name. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Type: Name: Options Host: Port: User: Domain: Password: Share: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Service name: Folder: Extra Options: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Samba Share Samba Share (Auto-discover host and port) Secure Shell (sshfs) Locally Mounted Folder RemoteFsDevice Available Not Available Failed to resolve connection details for %1 Connecting... Password prompting does not work when cantata is started from the commandline. No suitable ssh-askpass application installed! This is required for entering passwords. Mount point ("%1") is not empty! "sshfs" is not installed! Disconnecting... "fusermount" is not installed! Failed to connect to "%1" Failed to disconnect from "%1" Updating tracks... Not Connected Capacity Unknown %1 free RgDialog ReplayGain Show All Tracks Show Untagged Tracks Remove From List Artist Album Title Album Gain Track Gain Album Peak Track Peak Scan Update ReplayGain tags in tracks? Update Tags Abort scanning of tracks? Abort Abort reading of existing tags? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Do you wish to scan all tracks, or only tracks without existing tags? Untagged Tracks All Tracks Scanning tracks... Reading existing tags... %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: Device has been removed! Device is not connected. Device is busy? %1 dB Failed Original: %1 dB Original: %1 Remove the selected tracks from the list? Remove Tracks RulesPlaylists Album Artist Artist Album Composer Date Genre Rating File Age Random %n Rule(s) %n Rule %n Rules , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 ScrobblingLove %1: Loved Current Track %1: Love Current Track ScrobblingSettings Scrobble using: Username: Password: Status: Login Scrobble tracks Show 'Love' button %1 (via MPD) scrobbler name (via MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Authenticating... Authenticated Not Authenticated ScrobblingStatus %1: Scrobble Tracks SearchModel # (Track Number) SearchPage Locate In Library Artist: Composer: Performer: Album: Title: Genre: Comment: Date: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Any: No tracks found. %n Tracks (%1) %n Track (%1) %n Tracks (%1) SearchWidget Search... Close Search Bar ServerSettings Collection: Name: Host: Password: Music folder: Cover filename: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> HTTP stream URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Standard Basic Delete '%1'? Delete New Collection %1 Default ServiceStatusLabel Logged into %1 <b>NOT</b> logged into %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Shortcut for Selected Action Default: None Custom: SinglePageWidget Refresh View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Edit Remove Are you sure you wish to remove the selected rules? This cannot be undone. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cannot access song files! Please check that the device is still attached. SongView Lyrics Information Metadata Scroll Lyrics Refresh Lyrics Edit Lyrics Delete Lyrics File Refresh Track Information Cancel Track Reload lyrics? Reload from disk, or delete disk copy and download? Reload Reload From Disk Download Current playing song has changed, still perform search? Song Changed Perform Search Delete lyrics file? Delete File Artist Album artist Composer Lyricist Conductor Remixer Album Subtitle Track number Disc number Genre Date Original date Comment Copyright Label Catalogue number Title sort Artist sort Album artist sort Album sort Encoded by Encoder Mood Media Bitrate Sample rate Channels Tagging time Performer (%1) %1 kb/s %1 Hz Bits Performer Year Filename Fetching lyrics via %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Total space used: %1 SqlLibraryModel %n Artist(s) %n Artist %n Artists %n Album(s) %n Album %n Albums %n Tracks (%1) %n Track (%1) %n Tracks (%1) Cue Sheet Playlist StoredPlaylistsPage Rename Remove Duplicates Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Remove Playlists Playlist Name Enter a name for the playlist: A playlist named '%1' already exists! Overwrite? Overwrite Playlist Rename Playlist Enter new name for playlist: Cannot add songs from '%1' to '%2' StreamDialog Add stream to favourites Name: URL: Add Stream Edit Stream <i><b>ERROR:</b> Invalid protocol</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Update available Check the providers you wish to install/update. Install/Update Stream Providers Downloading list... Failed to download list of stream providers! Installing/updating %1 Failed to install '%1' Failed to download '%1' Install/update the selected stream providers? Install the selected stream providers? Update the selected stream providers? Install/Update Abort installation/update? Abort %n Update(s) available %n Update available %n Updates available Downloading %1 Update all updateable providers StreamSearchModel TuneIn ShoutCast Dirble Stream Search Search for radio streams Enter string to search Not Loaded Loading... %n Entry(s) %n Entry %n Entries StreamSearchPage Added '%1'' to favorites Added '%1'' to favourites StreamsBrowsePage Import Streams Into Favorites Import Streams Into Favourites Export Favorite Streams Export Favourite Streams Add New Stream To Favorites Add New Stream To Favourites Edit Seatch For Streams Configure Digitally Imported Service name Import Streams XML Streams (*.xml *.xml.gz *.cantata) Export Streams XML Streams (*.xml.gz) Failed to create '%1'! Stream '%1' already exists! A stream named '%1' already exists! Bookmark added Already bookmarked Already in favorites Already in favourites Reload '%1' streams? Are you sure you wish to remove bookmark to '%1'? Are you sure you wish to remove all '%1' bookmarks? Are you sure you wish to remove the %1 selected streams? Are you sure you wish to remove '%1'? Added '%1'' to favorites Added '%1'' to favourites StreamsModel Bookmarks TuneIn IceCast ShoutCast Dirble Favorites Favourites Bookmark Category Add Stream To Favorites Add Stream To Favourites Configure Digitally Imported Reload Streams Radio stations Not Loaded Loading... %n Entry(s) %n Entry %n Entries StreamsSettings Use the checkboxes below to configure the list of active providers. Built-in categories are shown in italic, and these cannot be removed. Configure Streams From File... Download... Configure Provider Install Remove Install Streams Cantata Streams (*.streams) A category named '%1' already exists! Overwrite? Failed top open package file. Invalid file format! Failed to create stream category folder! Failed to save stream list! Are you sure you wish to remove '%1'? Failed to remove streams folder! SyncCollectionWidget Search Check Items Uncheck Items SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Synchronise Device and library are in sync. Loading all songs from library, please wait...%1%... Local Music Library Properties Device has been removed! Device has been changed? Device is busy? TableView Stretch Columns To Fit Window Left Center Right Alignment TagEditor Track: Title: Artist: Album artist: Composer: Album: Track number: Disc number: Genre: Year: Rating: <i>(Various)</i> Comment: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Tags Tools Apply "Various Artists" Workaround Revert "Various Artists" Workaround Set 'Album Artist' from 'Artist' Capitalize Capitalise Adjust Track Numbers Read Ratings from File Write Ratings to File All tracks Apply "Various Artists" workaround to <b>all</b> tracks? Apply "Various Artists" workaround? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Revert "Various Artists" workaround <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> Revert Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Album Artist from Artist Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalise the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Capitalise the first letter of text fields (e.g. 'Title', 'Artist', etc)? Adjust the value of each track number by: Adjust track number by: Read ratings for all tracks from the music files? Read rating from music file? Ratings Read Ratings Read Rating Read, and updated, ratings from the following tracks: Not all Song ratings have been read from MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Song rating has not been read from MPD! Write ratings for all tracks to the music files? Write rating to music file? Write Ratings Write Rating Failed to write ratings of the following tracks: Failed to write rating to music file! All tracks [modified] %1 [modified] %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: Would you also like to rename your song files, so as to match your tags? Rename Files Rename Device has been removed! Device is not connected. Device is busy? TagSpinBox (Various) ThinSplitter Reset Spacing TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Selected: TrackOrganiser Filenames Filename scheme: VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names Original Name New Name Ratings will be lost if a file is renamed. Organize Files Organise Files Rename Remove From List Abort renaming of files? Abort Source file does not exist! Skip Auto Skip Destination file already exists! Failed to create destination folder! Failed to rename '%1' to '%2' Remove the selected tracks from the list? Remove Tracks Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Device has been removed! Device is not connected. Device is busy? TrayItem Cantata Now playing UltimateLyricsProvider (Polish Translations) (Portuguese Translations) UmsDevice Not Scanned Not Connected %1 free ValueSlider (recommended) View Cancel VolumeSlider Mute Unmute Volume %1% (Muted) Volume %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | album|score|soundtrack Search pattern for an album, separated by | WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Reload cantata-2.2.0/translations/cantata_es.ts000066400000000000000000023172021316350454000203310ustar00rootroot00000000000000 Refresh Album Information Actualizar información de álbum Album Álbum Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Pistas Refresh Artist Information Actualizar información de artista Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Artista Albums Álbumes Web Links Enlaces web Similar Artists Artistas similares Lyrics Providers Proveedores de letras Wikipedia Languages Idiomas de Wikipedia Other Otro Reset Spacing Restablecer espaciado &Artist &Artista Al&bum Ál&bum &Track &Pista Read more on last.fm Vea más en last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Si Cantata no encuentra letras, o encuentra una equivocada, utilice este diálogo para introducir detalles de la búsqueda. Por ejemplo, la canción actual puede ser una versión. Si es así, puede ser útil buscarlas letras por el autor original. Si está búsqueda ofrece letras nuevas, seguirán asociadas al título de la canción y artista original que Cantata muestra. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Título: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Artista: Search For Lyrics Búsqueda de letras Choose the websites you want to use when searching for lyrics. Seleccione los sitios web que utilizar para la búsqueda de letras. Song Information Información de canción Images (*.png *.jpg) Imágenes (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics Letras Information Información Scroll Lyrics Desplazar letras Refresh Lyrics Actualizar letras Edit Lyrics Editar letras Delete Lyrics File Eliminar fichero de letras Refresh Track Information Actualizar información de pista Cancel Cancelar Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Pista Reload lyrics? Reload from disk, or delete disk copy and download? ¿Desea recargar las letras? ¿Desea recargar la copia en el disco o eliminar la copia y descargar otra vez? Reload Actualizar Reload From Disk Recargar desde el disco Download Descargar Current playing song has changed, still perform search? La canción en reproducción ha cambiado, ¿quiere realizar la búsqueda? Song Changed Canción modificada Perform Search Realizar búsqueda Delete lyrics file? ¿Desea eliminar el fichero de letras? Delete File Eliminar fichero Album artist Artista del álbum Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Compositor Lyricist Letrista Conductor Director Remixer Remezclador Subtitle Subtítulo Track number Número de pista Disc number Número de disco Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Género Date Fecha Original date Fecha original Comment Comentario Copyright Derechos de autor Label Etiqueta Catalogue number Número de catálogo Title sort Ordenar por títuo Artist sort Ordenar por artista Album artist sort Ordenar por artista del álbum Album sort Ordenar por álbum Encoded by Codificado por Encoder Codificador Mood Mood Media Medio Bitrate Tasa de bits Sample rate Frecuencia de muestreo Channels Canales Tagging time Hora de etiquetado Performer (%1) Intérprete (%1) Performer Intérprete Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Año Fetching lyrics via %1 Obteniendo letras mediante %1 (Polish Translations) (Traducción al polaco) (Portuguese Translations) (Traducción al portugués) Track listing Lista de pistas Read more on wikipedia Lea más en Wikipedia Open in browser Abrir en explorador artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artista|banda|cantante|vocalista|músico album|score|soundtrack Search pattern for an album, separated by | albúm|pista|banda sonora Choose the wikipedia languages you want to use when searching for artist and album information. Seleccione los idiomas de Wikipeda que utilizar para la búsqueda de información de artista y álbum. Cantata is playing a track Cantata está reproduciendo una pista <b>INVALID</b> <b>NO VÁLIDO</b> <i>(When different)</i> <i>(Cuando hay diferencias)</i> Artists:%1, Albums:%2, Songs:%3 Artistas:%1, Álbumes:%2, Canciones:%3 %1 free %1 libre Local Music Library Biblioteca de música local Audio CD CD de audio Copy Songs To Device Copiar canciones a dispositivo Copy Songs Copiar canciones Delete Songs Eliminar canciones Not Configured Sin configuración Use Defaults Utilizar valores predefinidos Are you sure you wish to stop? ¿Está seguro de que desea detener? Stop Detener Device has been removed! El dispositivo se ha extraído. Device is not connected! El dispositivo no está conectado. Device is busy? ¿Está el dispositivo en uso? Device has been changed? ¿Ha cambiado el dispositivo? Clearing unused folders Eliminando directorios no utilizados Calculate ReplayGain for ripped tracks? ¿Desea calcular la ganancia de reproducción para pistas extraídas? ReplayGain Ganancia de reproducción Calculate Calcular The destination filename already exists! El nombre de fichero de destino ya existe. Song already exists! La canción ya existe. Song does not exist! La canción no existe. Failed to create destination folder!<br/>Please check you have sufficient permissions. No se pudo crear el directorio de destino.<br/>Compruebe que tiene los permisos necesarios. Source file no longer exists? ¿El fichero fuente ya no existe? Failed to copy. Fallo al copiar. Failed to delete. Fallo al eliminar. Not connected to device. No conectado al dispositivo. Selected codec is not available. El códec seleccionado no está disponible. Transcoding failed. Fallo al transcodificar. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Fallo al crear el fichero temporal.<br/>(Necesario para transcodificar a dispositivos MTP.) Failed to read source file. Fallo de lectura del fichero fuente. Failed to write to destination file. Fallo de escritura al fichero de destino. No space left on device. No hay espacio disponible en el dispositivo. Failed to update metadata. Fallo al actualizar metadatos. Failed to download track. Fallo al descargar la pista. Failed to lock device. Fallo de bloqueo de dispositivo. Local Music Library Properties Propiedades de la biblioteca de música local Error Fallo Skip Omitir Auto Skip Omitir automáticamente Retry Reintentar Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Álbum: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Pista: Source file: Fichero fuente: Destination file: Fichero destino: File: Fichero: Calculating... Calculando... %1 (Estimated) time (Estimated) %1 (Estimación) Time remaining: Tiempo restante: Saving cache Guardando almacén Apply "Various Artists" Workaround Arreglo para «Varios Artistas» Revert "Various Artists" Workaround Deshacer arreglo para «Varios Artistas» Capitalize Aplicar mayúsculas Adjust Track Numbers Ajustar números de pista Tools Herramientas Apply "Various Artists" workaround? ¿Desea aplicar el arreglo para «Various Artists»? Revert Revertir Adjust track number by: Ajustar números de pista según: Reading disc Leyendo disco CDDB CDDB MusicBrainz MusicBrainz Data Track Pista de datos Failed to open CD device Fallo al abrir el dispositivo de CD Track %1 Pista %1 Failed to create CDDB connection Fallo de conexión a CDDB No matches found in CDDB No se han encontrado coincidencias en CDDB CDDB error: %1 Fallo de CDDB: %1 Multiple matches were found. Please choose the relevant one from below: Se han encontrado varias coincidencias. Seleccione la coincidencia pertinente de la lista a continuación: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Título Disc Selection Selección de disco %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disco %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Actualizando (%1)... Updating (%1%)... Actualizando (%1%)... Device Properties Propiedades del dispositivo Don't copy covers No copiar carátulas Embed cover within each file Integrar carátula en cada fichero No maximum size Sin tamaño máximo 400 pixels 400 píxeles 300 pixels 300 píxeles 200 pixels 200 píxeles 100 pixels 100 píxeles <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Si la etiqueta «Artista del álbum» está definida como «Various Artists», Cantata definirá la etiqueta «Artista» como el valor de «Various Artists» y la etiqueta «Título» como «Artista de pista - Título de pista».<hr/> Al copiar desde un dispositivo, Cantata comprobará si «Artista del álbum» y «Artista» están definidos como «Various Artists». Si es así, intentará extraer el artista real de la etiqueta «Título», y eliminará el nombre de artista de la etiqueta «Título».</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Si se activa, Cantata creará un almacén de la biblioteca de música del dispositivo. Esto agilizará las exploraciones futuras de la biblioteca de música (ya que se usa el fichero de almacén en lugar de analizar las etiquetas de cada fichero.)<hr/><b>NOTA:</b> Si utiliza otra aplicación para actualizar la biblioteca del dispositivo, este caché quedará obsoleto. Para rectificar esto, pulse el icono de actualización en la lista de dispositivos. Esto elimina el fichero de almacén e inicia un nuevo análisis del contenido del dispositivo.</p> Do not transcode No transcodificar Transcode to %1 Transcodificar a %1 %1 (%2 free) name (size free) %1 (%2 libre) Copy To Library Copiar a biblioteca Forget Device Olvidar dispositivo Add Device Añadir dispositivo Lookup album and track details? ¿Desea consultar los detalles de álbum y pista? Refresh Actualizar Via CDDB Mediante CDDB Via MusicBrainz Mediante MusicBrainz Partial Parcial Full Completa Eject Expulsar Disconnect Desconectar Please close other dialogs first. Primero cierre otras ventanas de diálogo. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) es un códec patentado con pérdida de datos para audio digital.<br>Habitualmente, AAC ofrece una calidad de audio superior a MP3 con una tasa de bits similar. Es un elección razonable para los dispositivos iPod y otros reproductores de música portátiles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>AAC</b> utilizado por Cantata permite una <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida dee la tasa de bits en esta barra deslizante es una estimación de la <a href=http://www.ffmpeg.org/faq.html#SEC21>tasa de bits media</a> de la pista codificada.<br><b>150kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>120kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>200kb/s</b> es posiblemente demasiado. Expected average bitrate for variable bitrate encoding Tasa de bits media esperada para la codificación con tasa de bits variable Smaller file Fichero más pequeño Better sound quality Mayor calidad de audio <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://es.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) es un códec de audio digital patentado que utiliza una forma de compresión con pérdida de datos.<br>A pesar de sus limitaciones, es un formato común para el almacenamiento de audio, y generalmente compatible con todos los reproductores de música portátiles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>MP3</b> utilizado por Cantata permite una <a href=http://en.wikipedia.org/wiki/MP3#VBR>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida de la tasa de bits en esta barra deslizante es una estimación de la tasa de bits media de la pista codificada.<br><b>160kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>120kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>205kb/s</b> es posiblemente demasiado. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://es.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> es un códec de audio abierto y exento de regalías.<br>Genera ficheros más pequeños que MP3 con una calidad similar o superior. Ogg Vorbis es una excelente elección, en particular con reproductores de música portátiles compatibles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>Vorbis</b> utilizado por Cantata admite la opción de <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>tasa de bits variable (VBR)</a>; esto es el valor de la tasa de bits varía a lo largo de la pista en base a la complejidad del contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los menos complejos. Este enfoque ofrece una calidad general superior y un fichero más pequeño que el uso de una tasa de bits constante en toda la pista.<br>El codificador Vorbis utiliza una clasificación entre -1 y 10 para definir un nivel de calidad de audio esperado. La medida de la tasa de bits en esta barra deslizante es una estimación (ofrecida por Vorbis) de la tasa de bits media para la pista codificada en base al valor de calidad dado. De hecho, las versiones más recientes y eficientes de Vorbis ofrecen una tasa de bits real menor.<br><b>5</b> es una buena elección para la reproducción de audio en un dispositivo portátil.<br/>Un valor menor a <b>3</b> puede ser insatisfactorio y un valor superior a <b>8</b> es posiblemente demasiado. Quality rating Clasificación de calidad Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> es un códec de audio digital sin patente que utiliza una forma de compresión de datos con pérdida. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>Opus</b> utilizado por Cantata permite una <a href=http://es.wikipedia.org/wiki/Tasa_de_bits_variable>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida de la tasa de bits en esta barra deslizante es una estimación de la tasa de bits media de la pista codificada.<br><b>128kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>100kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>256kb/s</b> es posiblemente demasiado. Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://es.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) es un códec de audio para la compresión de audio digital sin pérdida de datos.<br>Solo se recomienda para reproductores de música Apple y reproductores no compatibles con FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://es.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) es un códec libre y exento de regalías para la compresión de audio digital sin pérdida de datos.<br>Si desea almacenar la música sin perder calidad de audio, FLAC es una elección excelente. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. El <a href=http://flac.sourceforge.net/documentation_tools_flac.html>nivel de compresión</a> es un valor entero entre cero y ocho que representa la relación entre el tamaño del fichero y la velocidad de compresión al codificar con <b>FLAC</b>.<br/> Definir el nivel de compresión como <b>0</b> ofrece un tiempo de compresión menor pero genera un fichero comparativamente más grande.<br/>Por otra parte, un nivel de compresión de <b>8</b> ralentiza la compresión pero genera un fichero más pequeño.<br/>Tenga en cuenta que debido a que FLAC es por definición un códec sin pérdida de datos, la calidad de audio de la salida es idéntica sin importar el nivel de compresión.<br/>Así mismo, los niveles superiores a <b>5</b> aumentan sustancialmente el tiempo de compresión pero generan un fichero superficialmente menor, y no se recomiendan. Compression level Nivel de compresión Faster compression Compresión más rápida Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) es un códec privativo desarrollado por Microsoft para la compresión de audio sin pérdida de datos.<br>Solo se recomienda para dispositivos de reproducción de audio no compatibles con Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. La tasa de datos es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>Debido a las limitaciones del formato privativo <b>WMA</b> y la dificultad que presenta la ingeniería inversa de un codificador privativo, el codificador WMA utilizado por Cantata activa la opción de <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>tasa de bits constante (CBR)</a>.<br>Por ello, la medida de la tasa de bits de la barra deslizante es una estimación muy precisa de la pista coficada.<br><b>136kb/s</b> es una buena opción para la reproducción de audio en un dispositivo portátil.<br/>Un valor inferior a <b>112kb/s</b> puede ser insatisfactorio, y un valor superior a <b>182kb/s</b> es posiblemente demasiado. Filename Scheme Esquema de nombre de fichero Various Artists Example album artist Varios artistas Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variable</em></th><th><em>Botón</em></th><th><em>Descripción</em></th></tr> Updating... Actualizando... Reading cache Leyendo caché %1 %2% Message percent %1 %2% Connecting to device... Conectando al dispositivo... No devices found No se han encontrado dispositivos Connected to device Conectado al dispositivo Disconnected from device Desconectado del dispositivo Updating folders... Actualizando directorios... Updating files... Actualizando ficheros... Updating tracks... Actualizando pistas... Not Connected Sin conexión %1 (Disc %2) %1 (Disco %2) No matches found in MusicBrainz No se han encontrado coincidencias en MusicBrainz Connection Conexión Music Library Biblioteca de música Samba Share Recurso compartido Samba Samba Share (Auto-discover host and port) Samba Share (Detectar automáticamente host y port) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Directorio montado localmente Available Disponible Not Available No disponible Failed to resolve connection details for %1 Fallo al determinar los detalles de conexión para %1 Connecting... Conectando... Password prompting does not work when cantata is started from the commandline. La petición de contraseña no funciona cuando cantata se ejecuta desde la línea de órdenes. No suitable ssh-askpass application installed! This is required for entering passwords. No se ha detectado ninguna aplicación «ssh-askpass» adecuada. Se requiere para introducir contraseñas. Mount point ("%1") is not empty! El punto de montaje («%1») no está vacío. "sshfs" is not installed! sshfs no está instalado. Disconnecting... Desconectando... "fusermount" is not installed! fusermount no está instalado. Failed to connect to "%1" Fallo de conexión con «%1» Failed to disconnect from "%1" Fallo de conexión con «%1» Capacity Unknown Capacidad desconocida Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Buscar Check Items Seleccionar elementos Uncheck Items Deseleecionar elementos Synchronize Sincronizar Device and library are in sync. El dispositivo y la biblioteca están sincronizados. Not Scanned No se ha analizado (recommended) (recomendado) Empty filename. Nombre de fichero vacío. Invalid filename. (%1) Nombre de fichero inválido. (%1) Failed to save %1. Fallo al guardar %1. Failed to delete rules file. (%1) Fallo al eliminar el fichero de normas. (%1) Invalid command. (%1) Orden no válida. (%1) Could not remove active rules link. No se pudo eliminar el enlace de normas activas. Active rules is not a link. Las normas activas no son un enlace. Could not create active rules link. No se pudo crear el enlace de normas activas. Rules file, %1, does not exist. El fichero de reglas, %1, no existe. Incorrect arguments supplied. Argumentos incorrectos introducidos. Unknown method called. Invocado método desconocido. Unknown error Error desconocido Start Dynamic Playlist Iniciar lista de reproducción dinámica Stop Dynamic Mode Detener modo dinámico Dynamic Playlists Listas de reproducción dinámicas You need to install "perl" on your system in order for Cantata's dynamic mode to function. Es necesario instalar Perl en su sistema para el funcionamiento del modo dinámico de Cantata. Failed to locate rules file - %1 No se ha podido detectar el fichero de normas - %1 Failed to remove previous rules file - %1 No se ha podido eliminar el fichero anterior de normas - %1 Failed to install rules file - %1 -> %2 No se ha podido instalar el fichero de normas - %1 -> %2 Dynamizer has been terminated. El dinamizador se ha interrumpido. Saving rule Guardando norma Deleting rule Eliminando norma Awaiting response for previous command. (%1) Esperando la respuesta de la orden anterior. (%1) Failed to save %1. (%2) Fallo al guardar %1. (%2) Failed to control dynamizer state. (%1) Fallo de control del estado del dinamizador. (%1) Failed to set the current dynamic rules. (%1) Fallo al definir la lista de normas dinámicas. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Añadir Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Editar Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Eliminar Remote dynamizer is not running. El dinamizador remoto no está activo. Remove Dynamic Rules Eliminar normas dinámicas Dynamic Rule Norma dinámica <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR</b>: 'Desde el año' debe ser menor que 'Al año'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> El rango de datos es demasiado grande (solo puede ser un máximo de %1 años)</i> SimilarArtists Artistas similares AlbumArtist Artista del álbum Include Incluir Exclude Excluir (Exact) (Exacto) Dynamic Rules Normas dinámicas None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Ninguno Failed to save %1 Fallo al guardar %1 Overwrite Rules Sobreescribir normas Saving %1 Guardando %1 Deleting... Eliminando… Name Nombre Item Count Recuento de elementos Space Used Espacio utilizado Total space used: %1 Espacio total utilizado: %1 Covers Carátulas Scaled Covers Carátulas escaladas Backdrops Fondos Artist Information Información de artista Album Information Información de albúm Track Information Información de pista Stream Listings Listados de flujos Podcast Directories Directorios de podcast Scrobble Tracks Registrar pistas escuchadas Delete All Eliminar todo Delete all '%1' items? ¿Desea eliminar los '%1' elementos? Delete Cache Items Eliminar los elementos de la caché %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Carátula actual CoverArt Archive Almacen de carátulas Image Imagen Downloading... Descargando... Image (%1 x %2 %3%) Image (width x height zoom%) Imagen (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Ya existe una imagen para este artista, y el fichero no permite la escritura. A cover already exists for this album, and the file is not writeable. Ya existe una carátula para este artista, y el fichero no permite la escritura. '%1' Artist Image '%1' Imagen de artista '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Carátula de álbum Failed to download image! Fallo al descargar la imagen. Load Local Cover Cargar carátula local File is already in list! El fichero ya está listado. Failed to read image! Fallo al leer la imagen Display Mostrar Searching... Buscando... Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Nombre: Open In File Manager Abrir en el explorador de ficheros Connection Established Conexión realizada Connection Failed Fallo de conexión Grouped Albums Álbumes agrupados Table Tabla Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Reproducir lista de reproducción Folders Directorios Playlists Listas de reproducción Devices - UMS, MTP (e.g. Android), and AudioCDs Dispositivos - UMS, MTP (p.ej. Android), y CDs de audio Search (via MPD) Búsqueda (mediante MPD) Info - Current song information (artist, album, and lyrics) Info - Información de pista actual (artista, álbum y letras) Large Grande Small Pequeña Tab-bar Barra de pestañas Left Izquierda Right Derecha Top Arriba Bottom Abajo System default Predefinido por el sistema Album, Artist, Year Álbum, Artista, Año Album, Year, Artist Álbum, Año, Artista Artist, Album, Year Artista, Álbum, Año Artist, Year, Album Artista, Año, Álbum Year, Album, Artist Año, Álbum, Artista Year, Artist, Album Año, Artista, Álbum Configure Cantata... Configurar Cantata... Preferences Preferencias Quit Cerrar About Cantata... Qt-only Acerca de Cantata... Show Window Mostrar ventana Server information... Información del servidor... Refresh Database Actualizar base de datos Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Conectar Collection Colección Outputs Salidas Stop After Track Detener después de la pista Add To Stored Playlist Añadir a lista de reproducción guardada Add Stream URL Añadir URL de flujo Clear Vaciar Center On Current Track Centrar en pista actual Expanded Interface Interfaz expandida Show Current Song Information Mostrar información de la canción actual Full Screen Pantalla completa Random Aleatorio Repeat Repetir Single Pistas únicas When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Cuando se activa «Pistas únicas», la reproducción se detiene tras la canción actual, o se repite si se activa el modo de repetición. Consume Eliminar tras reproducir When consume is activated, a song is removed from the play queue after it has been played. Cuando se activa esta opción, se elimina la canción de la cola de reproducción tras su reproducción Play Stream Reproducir flujo Locate In Library Ubicar en biblioteca Expand All Expandir todo Collapse All Contraer todo Devices Dispositivos Info Información Show Menubar Mostrar barra de menú &Music &Música &Edit &Editar &View &Ver &Queue &Cola &Settings A&justes &Help A&yuda Failed to locate any songs matching the dynamic playlist rules. No se ha detectado ninguna canción que coincida con las normas de lista dinámica de reproducción. Connecting to %1 Conectando a %1 Refresh MPD Database? ¿Desea actualizar la base de datos de MPD? About Cantata Qt-only Acerca de Cantata Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Fondos de vista contextual gracias a <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Metadatos de vista contextual gracias a <a href="http://www.wikipedia.org">Wikipedia</a> y <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Considere enviar su propio fan-art de música a <a href="http://www.fanart.tv">FanArt.tv</a> Server Information Información del servidor <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Servidor</b></td></tr><tr><td align="right">Protocolo:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Tiempo de actividad:&nbsp;</td><td>%4</td></tr><tr><td align="right">Reproduciendo:&nbsp;</td><td>%5</td></tr><tr><td align="right">Manipuladores:&nbsp;</td><td>%6</td></tr><tr><td align="right">Etiquetas:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Base de datos</b></td></tr><tr><td align="right">Artistas:&nbsp;</td><td>%1</td></tr><tr><td align="right">Álbumes:&nbsp;</td><td>%2</td></tr><tr><td align="right">Canciones:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duración:&nbsp;</td><td>%4</td></tr><tr><td align="right">Actualizado:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD ha informado del siguiente fallo: %1 Cantata Cantata Playback stopped Reproducción detenida Remove all songs from play queue? ¿Desea eliminar todas las canciones de la cola de reproducción? Priority Prioridad Enter priority (0..255): Introduzca la prioridad (0...255): Playlist Name Nombre de lista de reproducción Enter a name for the playlist: Introduzca un nombre para la lista de reproducción: Existing Playlist Lista de reproducción existente Auto Automático <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Conectando a %1.</b>Las entradas a continuación afectan a la colección de MPD conectada actualmente.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>No conectado.</b>Las entradas a continuación no se pueden modificar, ya que Cantata no está conectado a MPD.</i> Rename Renombrar Remove Duplicates Eliminar duplicados Remove Playlists Eliminar listas de reproducción Overwrite Playlist Sobrescribir la lista de reproducción Rename Playlist Renombrar lista de reproducción Enter new name for playlist: Introduzca un nombre nuevo para la lista de reproducción: Cannot add songs from '%1' to '%2' No se puede añadir canciones desde '%1' a '%2' 1 Track 1 pista %1 pistas %1 Tracks 1 Track (%2) 1 pista (%2) %1 pistas (%2) %1 Tracks (%2) 1 Album 1 álbum %1 álbumes %1 Albums 1 Artist 1 artista %1 artistas %1 Artists 1 Stream 1 Flujo %1 Flujos %1 Streams 1 Entry 1 entrada %1 entradas %1 Entries 1 Rule 1 norma %1 normas %1 Rules 1 Podcast 1 Podcast %1 Podcasts %1 Podcasts 1 Episode 1 Episodio %1 Episodios %1 Episodes 1 Update available 1 actualización disponible %1 actualizaciones disponibles %1 Updates available Collection Settings Ajustes de collección Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Reproducción Playback Settings Opciones de reproducción Interface Interfaz Interface Settings Configuración de la interfaz Scrobbling Scrobbling Scrobbling Settings Ajustes de scrobbling Audio CD Settings Configuración de CD de audio Proxy Proxy Proxy Settings Qt-only Ajustes de proxy Shortcuts Qt-only Atajos de teclado Keyboard Shortcut Settings Qt-only Configuración de atajos de teclado Cache Almacén Cached Items Elementos almacenados Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Configuración Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Compositor: Performer: Intérprete: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Género: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Comentario: Date: Fecha: Any: Cualquiera: No tracks found. No se han encontrado pistas. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Host: Add Collection Añadir colección Standard Estándar Basic Básico Delete Eliminar New Collection %1 Colección nueva %1 Default Predefinido Previous Track Pista anterior Next Track Pista siguiente Play/Pause Reproducir/Pausa Stop After Current Track Detener después de la pista actual Increase Volume Subir volumen Decrease Volume Bajar volumen Save As Guardar como Add With Priority Añadir con prioridad Set Priority Definir prioridad Highest Priority (255) Prioridad máxima (255) High Priority (200) Prioridad alta (200) Medium Priority (125) Prioridad media (125) Low Priority (50) Prioridad baja (50) Default Priority (0) Prioridad predefinida (0) Custom Priority... Prioridad personalizada... Add To Playlist Añadir a lista de reproducción Organize Files Organizar ficheros Set Image Establecer imagen Find Buscar Add To Play Queue Añadir a lista de reproducción Now playing En reproducción Cue Sheet Hoja cue Playlist Lista de reproducción Configure Device Configurar dispositivo Refresh Device Actualizar dispositivo Connect Device Conecte el dispositivo Disconnect Device Desconecte el dispositivo Edit CD Details Editar detalles de CD No Devices Attached No hay dispositivos conectados Not logged in No ha iniciado una sesión Logged in Sesión iniciada No subscriptions Ninguna suscripción You do not have an active subscription No tiene ninguna suscripción activa Logged in (expiry:%1) Sesión iniciada (finalización:%1) Session expired Sesión finalizada %1 by %2 Album by Artist %1 por %2 New Playlist... Nueva lista de reproducción… Smart Playlist Lista de reproducción inteligente Length Duración Disc Disco Undo Deshacer Redo Rehacer Shuffle Orden aleatorio Sort By Ordenar por Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Artista del álbum Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Nombre de pista TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Not Loaded No se ha cargado Loading... Cargando… Bookmarks Favoritos IceCast IceCast Favorites Favoritos Bookmark Category Categoría de favorito Add Stream To Favorites Añadir flujo a favoritos Streams Flujos Unknown Desconocido "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed Fallo de conexión a %1 Connection to %1 failed - please check your proxy settings Fallo de conexión a %1 failed - compruebe los ajustes de proxy Connection to %1 failed - incorrect password Fallo de conexión a %1 - contraseña incorrecta Failed to send command to %1 - not connected Fallo al enviar la orden a %1: no conectado Failed to load. Please check user "mpd" has read permission. Fallo al cargar. Compruebe que el usuario «mpd» tiene permisos de lectura. Failed to load. MPD can only play local files if connected via a local socket. Fallo al cargar. MPD solo puede reproducir ficheros locales si se conecta a través de un fichero socket local. Failed to send command. Disconnected from %1 Fallo al enviar la orden. Desconectado de %1 Failed to rename <b>%1</b> to <b>%2</b> Fallo al renombrar <b>%1</b> to <b>%2</b> Failed to save <b>%1</b> Fallo al crear <b>%1</b> You cannot add parts of a cue sheet to a playlist! No puede añadir secciones de una hoja CUE a una lista de reproducción. You cannot add a playlist to another playlist! No puede añadir una lista de reproducción a otra. Failed to send '%1' to %2. Please check %2 is registered with MPD. Fallo al enviar '%1' a %2. Compruebe que %2 está registrado con MPD. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Pistas únicas Personal Personal Various Artists Varios artistas <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> en <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> por <b>%2</b> en <b>%3</b> No proxy Sin proxy Use the system proxy settings Utilizar las opciones de proxy del sistema Manual proxy configuration Configuración manual de proxy Jamendo Settings Configuración de Jamendo MP3 MP3 Ogg Ogg Streaming format: Formato de flujo: Streaming Emisión de flujo MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Magnatune Settings Configuración de Magnatune Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Nombre de usuario: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Contraseña: Membership: Datos de la cuenta: Downloads: Descargas: Failed to parse Fallo de análisis Failed to download Fallo de descarga RSS: RSS: Website: Sitio web: Podcast details Detalles del podcast Select a podcast to display its details Seleccione un podcast para mostrar sus detalles Enter search term... Introduzca el término de búsqueda... Failed to fetch podcasts from %1 No se pudo obtener podcasts desde %1 There was a problem parsing the response from %1 Se ha detectado un problema al analizar al respuesta de %1 Failed to download directory listing No se pudo descargar el listado de directorio Failed to parse directory listing No se pudo analizar el listado de directorio URL URL Enter podcast URL... Introduzca la URL de podcast... Load Cargar Enter podcast URL below, and press 'Load' Introduzca a continuación la URL de podcast y pulse 'Cargar' Invalid URL! URL no válida. Failed to fetch podcast! No se pudo obtener el podcast. Failed to parse podcast. No se puedo analizar el podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata solo admite podcasts de audio. La URL introducida solo contiene podcasts de vídeo. Subscribe Suscribir Enter URL Introduzca la URL Manual podcast URL URL de podcast manual Search %1 Buscar %1 Search for podcasts on %1 Buscar podcasts en %1 Add Podcast Subscription Añadir suscripción de podcast Browse %1 Explorar %1 Browse %1 podcasts Explorar %1 podcasts You are already subscribed to this podcast! Ya hay una suscripción a este podcast. Subscription added Suscripción añadida %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Descargando: %1%) Failed to parse %1 No se pudo analizar %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata solo admite podcasts de audio. %1 solo contiene podcasts de vídeo. Failed to download %1 No se pudo descargar %1 Check for new episodes: Comprobar si hay episodios nuevos: Download episodes to: Descargar episodios a: Podcast Settings Ajustes de podcast Manually Manualmente Every 15 minutes Cada 15 minutos Every 30 minutes Cada 30 minutos Every hour Cada hora Every 2 hours Cada 2 horas Every 6 hours Cada 6 horas Every 12 hours Cada 12 horas Every day Cada día Every week Cada semana Add Subscription Añadir suscripción Remove Subscription Eliminar suscripción Do you wish to download the selected podcast episodes? ¿Desea descargar los episodios de podcast seleccionados? Do you wish to the delete downloaded files of the selected podcast episodes? ¿Desea eliminar los ficheros descargados de los episodios de podcast seleccionados? Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Imagen de fondo Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Imagen de artista Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Imagen seleccionada: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Difuminar: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Opacidad: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) Cambiar de vista automáticamente después de: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) No cambiar automáticamente ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Fondo oscuro Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Fondo oscurecido y texto blanco sin importar la paleta de color actual. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Siempre colapsar en un solo panel Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Mostrar solo el texto básico de Wikipedia Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Disponible: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Seleccionado: Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Copiar canciones desde: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Requiere configuración) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Copiar canciones a: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Formato destino: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Sobrescribir canciones To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Para copiar: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Detalles del álbum Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Año: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Disco: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Único artista Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Obtención de información de álbum y pista Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Consulta inicial mediante: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) Host CDDB: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) Puerto CDDB: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Buscar información al introducir el CD. Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Extracción de audio Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Modo «full» de paranoia (máxima calidad) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) No omitir por fallo de lectura Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Directorio de música: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Copiar carátulas de álbum como: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Tamaño máximo de carátula: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Volumen predefinido: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) Arreglo para 'Various Artists' Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Analizar música de forma automática al conectar Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Utilizar caché Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Nombres de fichero Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Esquema de nombre de fichero: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) Compatibilidad con VFAT Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Utilizar solo caracteres ASCII Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Sustituir espacios con guiones bajos Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Transcodificación Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Solo transcodificar si el fichero fuente tiene un formato diferente Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Ejemplo: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) Acerca de esquemas de nombre de fichero The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) El artista del album. Para la mayoría de álbumes, es igual a <i>Artista de pista.</i> Para compilaciones, habitualmente es <i>Varios artistas.</i> The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) El nombre del álbum. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Nombre de álbum The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) El compositor. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) El artista de cada pista. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Artista de pista The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) El título de pista (sin <i>Artista de pista</i>). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) El título de pista (con <i>Artista de pista</i>, si es distinto a <i>Artista de álbum</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Nombre de pista (+Artista) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) El número de pista. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Núm. de pista The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) El número de álbum de un álbum de varios discos. Habitualmente, las compilaciones consisten en varios discos. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) Núm. de CD The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) El año de publicación del album. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) El género del álbum. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Tipo: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Opciones Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Puerto: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Usuario: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Dominio: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Recurso compartido: Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Nombre del servicio: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Directorio: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Opciones adicionales: Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Nombre de las normas dinámicas seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) segundos About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) Acerca de las normas Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Incluir las canciones que coinciden con lo siguiente: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Excluir las canciones que coinciden con lo siguiente: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Artistas similares a: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Artista del álbum: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) Del año: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Todo To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) Al año: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Coincidencia exacta Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Añadir fichero local Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Guardar letras descargadas en el directorio de música Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Guardar fondos descargados en el directorio de música Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Primera ejecución de Cantata Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Bienvenido a Cantata <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Bienvenido a Cantata</p></body></html> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Configuración estándar para varios usuarios/servidores Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Configuración básica para un usuario Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Detalles de conexión The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Los ajustes a continuación son los ajustes básicos que Cantata requiere. Introduzca los detalles pertinentes y utilice el botón «Conectar» para probar la conexión. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Directorio de música Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Seleccione la carpeta que contiene su colección de música. <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata descargará las carátulas faltantes y las letras desde Internet.</p><p>Para cada uno de ellos, confirme si se desea que Cantata guarde los ficheros pertinentes dentro del directorio de música, o dentro de sus directorios personales cache/config.</p> Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Finalizado Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata ya está configurado.<br/><br/>El diálogo de configuración de Cantata se puede utilizar para personalizar la apariencia de Cantata, y para añadir sistemas MPD adicionales, etc. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>Advertencia:</b> No es un miembro del grupo «users». Cantata funciona mejor (almacenar portadas de álbum, letras y más con los permisos adecuados) si Ud. o el administrador incluyen al usuario en el grupo. Si añade su propio usuario tendrá que reiniciar la sesión para que tenga efecto. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Barra lateral Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Vistas Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Estilo: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Posición: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Mostrar solo iconos, sin texto Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Ocultar automáticamente Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Colapsar álbumes en el inicio Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Expandir de forma automática el álbum actual Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Desplazar a pista actual Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) Consultar antes de eliminar Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Carátula de álbum actual External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Externo Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Mostrar mensajes emergentes al cambiar de pista Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Mostrar icono en el área de notificación Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Minimizar al área de notificación al cerrar On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) Al iniciar Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Mostrar ventana principal Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) Ocultar ventana principal Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Restablecer estado anterior General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) General Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) Obtener carátulas faltantes de Last.fm Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Mostrar la acción de eliminar en menús contextuales Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Forzar la activación con un solo clic de los elementos Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) Adaptar la interfaz a una interacción táctil Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Idioma: [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dinámico] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Salir de pantalla completa Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Detener reproducción al cerrar Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) Inhibir la suspensión durante la reproducción Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Salida s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) Acerca de la ganancia de reproducción Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Utilice las casillas a continuación para controlar las salidas activas. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Colección: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Nombre de fichero de carátula: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Nombre de fichero (sin extensión) para guardar las carátulas descargadas.<br/>Si no se define, se utiliza «cover».<br/><br/><i>%artist% se sustituirá por el artista de álbum de la canción actual, y %album% se sustituirá por el nombre del álbum.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) URL de flujo HTTP: Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Modo: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy HTTP SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy SOCKS Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Utilice las casillas a continuación para configurar la lista de servicios activos. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Configurar servicio Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Registrar pistas reproducidas mediante: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Estado: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Iniciar sesión Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Registrar pistas reproducidas Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Mostrar el botón "Love" You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Puede escuchar sin coste sin una cuenta, pero los miembros Premium pueden escuchar flujos de calidad mayor sin anuncios. Visite <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> para pasar a una cuenta premium. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Cuenta premium Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Tipo de flujo: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Hora de finalización de sesión: Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Utilice las casillas a continuación para configurar la lista de proveedores activos. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Buscar: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Atajo para la acción seleccionada Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Predefinido: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Personalizado: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Artista del álbum: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Número de pista: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Número de disco: Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Nombre original New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Nombre nuevo Your names NAME OF TRANSLATORS Omar Campagne Polaino Your emails EMAIL OF TRANSLATORS ocampagne@gmail.com Show All Tracks Mostrar todas las pistas Show Untagged Tracks Mostrar pistas sin etiquetar Remove From List Eliminar de la lista Album Gain Ganancia de reproducción del álbum Track Gain Ganancia de reproducción de pista Album Peak Punto más alto del álbum Track Peak Punto más alto del pista Scan Analizar Update ReplayGain tags in tracks? ¿Desea actualizar las etiquetas ReplayGain de las pistas? Update Tags Actualizar etiquetas Abort scanning of tracks? ¿Desea interrumpir el análisis de las pistas? Abort Cancelar Abort reading of existing tags? ¿Desea cancelar la lectura de las etiquetas existentes? Do you wish to scan all tracks, or only tracks without existing tags? ¿Desea analizar todas las pistas, o solo aquellas sin etiquetas? Untagged Tracks Pistas sin etiquetar All Tracks Todas las pistas Scanning tracks... Analizando pistas... Reading existing tags... Leyendo etiquetas existentes... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (¿Etiquetas corruptas?) Failed to update the tags of the following tracks: Fallo al actualizar las etiquetas de las siguientes pistas: Device is not connected. El dispositivo no está conectado. %1 dB %1 dB Failed Fallo Original: %1 dB Original: %1 dB Original: %1 Original: %1 Remove the selected tracks from the list? ¿Eliminar las pistas seleccionadas de la lista? Remove Tracks Eliminar pistas Invalid service Servicio no válido Invalid method Método no válido Authentication failed Fallo de identificación Invalid format Formato no válido Invalid parameters Parámetros no válidos Invalid resource specified Recurso no válido especificado Operation failed Fallo de la operación Invalid session key Clave de sesión no válida Invalid API key Clave API no válida Service offline Servicio no disponible Last.fm is currently busy, please try again in a few minutes Last.fm está actualmente ocupada, vuelva a intentarlo en unos minutos Rate-limit exceeded Límite de tasa superado %1 error: %2 %1 error: %2 %1: Loved Current Track %1: Pista actual marcada como favorito %1: Love Current Track %1: Marcar pista actual como favorito %1 (via MPD) scrobbler name (via MPD) %1 (mediante MPD) Authenticating... Identificándose... Authenticated Identificado Not Authenticated No identificado %1: Scrobble Tracks %1: Registrar pistas escuchadas Digitally Imported Settings Configuración importada digitalmente MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Finalizar sesión URL: URL: Add Stream Añadir flujo Edit Stream Editar flujo <i><b>ERROR:</b> Invalid protocol</i> <i><b>ERROR:</b>Protocolo no válido</i> Installed Instalado Update available Actualización disponible Check the providers you wish to install/update. Seleccione los proveedores que desea instalar/actualizar. Install/Update Stream Providers Instalar/actualizar proveedores de flujo Downloading list... Descargando lista... Digitally Imported Importado digitalmente Local and National Radio (ListenLive) Radio local y nacional (ListenLive) Failed to download list of stream providers! Fallo al descargar la lista de proveedores de flujos. Installing/updating %1 Instalando/actualizando %1 Install/Update Instalar/Actualizar Abort installation/update? ¿Desea cancelar la instalación/actualización? Downloading %1 Descargando %1 Update all updateable providers Actualizar todos los proveedores actualizables Import Streams Into Favorites Importar flujos a favoritos Export Favorite Streams Exportar flujos favoritos Add New Stream To Favorites Añadir flujo nuevo a favoritos Digitally Imported Service name Importado digitalmente Import Streams Importar flujos XML Streams (*.xml *.xml.gz *.cantata) Flujos XML (*.xml *.xml.gz *.cantata) Export Streams Exportar flujos XML Streams (*.xml.gz) Flujos XML (*.xml.gz) Bookmark added Favorito añadido Already bookmarked Ya está en favoritos Already in favorites Ya está en favoritos Are you sure you wish to remove the %1 selected streams? ¿Desea eliminar los %1 flujos seleccionados? Added '%1'' to favorites Añadido '%1' a favoritos Configure Streams Configuración de flujos From File... Del fichero... Download... Descargar... Configure Provider Configurar proveedor Install Instalar Install Streams Instalar flujos Cantata Streams (*.streams) Flujos de Cantata (*.streams) Invalid file format! Formato de fichero no válido. Failed to create stream category folder! No se pudo crear el directorio de categoría de flujo. Failed to save stream list! No se pudo crear la lista de flujo. Failed to remove streams folder! No se pudo eliminar el directorio de flujos. &OK &Aceptar &Cancel &Cancelar &Yes &Si &No &No &Discard &Descartar &Save &Guardar &Apply A&plicar &Close C&errar &Overwrite S&obrescribir &Reset &Restablecer &Continue Con&tinuar &Delete &Eliminar &Stop &Parar &Remove &Eliminar &Previous Anter&ior &Next Sig&uiente Configure... Configurar... Password Contraseña Please enter password: Introduzca la contraseña: Warning Advertencia Question Pregunta Select Folder Seleccione el directorio Select File Seleccione el fichero %1 B %1 B %1 kB %1 Kb %1 MB %1 Mb %1 GB %1 Gb %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags Etiquetas Set 'Album Artist' from 'Artist' Definir «Artista del álbum» como «Artista» All tracks Todas las pistas Apply "Various Artists" workaround to <b>all</b> tracks? ¿Desea aplicar el arreglo para «Varios Artistas» a <b>todas</b> las pistas? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Esto define «Artista del álbum» y «Artista» con el valor de «Varios Artistas», y «Título» con el de «Artista de pista - Título de pista»</i> Revert "Various Artists" workaround on <b>all</b> tracks? ¿Desea deshacer el arreglo para «Varios Artistas» a <b>todas</b> las pistas? Revert "Various Artists" workaround Deshacer arreglo para «Varios Artistas» <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Si «Artista del álbum» es igual a «Artista» y «Título» tiene el formato «Artista de pista - Título de pista», el valor para «Artista» se tomará de «Título», y «Título» se definirá solo con el título de pista. Por ejemplo:<br/><br/>si «Título» es «Wibble - Wobble», entonces «Artista» se define como «Wibble», y «Título» se define como «Wobble»</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? ¿Quiere definir «Artista del álbum» como «Artista» (si «Artista del álbum» está vacío) para <b>todas</b> las pistas? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? ¿Quiere definir «Artista del álbum» como «Artista» (si «Artista del álbum» está vacío)? Album Artist from Artist «Artista del álbum» como «Artista» Adjust the value of each track number by: Ajustar el valor de cada número de pista en: All tracks [modified] Todas las pistas [modificado] %1 [modified] %1 [modificado] Would you also like to rename your song files, so as to match your tags? ¿Desea renombrar los ficheros de audio para que coincidan con las etiquetas? Rename Files Renombrar ficheros Abort renaming of files? ¿Cancelar la modificación de los ficheros? <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Compositor:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artista:</b></td><td>%1</td></tr><tr><td align="right"><b>Álbum:</b></td><td>%2</td></tr><tr><td align="right"><b>Año:</b></td><td>%3</td></tr> Filter On Genre Filtrar por género All Genres Todos los géneros Go Back Retroceder Menu Menú (Stream) (Flujo) Search... Buscar… Close Search Bar Cerrar barra de búsqueda Logged into %1 Identificado en %1 <b>NOT</b> logged into %1 <b>NO</b> identificado en %1 Basic Tree (No Icons) Árbol básico (sin iconos) Simple Tree Árbol sencillo Detailed Tree Árbol con detalles List Lista Stretch Columns To Fit Window Estirar columnas para adaptarse a la ventana (Various) (Varios) Mute Silenciar Unmute Desactivar silenciar Volume %1% (Muted) Volumen: %1 % (silenciado) Volume %1% Volumen %1% 1 Track Singular 1 Pista %1 Tracks Plural (N!=1) %1 pistas 1 Track (%1) Singular 1 pista (%1) %1 Tracks (%2) Plural (N!=1) %1 pistas (%2) 1 Album Singular 1 Álbum %1 Albums Plural (N!=1) %1 álbumes 1 Artist Singular 1 Artista %1 Artists Plural (N!=1) %1 artistas 1 Stream Singular 1 Flujo %1 Streams Plural (N!=1) %1 flujos 1 Entry Singular 1 Entrada %1 Entries Plural (N!=1) %1 entradas 1 Rule Singular 1 norma %1 Rules Plural (N!=1) %1 normas 1 Podcast Singular 1 Podcast %1 Podcasts Plural (N!=1) %1 Podcasts 1 Episode Singular 1 Episodio %1 Episodes Plural (N!=1) %1 Episodios 1 Update available Singular 1 actualización disponible %1 Updates available Plural (N!=1) %1 actualizaciones disponibles ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Copiar canciones desde: Configure Configuración (Needs configuring) (Requiere configuración) Copy songs to: Copiar canciones a: Destination format: Formato destino: Overwrite songs Sobrescribir canciones To copy: Para copiar: <b>INVALID</b> <b>NO VÁLIDO</b> <i>(When different)</i> <i>(Cuando hay diferencias)</i> Artists:%1, Albums:%2, Songs:%3 Artistas:%1, Álbumes:%2, Canciones:%3 %1 free %1 libre Local Music Library Biblioteca de música local Audio CD CD de audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Copy Songs To Library Copy Songs To Device Copiar canciones a dispositivo Copy Songs Copiar canciones Delete Songs Eliminar canciones You have not configured the destination device. Continue with the default settings? Not Configured Sin configuración Use Defaults Utilizar valores predefinidos You have not configured the source device. Continue with the default settings? Are you sure you wish to stop? ¿Está seguro de que desea detener? Stop Detener Device has been removed! El dispositivo se ha extraído. Device is not connected! El dispositivo no está conectado. Device is busy? ¿Está el dispositivo en uso? Device has been changed? ¿Ha cambiado el dispositivo? Clearing unused folders Eliminando directorios no utilizados Calculate ReplayGain for ripped tracks? ¿Desea calcular la ganancia de reproducción para pistas extraídas? ReplayGain Ganancia de reproducción Calculate Calcular The destination filename already exists! El nombre de fichero de destino ya existe. Song already exists! La canción ya existe. Song does not exist! La canción no existe. Failed to create destination folder!<br/>Please check you have sufficient permissions. No se pudo crear el directorio de destino.<br/>Compruebe que tiene los permisos necesarios. Source file no longer exists? ¿El fichero fuente ya no existe? Failed to copy. Fallo al copiar. Failed to delete. Fallo al eliminar. Not connected to device. No conectado al dispositivo. Selected codec is not available. El códec seleccionado no está disponible. Transcoding failed. Fallo al transcodificar. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Fallo al crear el fichero temporal.<br/>(Necesario para transcodificar a dispositivos MTP.) Failed to read source file. Fallo de lectura del fichero fuente. Failed to write to destination file. Fallo de escritura al fichero de destino. No space left on device. No hay espacio disponible en el dispositivo. Failed to update metadata. Fallo al actualizar metadatos. Failed to download track. Fallo al descargar la pista. Failed to lock device. Fallo de bloqueo de dispositivo. Local Music Library Properties Propiedades de la biblioteca de música local Error Fallo Skip Omitir Auto Skip Omitir automáticamente Retry Reintentar Artist: Artista: Album: Álbum: Track: Pista: Source file: Fichero fuente: Destination file: Fichero destino: File: Fichero: Saving cache Guardando almacén Calculating... Calculando... Time remaining: Tiempo restante: AlbumDetails Album Details Detalles del álbum Artist: Artista: Composer: Compositor: Title: Título: Genre: Género: Year: Año: Disc: Disco: Single artist Único artista Tracks Pistas Track Pista Artist Artista Title Título AlbumDetailsDialog Audio CD CD de audio Apply "Various Artists" Workaround Arreglo para «Varios Artistas» Revert "Various Artists" Workaround Deshacer arreglo para «Varios Artistas» Capitalize Aplicar mayúsculas Adjust Track Numbers Ajustar números de pista Tools Herramientas Apply "Various Artists" workaround? ¿Desea aplicar el arreglo para «Various Artists»? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Revert Revertir Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Adjust track number by: Ajustar números de pista según: AlbumView Refresh Album Information Actualizar información de álbum Album Álbum Tracks Pistas ArtistView Refresh Artist Information Actualizar información de artista Artist Artista Albums Álbumes Web Links Enlaces web Similar Artists Artistas similares AudioCdDevice Reading disc Leyendo disco %n Tracks (%1) 1 pista (%1) %n pistas (%1) AudioCdSettings Album and Track Information Retrieval Obtención de información de álbum y pista Initially look up via: Consulta inicial mediante: CDDB Host: Host CDDB: CDDB Port: Puerto CDDB: Lookup information as soon as CD is inserted Buscar información al introducir el CD. Audio Extraction Extracción de audio Full paranoia mode (best quality) Modo «full» de paranoia (máxima calidad) Never skip on read error No omitir por fallo de lectura CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Hoja cue Playlist Lista de reproducción CacheItem Deleting... Eliminando… Calculating... Calculando... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Carátulas Scaled Covers Carátulas escaladas Backdrops Fondos Lyrics Letras Artist Information Información de artista Album Information Información de albúm Track Information Información de pista Stream Listings Listados de flujos Podcast Directories Directorios de podcast Wikipedia Languages Idiomas de Wikipedia Scrobble Tracks Registrar pistas escuchadas Delete All Eliminar todo Delete all '%1' items? ¿Desea eliminar los '%1' elementos? Delete Cache Items Eliminar los elementos de la caché Delete items from all selected categories? CacheTree Name Nombre Item Count Recuento de elementos Space Used Espacio utilizado CddbInterface Data Track Pista de datos Failed to open CD device Fallo al abrir el dispositivo de CD Track %1 Pista %1 Failed to create CDDB connection Fallo de conexión a CDDB Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB No se han encontrado coincidencias en CDDB CDDB error: %1 Fallo de CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Se han encontrado varias coincidencias. Seleccione la coincidencia pertinente de la lista a continuación: Artist Artista Title Título Disc Selection Selección de disco %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disco %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Proveedores de letras Wikipedia Languages Idiomas de Wikipedia Other Otro ContextWidget &Artist &Artista Al&bum Ál&bum &Track &Pista CoverDialog Search Buscar Add a local file Añadir fichero local Configure Configuración This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive Almacen de carátulas An image already exists for this artist, and the file is not writeable. Ya existe una imagen para este artista, y el fichero no permite la escritura. A cover already exists for this album, and the file is not writeable. Ya existe una carátula para este artista, y el fichero no permite la escritura. '%1' Artist Image '%1' Imagen de artista '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Carátula de álbum Failed to set cover! Could not download to temporary file! Failed to download image! Fallo al descargar la imagen. Load Local Cover Cargar carátula local Images (*.png *.jpg) Imágenes (*.png *.jpg) File is already in list! El fichero ya está listado. Failed to read image! Fallo al leer la imagen Display Mostrar Remove Eliminar Failed to set cover! Could not make copy! Failed to set cover! Could not backup original! Failed to set cover! Could not copy file to '%1'! Searching... Buscando... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Compositor:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artista:</b></td><td>%1</td></tr><tr><td align="right"><b>Álbum:</b></td><td>%2</td></tr><tr><td align="right"><b>Año:</b></td><td>%3</td></tr> CoverPreview Image Imagen Downloading... Descargando... Image (%1 x %2 %3%) Image (width x height zoom%) Imagen (%1 x %2 %3%) CustomActionDialog Name: Nombre: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Añadir Edit Editar Remove Eliminar Name Nombre Command Remove the selected commands? Device Updating (%1)... Actualizando (%1)... Updating (%1%)... Actualizando (%1%)... DevicePropertiesDialog Device Properties Propiedades del dispositivo DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Name: Nombre: Music folder: Directorio de música: Copy album covers as: Copiar carátulas de álbum como: Maximum cover size: Tamaño máximo de carátula: Default volume: Volumen predefinido: 'Various Artists' workaround Arreglo para 'Various Artists' Automatically scan music when attached Analizar música de forma automática al conectar Use cache Utilizar caché Filenames Nombres de fichero Filename scheme: Esquema de nombre de fichero: VFAT safe Compatibilidad con VFAT Use only ASCII characters Utilizar solo caracteres ASCII Replace spaces with underscores Sustituir espacios con guiones bajos Append 'The' to artist names If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Transcodificación Only transcode if source file is of a different format Solo transcodificar si el fichero fuente tiene un formato diferente Only transcode if source is FLAC/WAV Don't copy covers No copiar carátulas Embed cover within each file Integrar carátula en cada fichero No maximum size Sin tamaño máximo 400 pixels 400 píxeles 300 pixels 300 píxeles 200 pixels 200 píxeles 100 pixels 100 píxeles <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Si la etiqueta «Artista del álbum» está definida como «Various Artists», Cantata definirá la etiqueta «Artista» como el valor de «Various Artists» y la etiqueta «Título» como «Artista de pista - Título de pista».<hr/> Al copiar desde un dispositivo, Cantata comprobará si «Artista del álbum» y «Artista» están definidos como «Various Artists». Si es así, intentará extraer el artista real de la etiqueta «Título», y eliminará el nombre de artista de la etiqueta «Título».</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Si se activa, Cantata creará un almacén de la biblioteca de música del dispositivo. Esto agilizará las exploraciones futuras de la biblioteca de música (ya que se usa el fichero de almacén en lugar de analizar las etiquetas de cada fichero.)<hr/><b>NOTA:</b> Si utiliza otra aplicación para actualizar la biblioteca del dispositivo, este caché quedará obsoleto. Para rectificar esto, pulse el icono de actualización en la lista de dispositivos. Esto elimina el fichero de almacén e inicia un nuevo análisis del contenido del dispositivo.</p> Do not transcode No transcodificar Encoder Codificador Transcode to %1 Transcodificar a %1 %1 (%2 free) name (size free) %1 (%2 libre) DevicesModel Configure Device Configurar dispositivo Refresh Device Actualizar dispositivo Connect Device Conecte el dispositivo Disconnect Device Desconecte el dispositivo Edit CD Details Editar detalles de CD Not Connected Sin conexión No Devices Attached No hay dispositivos conectados DevicesPage Copy To Library Copiar a biblioteca Synchronise Forget Device Olvidar dispositivo Add Device Añadir dispositivo Lookup album and track details? ¿Desea consultar los detalles de álbum y pista? Refresh Actualizar Via CDDB Mediante CDDB Via MusicBrainz Mediante MusicBrainz Which type of refresh do you wish to perform? Partial - Only new songs are scanned (quick) Full - All songs are rescanned (slow) Partial Parcial Full Completa Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs Eliminar canciones Are you sure you wish to forget '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Eject Expulsar Are you sure you wish to disconnect '%1'? Disconnect Desconectar Please close other dialogs first. Primero cierre otras ventanas de diálogo. DigitallyImported Not logged in No ha iniciado una sesión Logged in Sesión iniciada Unknown error Error desconocido No subscriptions Ninguna suscripción You do not have an active subscription No tiene ninguna suscripción activa Logged in (expiry:%1) Sesión iniciada (finalización:%1) Session expired Sesión finalizada DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Puede escuchar sin coste sin una cuenta, pero los miembros Premium pueden escuchar flujos de calidad mayor sin anuncios. Visite <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> para pasar a una cuenta premium. Premium Account Cuenta premium Username: Nombre de usuario: Password: Contraseña: Stream type: Tipo de flujo: Status: Estado: Login Iniciar sesión Session expiry: Hora de finalización de sesión: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Digitally Imported Settings Configuración importada digitalmente MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated No identificado Authenticating... Identificándose... Authenticated Identificado Logout Finalizar sesión DockMenu Play Pause DynamicPlaylists Start Dynamic Playlist Iniciar lista de reproducción dinámica Stop Dynamic Mode Detener modo dinámico Dynamic Playlists Listas de reproducción dinámicas Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Es necesario instalar Perl en su sistema para el funcionamiento del modo dinámico de Cantata. Failed to locate rules file - %1 No se ha podido detectar el fichero de normas - %1 Failed to remove previous rules file - %1 No se ha podido eliminar el fichero anterior de normas - %1 Failed to install rules file - %1 -> %2 No se ha podido instalar el fichero de normas - %1 -> %2 Dynamizer has been terminated. El dinamizador se ha interrumpido. Awaiting response for previous command. (%1) Esperando la respuesta de la orden anterior. (%1) Saving rule Guardando norma Deleting rule Eliminando norma Failed to save %1. (%2) Fallo al guardar %1. (%2) Failed to delete rules file. (%1) Fallo al eliminar el fichero de normas. (%1) Failed to control dynamizer state. (%1) Fallo de control del estado del dinamizador. (%1) Failed to set the current dynamic rules. (%1) Fallo al definir la lista de normas dinámicas. (%1) DynamicPlaylistsPage Add Añadir Edit Editar Remove Eliminar Remote dynamizer is not running. El dinamizador remoto no está activo. Are you sure you wish to remove the selected rules? This cannot be undone. Remove Dynamic Rules Eliminar normas dinámicas FancyTabWidget Configure... Configurar... FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Guardar letras descargadas en el directorio de música Save downloaded backdrops in music folder Guardar fondos descargados en el directorio de música If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: Ejemplo: About filename schemes Acerca de esquemas de nombre de fichero The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> El artista del album. Para la mayoría de álbumes, es igual a <i>Artista de pista.</i> Para compilaciones, habitualmente es <i>Varios artistas.</i> Album Artist Artista del álbum The name of the album. El nombre del álbum. Album Title Nombre de álbum The composer. El compositor. Composer Compositor The artist of each track. El artista de cada pista. Track Artist Artista de pista The track title (without <i>Track Artist</i>). El título de pista (sin <i>Artista de pista</i>). Track Title Nombre de pista The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). El título de pista (con <i>Artista de pista</i>, si es distinto a <i>Artista de álbum</i>). Track Title (+Artist) Nombre de pista (+Artista) The track number. El número de pista. Track # Núm. de pista The album number of a multi-album album. Often compilations consist of several albums. El número de álbum de un álbum de varios discos. Habitualmente, las compilaciones consisten en varios discos. CD # Núm. de CD The year of the album's release. El año de publicación del album. Year Año The genre of the album. El género del álbum. Genre Género Filename Scheme Esquema de nombre de fichero Various Artists Example album artist Varios artistas Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variable</em></th><th><em>Botón</em></th><th><em>Descripción</em></th></tr> FolderPage Open In File Manager Abrir en el explorador de ficheros Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs Eliminar canciones FsDevice Updating... Actualizando... Reading cache Leyendo caché Saving cache Guardando almacén %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Filtrar por género All Genres Todos los géneros GroupedViewDelegate Audio CD CD de audio Streams Flujos %n Track(s) 1 Pista %n pistas InitialSettingsWizard Cantata First Run Primera ejecución de Cantata Welcome to Cantata Bienvenido a Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Bienvenido a Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup Configuración estándar para varios usuarios/servidores <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup Configuración básica para un usuario <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details Detalles de conexión The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Los ajustes a continuación son los ajustes básicos que Cantata requiere. Introduzca los detalles pertinentes y utilice el botón «Conectar» para probar la conexión. Host: Host: Password: Contraseña: Music folder: Directorio de música: Connect Conectar The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Music folder Directorio de música Please choose the folder containing your music collection. Seleccione la carpeta que contiene su colección de música. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata descargará las carátulas faltantes y las letras desde Internet.</p><p>Para cada uno de ellos, confirme si se desea que Cantata guarde los ficheros pertinentes dentro del directorio de música, o dentro de sus directorios personales cache/config.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Guardar letras descargadas en el directorio de música Save downloaded backdrops in music folder Guardar fondos descargados en el directorio de música If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Finished! Finalizado Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata ya está configurado.<br/><br/>El diálogo de configuración de Cantata se puede utilizar para personalizar la apariencia de Cantata, y para añadir sistemas MPD adicionales, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Advertencia:</b> No es un miembro del grupo «users». Cantata funciona mejor (almacenar portadas de álbum, letras y más con los permisos adecuados) si Ud. o el administrador incluyen al usuario en el grupo. Si añade su propio usuario tendrá que reiniciar la sesión para que tenga efecto. Not Connected Sin conexión Connection Established Conexión realizada Connection Failed Fallo de conexión Cantata will now terminate InputDialog Password Contraseña Please enter password: Introduzca la contraseña: InterfaceSettings Sidebar Barra lateral Views Vistas Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Opciones Style: Estilo: Position: Posición: Only show icons, no text Mostrar solo iconos, sin texto Auto-hide Ocultar automáticamente Play Queue Reproducir lista de reproducción Initially collapse albums Colapsar álbumes en el inicio Automatically expand current album Expandir de forma automática el álbum actual Scroll to current track Desplazar a pista actual Prompt before clearing Consultar antes de eliminar Separate action (and shortcut) for play queue search Background Image Imagen de fondo None Ninguno Current album cover Carátula de álbum actual Custom image: Imagen seleccionada: Blur: Difuminar: 10px 10px Opacity: Opacidad: 40% 40% Toolbar Show stop button Show cover of current track Show track rating External Externo Enable MPRIS D-BUS interface Show popup messages when changing tracks Mostrar mensajes emergentes al cambiar de pista Show icon in notification area Mostrar icono en el área de notificación Minimize to notification area when closed Minimizar al área de notificación al cerrar On Start-up Al iniciar Show main window Mostrar ventana principal Hide main window Ocultar ventana principal Restore previous state Restablecer estado anterior Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks Pistas únicas If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General General Fetch missing covers from Last.fm Obtener carátulas faltantes de Last.fm Show delete action in context menus Mostrar la acción de eliminar en menús contextuales Enforce single-click activation of items Forzar la activación con un solo clic de los elementos Changing the style setting will require a re-start of Cantata. Make interface more touch friendly Adaptar la interfaz a una interacción táctil Show song information tooltips Language: Idioma: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Library Folders Directorios Playlists Listas de reproducción Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Dispositivos - UMS, MTP (p.ej. Android), y CDs de audio Search (via MPD) Búsqueda (mediante MPD) Info - Current song information (artist, album, and lyrics) Info - Información de pista actual (artista, álbum y letras) Large Grande Small Pequeña Tab-bar Barra de pestañas Left Izquierda Right Derecha Top Arriba Bottom Abajo Images (*.png *.jpg) Imágenes (*.png *.jpg) 10px pixels 10px Notifications English (en) System default Predefinido por el sistema %1% value% %1% %1 px pixels %1 px ItemView Go Back Retroceder Updating... Actualizando... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings Configuración de Jamendo MP3 MP3 Ogg Ogg Streaming format: Formato de flujo: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Ninguno Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm Vea más en last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Nombre Year Año Album, Artist, Year Álbum, Artista, Año Album, Year, Artist Álbum, Año, Artista Artist, Album, Year Artista, Álbum, Año Artist, Year, Album Artista, Año, Álbum Year, Album, Artist Año, Álbum, Artista Year, Artist, Album Año, Artista, Álbum Modified Date Group By Genre Género Artist Artista Album Álbum Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs Eliminar canciones LyricSettings Choose the websites you want to use when searching for lyrics. Seleccione los sitios web que utilizar para la búsqueda de letras. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Si Cantata no encuentra letras, o encuentra una equivocada, utilice este diálogo para introducir detalles de la búsqueda. Por ejemplo, la canción actual puede ser una versión. Si es así, puede ser útil buscarlas letras por el autor original. Si está búsqueda ofrece letras nuevas, seguirán asociadas al título de la canción y artista original que Cantata muestra. Title: Título: Artist: Artista: Search For Lyrics Búsqueda de letras MPDConnection Unknown Desconocido Connection to %1 failed Fallo de conexión a %1 Connection to %1 failed - please check your proxy settings Fallo de conexión a %1 failed - compruebe los ajustes de proxy Connection to %1 failed - incorrect password Fallo de conexión a %1 - contraseña incorrecta Connecting to %1 Conectando a %1 Failed to send command to %1 - not connected Fallo al enviar la orden a %1: no conectado Failed to load. Please check user "mpd" has read permission. Fallo al cargar. Compruebe que el usuario «mpd» tiene permisos de lectura. Failed to load. MPD can only play local files if connected via a local socket. Fallo al cargar. MPD solo puede reproducir ficheros locales si se conecta a través de un fichero socket local. MPD reported the following error: %1 MPD ha informado del siguiente fallo: %1 Failed to send command. Disconnected from %1 Fallo al enviar la orden. Desconectado de %1 Failed to rename <b>%1</b> to <b>%2</b> Fallo al renombrar <b>%1</b> to <b>%2</b> Failed to save <b>%1</b> Fallo al crear <b>%1</b> You cannot add parts of a cue sheet to a playlist! No puede añadir secciones de una hoja CUE a una lista de reproducción. You cannot add a playlist to another playlist! No puede añadir una lista de reproducción a otra. Failed to send '%1' to %2. Please check %2 is registered with MPD. Fallo al enviar '%1' a %2. Compruebe que %2 está registrado con MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Ninguno Streaming Emisión de flujo MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Configuración de Magnatune Username: Nombre de usuario: Password: Contraseña: Membership: Datos de la cuenta: Downloads: Descargas: MainWindow [Dynamic] [Dinámico] Exit Full Screen Salir de pantalla completa Configure Cantata... Configurar Cantata... Preferences Preferencias Quit Cerrar About Cantata... Acerca de Cantata... Show Window Mostrar ventana Server information... Información del servidor... Refresh Database Actualizar base de datos Refresh Actualizar Connect Conectar Collection Colección Outputs Salidas Stop After Track Detener después de la pista Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist Añadir a lista de reproducción guardada Crop Others Add Stream URL Añadir URL de flujo Clear Vaciar Center On Current Track Centrar en pista actual Expanded Interface Interfaz expandida Show Current Song Information Mostrar información de la canción actual Full Screen Pantalla completa Random Aleatorio Repeat Repetir Single Pistas únicas When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Cuando se activa «Pistas únicas», la reproducción se detiene tras la canción actual, o se repite si se activa el modo de repetición. Consume Eliminar tras reproducir When consume is activated, a song is removed from the play queue after it has been played. Cuando se activa esta opción, se elimina la canción de la cola de reproducción tras su reproducción Find in Play Queue Play Stream Reproducir flujo Locate In Library Ubicar en biblioteca Play next Edit Track Information (Play Queue) Expand All Expandir todo Collapse All Contraer todo Cancel Cancelar Play Queue Reproducir lista de reproducción Library Folders Directorios Playlists Listas de reproducción Internet Devices Dispositivos Search Buscar Info Información Show Menubar Mostrar barra de menú &Music &Música &Edit &Editar &View &Ver &Queue &Cola &Settings A&justes &Help A&yuda Set Rating No Rating Failed to locate any songs matching the dynamic playlist rules. No se ha detectado ninguna canción que coincida con las normas de lista dinámica de reproducción. Connecting to %1 Conectando a %1 Refresh MPD Database? ¿Desea actualizar la base de datos de MPD? About Cantata Acerca de Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Fondos de vista contextual gracias a <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Metadatos de vista contextual gracias a <a href="http://www.wikipedia.org">Wikipedia</a> y <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Considere enviar su propio fan-art de música a <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Abort download and quit Please close other dialogs first. Primero cierre otras ventanas de diálogo. Enabled: %1 Disabled: %1 Server Information Información del servidor <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Servidor</b></td></tr><tr><td align="right">Protocolo:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Tiempo de actividad:&nbsp;</td><td>%4</td></tr><tr><td align="right">Reproduciendo:&nbsp;</td><td>%5</td></tr><tr><td align="right">Manipuladores:&nbsp;</td><td>%6</td></tr><tr><td align="right">Etiquetas:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Base de datos</b></td></tr><tr><td align="right">Artistas:&nbsp;</td><td>%1</td></tr><tr><td align="right">Álbumes:&nbsp;</td><td>%2</td></tr><tr><td align="right">Canciones:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duración:&nbsp;</td><td>%4</td></tr><tr><td align="right">Actualizado:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD ha informado del siguiente fallo: %1 Cantata Cantata Playback stopped Reproducción detenida Remove all songs from play queue? ¿Desea eliminar todas las canciones de la cola de reproducción? Priority Prioridad Enter priority (0..255): Introduzca la prioridad (0...255): Decrease priority for each subsequent track Playlist Name Nombre de lista de reproducción Enter a name for the playlist: Introduzca un nombre para la lista de reproducción: '%1' is used to store favorite streams, please choose another name. A playlist named '%1' already exists! Add to that playlist? Existing Playlist Lista de reproducción existente %n Track(s) 1 Pista %n pistas %n Tracks (%1) 1 pista (%1) %n pistas (%1) MenuButton Menu Menú MessageOverlay Cancel Cancelar Mpris (Stream) (Flujo) MtpConnection Connecting to device... Conectando al dispositivo... No devices found No se han encontrado dispositivos Connected to device Conectado al dispositivo Disconnected from device Desconectado del dispositivo Updating folders... Actualizando directorios... Updating files... Actualizando ficheros... Updating tracks... Actualizando pistas... MtpDevice Not Connected Sin conexión %1 free %1 libre MusicBrainz Failed to open CD device Fallo al abrir el dispositivo de CD Track %1 Pista %1 %1 (Disc %2) %1 (Disco %2) No matches found in MusicBrainz No se han encontrado coincidencias en MusicBrainz MusicLibraryModel Cue Sheet Hoja cue Playlist Lista de reproducción %n Track(s) 1 Pista %n pistas %n Artist(s) 1 Artista %n artistas %n Album(s) 1 Álbum %n álbumes %n Tracks (%1) 1 pista (%1) %n pistas (%1) %1 by %2 Album by Artist %1 por %2 NoteLabel <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) (Flujo) OSXStyle &Window Close Minimize Zoom OnlineDbService Downloading...%1% Parsing music list.... Failed to download Fallo de descarga %n Artist(s) 1 Artista %n artistas OnlineDbWidget Group By Genre Género Artist Artista Configure Configuración The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Descargar Re-download music listing? OnlineSearchService Searching... Buscando... OnlineSearchWidget No tracks found. No se han encontrado pistas. %n Tracks (%1) 1 pista (%1) %n pistas (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Utilice las casillas a continuación para configurar la lista de servicios activos. Configure Service Configurar servicio OnlineView Song Information Información de canción OnlineXmlParser Failed to parse Fallo de análisis OpmlBrowsePage Reload Actualizar Failed to download directory listing No se pudo descargar el listado de directorio Failed to parse directory listing No se pudo analizar el listado de directorio OtherSettings Background Image Imagen de fondo None Ninguno Artist image Imagen de artista Custom image: Imagen seleccionada: Blur: Difuminar: 10px 10px Opacity: Opacidad: 40% 40% Automatically switch to view after: Cambiar de vista automáticamente después de: Do not auto-switch No cambiar automáticamente ms ms Dark background Fondo oscuro Darken background, and use white text, regardless of current color palette. Fondo oscurecido y texto blanco sin importar la paleta de color actual. Always collapse into a single pane Siempre colapsar en un solo panel Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text Mostrar solo el texto básico de Wikipedia Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Images (*.png *.jpg) Imágenes (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Seleccione el directorio Select File Seleccione el fichero PlayQueueModel Title Título Artist Artista Album Álbum # Track number Length Duración Disc Disco Year Año Original Year Genre Género Priority Prioridad Composer Compositor Performer Intérprete Rating Remove Duplicates Eliminar duplicados Undo Deshacer Redo Rehacer Shuffle Orden aleatorio Tracks Pistas Albums Álbumes Sort By Ordenar por Album Artist Artista del álbum Track Title Nombre de pista Track Number # (Track Number) PlayQueueView Remove Eliminar PlaybackSettings Playback Reproducción Fa&deout on stop: None Ninguno ms ms Stop playback on exit Detener reproducción al cerrar Inhibit suspend whilst playing Inhibir la suspensión durante la reproducción If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Output Salida <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>No conectado.</b>Las entradas a continuación no se pueden modificar, ya que Cantata no está conectado a MPD.</i> &Crossfade between tracks: s s Replay &gain: About replay gain Acerca de la ganancia de reproducción Use the checkboxes below to control the active outputs. Utilice las casillas a continuación para controlar las salidas activas. Track Pista Album Álbum Auto Automático <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Conectando a %1.</b>Las entradas a continuación afectan a la colección de MPD conectada actualmente.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> PlaylistRule Type: Tipo: Include songs that match the following: Incluir las canciones que coinciden con lo siguiente: Exclude songs that match the following: Excluir las canciones que coinciden con lo siguiente: Artist: Artista: Artists similar to: Artistas similares a: Album Artist: Artista del álbum: Composer: Compositor: Album: Álbum: Title: Título: Genre Género From Year: Del año: Any Todo To Year: Al año: Comment: Comentario: Filename / path: Exact match Coincidencia exacta Only enter values for the tags you wish to be search on. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Norma dinámica Smart Rule Add Añadir <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR</b>: 'Desde el año' debe ser menor que 'Al año'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> El rango de datos es demasiado grande (solo puede ser un máximo de %1 años)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules Nombre de las normas dinámicas Add Añadir Edit Editar Remove Eliminar Songs with ratings between: - Songs with duration between: seconds segundos Number of songs in play queue: Order songs: About Rules Acerca de las normas PlaylistRulesDialog Dynamic Rules Normas dinámicas None Ninguno No Limit About dynamic rules Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 Fallo al guardar %1 A set of rules named '%1' already exists! Overwrite? Overwrite Rules Sobreescribir normas Saving %1 Guardando %1 PlaylistsModel New Playlist... Nueva lista de reproducción… Stored Playlists Standard playlists %n Tracks (%1) 1 pista (%1) %n pistas (%1) Smart Playlist Lista de reproducción inteligente Plurals %n Track(s) 1 Pista %n pistas %n Tracks (%1) 1 pista (%1) %n pistas (%1) %n Album(s) 1 Álbum %n álbumes %n Artist(s) 1 Artista %n artistas %n Stream(s) 1 Flujo %n flujos %n Entry(s) 1 Entrada %n entradas %n Rule(s) 1 norma %n normas %n Podcast(s) 1 Podcast %n Podcast %n Episode(s) 1 Episodio %n Episodios %n Update(s) available 1 actualización disponible %n actualizaciones disponibles PodcastPage RSS: RSS: Website: Sitio web: Podcast details Detalles del podcast Select a podcast to display its details Seleccione un podcast para mostrar sus detalles PodcastSearchDialog Subscribe Suscribir Enter URL Introduzca la URL Manual podcast URL URL de podcast manual Search %1 Buscar %1 Search for podcasts on %1 Buscar podcasts en %1 Add Podcast Subscription Añadir suscripción de podcast Browse %1 Explorar %1 Browse %1 podcasts Explorar %1 podcasts You are already subscribed to this podcast! Ya hay una suscripción a este podcast. Subscription added Suscripción añadida PodcastSearchPage Enter search term... Introduzca el término de búsqueda... Search Buscar Failed to fetch podcasts from %1 No se pudo obtener podcasts desde %1 There was a problem parsing the response from %1 Se ha detectado un problema al analizar al respuesta de %1 PodcastService Subscribe to RSS feeds %n Podcast(s) 1 Podcast %n Podcast %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) 1 Episodio %n Episodios (Downloading: %1%) (Descargando: %1%) Failed to parse %1 No se pudo analizar %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata solo admite podcasts de audio. %1 solo contiene podcasts de vídeo. Failed to download %1 No se pudo descargar %1 PodcastSettingsDialog Check for new episodes: Comprobar si hay episodios nuevos: Download episodes to: Descargar episodios a: Download automatically: Podcast Settings Ajustes de podcast Manually Manualmente Every 15 minutes Cada 15 minutos Every 30 minutes Cada 30 minutos Every hour Cada hora Every 2 hours Cada 2 horas Every 6 hours Cada 6 horas Every 12 hours Cada 12 horas Every day Cada día Every week Cada semana Don't automatically download episodes Latest episode Latest %1 episodes All episodes PodcastUrlPage URL URL Enter podcast URL... Introduzca la URL de podcast... Load Cargar Enter podcast URL below, and press 'Load' Introduzca a continuación la URL de podcast y pulse 'Cargar' Invalid URL! URL no válida. Failed to fetch podcast! No se pudo obtener el podcast. Failed to parse podcast. No se puedo analizar el podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata solo admite podcasts de audio. La URL introducida solo contiene podcasts de vídeo. PodcastWidget Add Subscription Añadir suscripción Remove Subscription Eliminar suscripción Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Do you wish to download the selected podcast episodes? ¿Desea descargar los episodios de podcast seleccionados? Cancel podcast episode downloads (both current and any that are queued)? Do you wish to the delete downloaded files of the selected podcast episodes? ¿Desea eliminar los ficheros descargados de los episodios de podcast seleccionados? Do you wish to mark the selected podcast episodes as new? Do you wish to mark the selected podcast episodes as listened? Refresh all subscriptions? Refresh Actualizar Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track Cantata está reproduciendo una pista PreferencesDialog Collection Colección Collection Settings Ajustes de collección Playback Reproducción Playback Settings Opciones de reproducción Downloaded Files Downloaded Files Settings Interface Interfaz Interface Settings Configuración de la interfaz Info Información Info View Settings Scrobbling Scrobbling Scrobbling Settings Ajustes de scrobbling Audio CD CD de audio Audio CD Settings Configuración de CD de audio Proxy Proxy Proxy Settings Ajustes de proxy Shortcuts Atajos de teclado Keyboard Shortcut Settings Configuración de atajos de teclado Cache Almacén Cached Items Elementos almacenados Custom Actions Cantata Preferences Configure Configuración ProxySettings Mode: Modo: Type: Tipo: HTTP Proxy Proxy HTTP SOCKS Proxy Proxy SOCKS Host: Host: Port: Puerto: Username: Nombre de usuario: Password: Contraseña: No proxy Sin proxy Use the system proxy settings Utilizar las opciones de proxy del sistema Manual proxy configuration Configuración manual de proxy QObject Track listing Lista de pistas Read more on wikipedia Lea más en Wikipedia Open in browser Abrir en explorador <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) es un códec patentado con pérdida de datos para audio digital.<br>Habitualmente, AAC ofrece una calidad de audio superior a MP3 con una tasa de bits similar. Es un elección razonable para los dispositivos iPod y otros reproductores de música portátiles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>AAC</b> utilizado por Cantata permite una <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida dee la tasa de bits en esta barra deslizante es una estimación de la <a href=http://www.ffmpeg.org/faq.html#SEC21>tasa de bits media</a> de la pista codificada.<br><b>150kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>120kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>200kb/s</b> es posiblemente demasiado. Expected average bitrate for variable bitrate encoding Tasa de bits media esperada para la codificación con tasa de bits variable Smaller file Fichero más pequeño Better sound quality Mayor calidad de audio <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://es.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) es un códec de audio digital patentado que utiliza una forma de compresión con pérdida de datos.<br>A pesar de sus limitaciones, es un formato común para el almacenamiento de audio, y generalmente compatible con todos los reproductores de música portátiles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>MP3</b> utilizado por Cantata permite una <a href=http://en.wikipedia.org/wiki/MP3#VBR>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida de la tasa de bits en esta barra deslizante es una estimación de la tasa de bits media de la pista codificada.<br><b>160kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>120kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>205kb/s</b> es posiblemente demasiado. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://es.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> es un códec de audio abierto y exento de regalías.<br>Genera ficheros más pequeños que MP3 con una calidad similar o superior. Ogg Vorbis es una excelente elección, en particular con reproductores de música portátiles compatibles. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>Vorbis</b> utilizado por Cantata admite la opción de <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>tasa de bits variable (VBR)</a>; esto es el valor de la tasa de bits varía a lo largo de la pista en base a la complejidad del contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los menos complejos. Este enfoque ofrece una calidad general superior y un fichero más pequeño que el uso de una tasa de bits constante en toda la pista.<br>El codificador Vorbis utiliza una clasificación entre -1 y 10 para definir un nivel de calidad de audio esperado. La medida de la tasa de bits en esta barra deslizante es una estimación (ofrecida por Vorbis) de la tasa de bits media para la pista codificada en base al valor de calidad dado. De hecho, las versiones más recientes y eficientes de Vorbis ofrecen una tasa de bits real menor.<br><b>5</b> es una buena elección para la reproducción de audio en un dispositivo portátil.<br/>Un valor menor a <b>3</b> puede ser insatisfactorio y un valor superior a <b>8</b> es posiblemente demasiado. Quality rating Clasificación de calidad Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> es un códec de audio digital sin patente que utiliza una forma de compresión de datos con pérdida. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. La tasa de bits es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>El codificador <b>Opus</b> utilizado por Cantata permite una <a href=http://es.wikipedia.org/wiki/Tasa_de_bits_variable>tasa de bits variable (VBR)</a> ; esto es, la tasa de bits varía a lo largo de la pista en base al contenido de audio. Los intervalos de datos más complejos se codifican con una tasa de bits más elevada que los intervalos más sencillos; este enfoque ofrece una calidad general superior y un fichero más pequeño que el generado con una tasa de bits constante en toda la pista.<br>Por ello, la medida de la tasa de bits en esta barra deslizante es una estimación de la tasa de bits media de la pista codificada.<br><b>128kb/s</b> es una buena elección para la reproducción de música con un dispositivo portátil.<br/>Algo inferior a <b>100kb/s</b> puede ser insatisfactorio para la música y algo superior a <b>256kb/s</b> es posiblemente demasiado. Bitrate Tasa de bits Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://es.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) es un códec de audio para la compresión de audio digital sin pérdida de datos.<br>Solo se recomienda para reproductores de música Apple y reproductores no compatibles con FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://es.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) es un códec libre y exento de regalías para la compresión de audio digital sin pérdida de datos.<br>Si desea almacenar la música sin perder calidad de audio, FLAC es una elección excelente. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. El <a href=http://flac.sourceforge.net/documentation_tools_flac.html>nivel de compresión</a> es un valor entero entre cero y ocho que representa la relación entre el tamaño del fichero y la velocidad de compresión al codificar con <b>FLAC</b>.<br/> Definir el nivel de compresión como <b>0</b> ofrece un tiempo de compresión menor pero genera un fichero comparativamente más grande.<br/>Por otra parte, un nivel de compresión de <b>8</b> ralentiza la compresión pero genera un fichero más pequeño.<br/>Tenga en cuenta que debido a que FLAC es por definición un códec sin pérdida de datos, la calidad de audio de la salida es idéntica sin importar el nivel de compresión.<br/>Así mismo, los niveles superiores a <b>5</b> aumentan sustancialmente el tiempo de compresión pero generan un fichero superficialmente menor, y no se recomiendan. Compression level Nivel de compresión Faster compression Compresión más rápida Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) es un códec privativo desarrollado por Microsoft para la compresión de audio sin pérdida de datos.<br>Solo se recomienda para dispositivos de reproducción de audio no compatibles con Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. La tasa de datos es la medida de la cantidad de datos utilizados para representar un segundo de la pista de audio.<br>Debido a las limitaciones del formato privativo <b>WMA</b> y la dificultad que presenta la ingeniería inversa de un codificador privativo, el codificador WMA utilizado por Cantata activa la opción de <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>tasa de bits constante (CBR)</a>.<br>Por ello, la medida de la tasa de bits de la barra deslizante es una estimación muy precisa de la pista coficada.<br><b>136kb/s</b> es una buena opción para la reproducción de audio en un dispositivo portátil.<br/>Un valor inferior a <b>112kb/s</b> puede ser insatisfactorio, y un valor superior a <b>182kb/s</b> es posiblemente demasiado. Empty filename. Nombre de fichero vacío. Invalid filename. (%1) Nombre de fichero inválido. (%1) Failed to save %1. Fallo al guardar %1. Failed to delete rules file. (%1) Fallo al eliminar el fichero de normas. (%1) Invalid command. (%1) Orden no válida. (%1) Could not remove active rules link. No se pudo eliminar el enlace de normas activas. Active rules is not a link. Las normas activas no son un enlace. Could not create active rules link. No se pudo crear el enlace de normas activas. Rules file, %1, does not exist. El fichero de reglas, %1, no existe. Incorrect arguments supplied. Argumentos incorrectos introducidos. Unknown method called. Invocado método desconocido. Unknown error Error desconocido Artist Artista SimilarArtists Artistas similares AlbumArtist Artista del álbum Composer Compositor Comment Comentario Album Álbum Title Título Genre Género Date Fecha File Include Incluir Exclude Excluir (Exact) (Exacto) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Carátula actual CoverArt Archive Almacen de carátulas Grouped Albums Álbumes agrupados Table Tabla Parse in Library view, and show in Folders view Only show in Folders view Do not list %1 Tracks Plural (N!=1) %1 pistas 1 Track (%1) Singular 1 pista (%1) %1 Tracks (%2) Plural (N!=1) %1 pistas (%2) %1 Albums Plural (N!=1) %1 álbumes %1 Artists Plural (N!=1) %1 artistas %1 Streams Plural (N!=1) %1 flujos %1 Entries Plural (N!=1) %1 entradas %1 Rules Plural (N!=1) %1 normas %1 Podcasts Plural (N!=1) %1 Podcasts %1 Episodes Plural (N!=1) %1 Episodios %1 Updates available Plural (N!=1) %1 actualizaciones disponibles Previous Track Pista anterior Next Track Pista siguiente Play/Pause Reproducir/Pausa Stop Detener Stop After Current Track Detener después de la pista actual Stop After Track Detener después de la pista Increase Volume Subir volumen Decrease Volume Bajar volumen Save As Guardar como Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Añadir con prioridad Set Priority Definir prioridad Highest Priority (255) Prioridad máxima (255) High Priority (200) Prioridad alta (200) Medium Priority (125) Prioridad media (125) Low Priority (50) Prioridad baja (50) Default Priority (0) Prioridad predefinida (0) Custom Priority... Prioridad personalizada... Add To Playlist Añadir a lista de reproducción Organize Files Organizar ficheros Edit Track Information ReplayGain Ganancia de reproducción Copy Songs To Device Copiar canciones a dispositivo Delete Songs Eliminar canciones Set Image Establecer imagen Remove Eliminar Find Buscar Add To Play Queue Añadir a lista de reproducción Parse error loading cache file, please check your songs tags. Other Otro Default Predefinido "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Pistas únicas Personal Personal Unknown Desconocido Various Artists Varios artistas Album artist Artista del álbum Performer Intérprete Track number Número de pista Disc number Número de disco Year Año Orignal Year Length Duración <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> en <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> por <b>%2</b> en <b>%3</b> Invalid service Servicio no válido Invalid method Método no válido Authentication failed Fallo de identificación Invalid format Formato no válido Invalid parameters Parámetros no válidos Invalid resource specified Recurso no válido especificado Operation failed Fallo de la operación Invalid session key Clave de sesión no válida Invalid API key Clave API no válida Service offline Servicio no disponible Last.fm is currently busy, please try again in a few minutes Last.fm está actualmente ocupada, vuelva a intentarlo en unos minutos Rate-limit exceeded Límite de tasa superado General General Digitally Imported Importado digitalmente Local and National Radio (ListenLive) Radio local y nacional (ListenLive) &OK &Aceptar &Cancel &Cancelar &Yes &Si &No &No &Discard &Descartar &Save &Guardar &Apply A&plicar &Close C&errar &Help A&yuda &Overwrite S&obrescribir &Reset &Restablecer &Continue Con&tinuar &Delete &Eliminar &Stop &Parar &Remove &Eliminar &Previous Anter&ior &Next Sig&uiente Close Error Fallo Information Información Warning Advertencia Question Pregunta %1 B %1 B %1 kB %1 Kb %1 MB %1 Mb %1 GB %1 Gb %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Árbol básico (sin iconos) Simple Tree Árbol sencillo Detailed Tree Árbol con detalles List Lista Grid %n Track(s) 1 Pista %n pistas %n Tracks (%1) 1 pista (%1) %n pistas (%1) %n Album(s) 1 Álbum %n álbumes %n Artist(s) 1 Artista %n artistas %n Stream(s) 1 Flujo %n flujos %n Entry(s) 1 Entrada %n entradas %n Rule(s) 1 norma %n normas %n Podcast(s) 1 Podcast %n Podcast %n Episode(s) 1 Episodio %n Episodios %n Update(s) available 1 actualización disponible %n actualizaciones disponibles RemoteDevicePropertiesDialog Device Properties Propiedades del dispositivo Connection Conexión Music Library Biblioteca de música Add Device Añadir dispositivo A remote device named '%1' already exists! Please choose a different name. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Type: Tipo: Name: Nombre: Options Opciones Host: Host: Port: Puerto: User: Usuario: Domain: Dominio: Password: Contraseña: Share: Recurso compartido: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Service name: Nombre del servicio: Folder: Directorio: Extra Options: Opciones adicionales: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Samba Share Recurso compartido Samba Samba Share (Auto-discover host and port) Samba Share (Detectar automáticamente host y port) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Directorio montado localmente RemoteFsDevice Available Disponible Not Available No disponible Failed to resolve connection details for %1 Fallo al determinar los detalles de conexión para %1 Connecting... Conectando... Password prompting does not work when cantata is started from the commandline. La petición de contraseña no funciona cuando cantata se ejecuta desde la línea de órdenes. No suitable ssh-askpass application installed! This is required for entering passwords. No se ha detectado ninguna aplicación «ssh-askpass» adecuada. Se requiere para introducir contraseñas. Mount point ("%1") is not empty! El punto de montaje («%1») no está vacío. "sshfs" is not installed! sshfs no está instalado. Disconnecting... Desconectando... "fusermount" is not installed! fusermount no está instalado. Failed to connect to "%1" Fallo de conexión con «%1» Failed to disconnect from "%1" Fallo de conexión con «%1» Updating tracks... Actualizando pistas... Not Connected Sin conexión Capacity Unknown Capacidad desconocida %1 free %1 libre RgDialog ReplayGain Ganancia de reproducción Show All Tracks Mostrar todas las pistas Show Untagged Tracks Mostrar pistas sin etiquetar Remove From List Eliminar de la lista Artist Artista Album Álbum Title Título Album Gain Ganancia de reproducción del álbum Track Gain Ganancia de reproducción de pista Album Peak Punto más alto del álbum Track Peak Punto más alto del pista Scan Analizar Update ReplayGain tags in tracks? ¿Desea actualizar las etiquetas ReplayGain de las pistas? Update Tags Actualizar etiquetas Abort scanning of tracks? ¿Desea interrumpir el análisis de las pistas? Abort Cancelar Abort reading of existing tags? ¿Desea cancelar la lectura de las etiquetas existentes? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Do you wish to scan all tracks, or only tracks without existing tags? ¿Desea analizar todas las pistas, o solo aquellas sin etiquetas? Untagged Tracks Pistas sin etiquetar All Tracks Todas las pistas Scanning tracks... Analizando pistas... Reading existing tags... Leyendo etiquetas existentes... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (¿Etiquetas corruptas?) Failed to update the tags of the following tracks: Fallo al actualizar las etiquetas de las siguientes pistas: Device has been removed! El dispositivo se ha extraído. Device is not connected. El dispositivo no está conectado. Device is busy? ¿Está el dispositivo en uso? %1 dB %1 dB Failed Fallo Original: %1 dB Original: %1 dB Original: %1 Original: %1 Remove the selected tracks from the list? ¿Eliminar las pistas seleccionadas de la lista? Remove Tracks Eliminar pistas RulesPlaylists Album Artist Artista del álbum Artist Artista Album Álbum Composer Compositor Date Fecha Genre Género Rating File Age Random Aleatorio %n Rule(s) 1 norma %n normas , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 error: %2 ScrobblingLove %1: Loved Current Track %1: Pista actual marcada como favorito %1: Love Current Track %1: Marcar pista actual como favorito ScrobblingSettings Scrobble using: Registrar pistas reproducidas mediante: Username: Nombre de usuario: Password: Contraseña: Status: Estado: Login Iniciar sesión Scrobble tracks Registrar pistas reproducidas Show 'Love' button Mostrar el botón "Love" %1 (via MPD) scrobbler name (via MPD) %1 (mediante MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Authenticating... Identificándose... Authenticated Identificado Not Authenticated No identificado ScrobblingStatus %1: Scrobble Tracks %1: Registrar pistas escuchadas SearchModel # (Track Number) SearchPage Locate In Library Ubicar en biblioteca Artist: Artista: Composer: Compositor: Performer: Intérprete: Album: Álbum: Title: Título: Genre: Género: Comment: Comentario: Date: Fecha: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Fichero: Any: Cualquiera: No tracks found. No se han encontrado pistas. %n Tracks (%1) 1 pista (%1) %n pistas (%1) SearchWidget Search... Buscar… Close Search Bar Cerrar barra de búsqueda ServerSettings Collection: Colección: Name: Nombre: Host: Host: Password: Contraseña: Music folder: Directorio de música: Cover filename: Nombre de fichero de carátula: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Nombre de fichero (sin extensión) para guardar las carátulas descargadas.<br/>Si no se define, se utiliza «cover».<br/><br/><i>%artist% se sustituirá por el artista de álbum de la canción actual, y %album% se sustituirá por el nombre del álbum.</i></p> HTTP stream URL: URL de flujo HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Añadir colección Standard Estándar Basic Básico Delete '%1'? Delete Eliminar New Collection %1 Colección nueva %1 Default Predefinido ServiceStatusLabel Logged into %1 Identificado en %1 <b>NOT</b> logged into %1 <b>NO</b> identificado en %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Buscar: Shortcut for Selected Action Atajo para la acción seleccionada Default: Predefinido: None Ninguno Custom: Personalizado: SinglePageWidget Refresh Actualizar View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Añadir Edit Editar Remove Eliminar Are you sure you wish to remove the selected rules? This cannot be undone. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cannot access song files! Please check that the device is still attached. SongView Lyrics Letras Information Información Metadata Scroll Lyrics Desplazar letras Refresh Lyrics Actualizar letras Edit Lyrics Editar letras Delete Lyrics File Eliminar fichero de letras Refresh Track Information Actualizar información de pista Cancel Cancelar Track Pista Reload lyrics? Reload from disk, or delete disk copy and download? ¿Desea recargar las letras? ¿Desea recargar la copia en el disco o eliminar la copia y descargar otra vez? Reload Actualizar Reload From Disk Recargar desde el disco Download Descargar Current playing song has changed, still perform search? La canción en reproducción ha cambiado, ¿quiere realizar la búsqueda? Song Changed Canción modificada Perform Search Realizar búsqueda Delete lyrics file? ¿Desea eliminar el fichero de letras? Delete File Eliminar fichero Artist Artista Album artist Artista del álbum Composer Compositor Lyricist Letrista Conductor Director Remixer Remezclador Album Álbum Subtitle Subtítulo Track number Número de pista Disc number Número de disco Genre Género Date Fecha Original date Fecha original Comment Comentario Copyright Derechos de autor Label Etiqueta Catalogue number Número de catálogo Title sort Ordenar por títuo Artist sort Ordenar por artista Album artist sort Ordenar por artista del álbum Album sort Ordenar por álbum Encoded by Codificado por Encoder Codificador Mood Mood Media Medio Bitrate Tasa de bits Sample rate Frecuencia de muestreo Channels Canales Tagging time Hora de etiquetado Performer (%1) Intérprete (%1) %1 kb/s %1 Hz Bits Performer Intérprete Year Año Filename Fetching lyrics via %1 Obteniendo letras mediante %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Calculando... Total space used: %1 Espacio total utilizado: %1 SqlLibraryModel %n Artist(s) 1 Artista %n artistas %n Album(s) 1 Álbum %n álbumes %n Tracks (%1) 1 pista (%1) %n pistas (%1) Cue Sheet Hoja cue Playlist Lista de reproducción StoredPlaylistsPage Rename Renombrar Remove Duplicates Eliminar duplicados Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Remove Playlists Eliminar listas de reproducción Playlist Name Nombre de lista de reproducción Enter a name for the playlist: Introduzca un nombre para la lista de reproducción: A playlist named '%1' already exists! Overwrite? Overwrite Playlist Sobrescribir la lista de reproducción Rename Playlist Renombrar lista de reproducción Enter new name for playlist: Introduzca un nombre nuevo para la lista de reproducción: Cannot add songs from '%1' to '%2' No se puede añadir canciones desde '%1' a '%2' StreamDialog Add stream to favourites Name: Nombre: URL: URL: Add Stream Añadir flujo Edit Stream Editar flujo <i><b>ERROR:</b> Invalid protocol</i> <i><b>ERROR:</b>Protocolo no válido</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Instalado Update available Actualización disponible Check the providers you wish to install/update. Seleccione los proveedores que desea instalar/actualizar. Install/Update Stream Providers Instalar/actualizar proveedores de flujo Downloading list... Descargando lista... Failed to download list of stream providers! Fallo al descargar la lista de proveedores de flujos. Installing/updating %1 Instalando/actualizando %1 Failed to install '%1' Failed to download '%1' Install/update the selected stream providers? Install the selected stream providers? Update the selected stream providers? Install/Update Instalar/Actualizar Abort installation/update? ¿Desea cancelar la instalación/actualización? Abort Cancelar %n Update(s) available 1 actualización disponible %n actualizaciones disponibles Downloading %1 Descargando %1 Update all updateable providers Actualizar todos los proveedores actualizables StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Search for radio streams Enter string to search Not Loaded No se ha cargado Loading... Cargando… %n Entry(s) 1 Entrada %n entradas StreamSearchPage Added '%1'' to favorites Añadido '%1' a favoritos StreamsBrowsePage Import Streams Into Favorites Importar flujos a favoritos Export Favorite Streams Exportar flujos favoritos Add New Stream To Favorites Añadir flujo nuevo a favoritos Edit Editar Seatch For Streams Configure Configuración Digitally Imported Service name Importado digitalmente Import Streams Importar flujos XML Streams (*.xml *.xml.gz *.cantata) Flujos XML (*.xml *.xml.gz *.cantata) Export Streams Exportar flujos XML Streams (*.xml.gz) Flujos XML (*.xml.gz) Failed to create '%1'! Stream '%1' already exists! A stream named '%1' already exists! Bookmark added Favorito añadido Already bookmarked Ya está en favoritos Already in favorites Ya está en favoritos Reload '%1' streams? Are you sure you wish to remove bookmark to '%1'? Are you sure you wish to remove all '%1' bookmarks? Are you sure you wish to remove the %1 selected streams? ¿Desea eliminar los %1 flujos seleccionados? Are you sure you wish to remove '%1'? Added '%1'' to favorites Añadido '%1' a favoritos StreamsModel Bookmarks Favoritos TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Favoritos Bookmark Category Categoría de favorito Add Stream To Favorites Añadir flujo a favoritos Configure Digitally Imported Reload Actualizar Streams Flujos Radio stations Not Loaded No se ha cargado Loading... Cargando… %n Entry(s) 1 Entrada %n entradas StreamsSettings Use the checkboxes below to configure the list of active providers. Utilice las casillas a continuación para configurar la lista de proveedores activos. Built-in categories are shown in italic, and these cannot be removed. Configure Streams Configuración de flujos From File... Del fichero... Download... Descargar... Configure Provider Configurar proveedor Install Instalar Remove Eliminar Install Streams Instalar flujos Cantata Streams (*.streams) Flujos de Cantata (*.streams) A category named '%1' already exists! Overwrite? Failed top open package file. Invalid file format! Formato de fichero no válido. Failed to create stream category folder! No se pudo crear el directorio de categoría de flujo. Failed to save stream list! No se pudo crear la lista de flujo. Are you sure you wish to remove '%1'? Failed to remove streams folder! No se pudo eliminar el directorio de flujos. SyncCollectionWidget Search Buscar Check Items Seleccionar elementos Uncheck Items Deseleecionar elementos SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Sincronizar Device and library are in sync. El dispositivo y la biblioteca están sincronizados. Loading all songs from library, please wait...%1%... Local Music Library Properties Propiedades de la biblioteca de música local Device has been removed! El dispositivo se ha extraído. Device has been changed? ¿Ha cambiado el dispositivo? Device is busy? ¿Está el dispositivo en uso? TableView Stretch Columns To Fit Window Estirar columnas para adaptarse a la ventana Left Izquierda Center Right Derecha Alignment TagEditor Track: Pista: Title: Título: Artist: Artista: Album artist: Artista del álbum: Composer: Compositor: Album: Álbum: Track number: Número de pista: Disc number: Número de disco: Genre: Género: Year: Año: Rating: <i>(Various)</i> Comment: Comentario: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Tags Etiquetas Tools Herramientas Apply "Various Artists" Workaround Arreglo para «Varios Artistas» Revert "Various Artists" Workaround Deshacer arreglo para «Varios Artistas» Set 'Album Artist' from 'Artist' Definir «Artista del álbum» como «Artista» Capitalize Aplicar mayúsculas Adjust Track Numbers Ajustar números de pista Read Ratings from File Write Ratings to File All tracks Todas las pistas Apply "Various Artists" workaround to <b>all</b> tracks? ¿Desea aplicar el arreglo para «Varios Artistas» a <b>todas</b> las pistas? Apply "Various Artists" workaround? ¿Desea aplicar el arreglo para «Various Artists»? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Esto define «Artista del álbum» y «Artista» con el valor de «Varios Artistas», y «Título» con el de «Artista de pista - Título de pista»</i> Revert "Various Artists" workaround on <b>all</b> tracks? ¿Desea deshacer el arreglo para «Varios Artistas» a <b>todas</b> las pistas? Revert "Various Artists" workaround Deshacer arreglo para «Varios Artistas» <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Si «Artista del álbum» es igual a «Artista» y «Título» tiene el formato «Artista de pista - Título de pista», el valor para «Artista» se tomará de «Título», y «Título» se definirá solo con el título de pista. Por ejemplo:<br/><br/>si «Título» es «Wibble - Wobble», entonces «Artista» se define como «Wibble», y «Título» se define como «Wobble»</i> Revert Revertir Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? ¿Quiere definir «Artista del álbum» como «Artista» (si «Artista del álbum» está vacío) para <b>todas</b> las pistas? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? ¿Quiere definir «Artista del álbum» como «Artista» (si «Artista del álbum» está vacío)? Album Artist from Artist «Artista del álbum» como «Artista» Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Adjust the value of each track number by: Ajustar el valor de cada número de pista en: Adjust track number by: Ajustar números de pista según: Read ratings for all tracks from the music files? Read rating from music file? Ratings Read Ratings Read Rating Read, and updated, ratings from the following tracks: Not all Song ratings have been read from MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Song rating has not been read from MPD! Write ratings for all tracks to the music files? Write rating to music file? Write Ratings Write Rating Failed to write ratings of the following tracks: Failed to write rating to music file! All tracks [modified] Todas las pistas [modificado] %1 [modified] %1 [modificado] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (¿Etiquetas corruptas?) Failed to update the tags of the following tracks: Fallo al actualizar las etiquetas de las siguientes pistas: Would you also like to rename your song files, so as to match your tags? ¿Desea renombrar los ficheros de audio para que coincidan con las etiquetas? Rename Files Renombrar ficheros Rename Renombrar Device has been removed! El dispositivo se ha extraído. Device is not connected. El dispositivo no está conectado. Device is busy? ¿Está el dispositivo en uso? TagSpinBox (Various) (Varios) ThinSplitter Reset Spacing Restablecer espaciado TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Disponible: Selected: Seleccionado: TrackOrganiser Filenames Nombres de fichero Filename scheme: Esquema de nombre de fichero: VFAT safe Compatibilidad con VFAT Use only ASCII characters Utilizar solo caracteres ASCII Replace spaces with underscores Sustituir espacios con guiones bajos Append 'The' to artist names Original Name Nombre original New Name Nombre nuevo Ratings will be lost if a file is renamed. Organize Files Organizar ficheros Rename Renombrar Remove From List Eliminar de la lista Abort renaming of files? ¿Cancelar la modificación de los ficheros? Abort Cancelar Source file does not exist! Skip Omitir Auto Skip Omitir automáticamente Destination file already exists! Failed to create destination folder! Failed to rename '%1' to '%2' Remove the selected tracks from the list? ¿Eliminar las pistas seleccionadas de la lista? Remove Tracks Eliminar pistas Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Device has been removed! El dispositivo se ha extraído. Device is not connected. El dispositivo no está conectado. Device is busy? ¿Está el dispositivo en uso? TrayItem Cantata Cantata Now playing En reproducción UltimateLyricsProvider (Polish Translations) (Traducción al polaco) (Portuguese Translations) (Traducción al portugués) UmsDevice Not Scanned No se ha analizado Not Connected Sin conexión %1 free %1 libre ValueSlider (recommended) (recomendado) View Cancel Cancelar VolumeSlider Mute Silenciar Unmute Desactivar silenciar Volume %1% (Muted) Volumen: %1 % (silenciado) Volume %1% Volumen %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artista|banda|cantante|vocalista|músico album|score|soundtrack Search pattern for an album, separated by | albúm|pista|banda sonora WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Seleccione los idiomas de Wikipeda que utilizar para la búsqueda de información de artista y álbum. Reload Actualizar cantata-2.2.0/translations/cantata_fr.ts000066400000000000000000023050311316350454000203260ustar00rootroot00000000000000 Refresh Album Information Rafraîchir les informations de l'album Album Album Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Pistes Refresh Artist Information Rafraîchir les informations de l'artiste Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Artiste Albums Albums Web Links Liens Web Similar Artists Artistes similaires Lyrics Providers Fournisseur de paroles Wikipedia Languages Langues Wikipédia Other Autre Reset Spacing Réinitialiser l'espacement &Artist &Artiste Al&bum Al&bum &Track &Piste Read more on last.fm Continuer la lecture sur last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Si Cantata n'a pas trouvé de paroles, ou n'a pas trouvé les bonnes, utilisez cette fenêtre pour entrer de nouveaux paramètres de recherche. Par exemple, la chanson dont vous cherchez les paroles est peut-être une reprise - dans ce cas, chercher les paroles de la version originale peut donner de meilleurs résultats. Si cette recherche donne de nouveaux résultats, ils seront toujours associés avec le titre et l'artiste de la version originale dans l'affichage de Cantata. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Titre: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Artiste: Search For Lyrics Rechercher des paroles Choose the websites you want to use when searching for lyrics. Choisissez les sites sources pour la recherche de paroles. Song Information Informations sur la piste Images (*.png *.jpg) Images (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics Paroles Information Informations Metadata Métadonnées Refresh Lyrics Rafraîchir les paroles Edit Lyrics Éditer les paroles Delete Lyrics File Supprimer le fichier contenant les paroles Refresh Track Information Rafraîchir les informations sur la piste Cancel Annuler Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Piste Reload lyrics? Reload from disk, or delete disk copy and download? Recharger les paroles ? Recharger depuis le disque, ou effacer la copie disque et télécharger ? Reload Rafraîchir Reload From Disk Rafraîchir à partir du disque Download Télécharger Current playing song has changed, still perform search? La piste courante a changé, continuer la recherche ? Song Changed Piste modifiée Perform Search Effectuer la recherche Delete lyrics file? Supprimer les paroles ? Delete File Supprimer le fichier Album artist Artiste de l'album Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Compositeur Lyricist Parolier Conductor Chef d'orchestre Remixer Remixeur Subtitle Sous-titre Track number Piste numéro Disc number Disque numéro Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Genre Date Date Original date Date originale Comment Commentaire Copyright Copyright Label Label Catalogue number Numéro du catalogue Title sort Trier par titre Artist sort Trier par artiste Album artist sort Trier par artiste de l'album Album sort Trier par album Encoded by Encodé par Encoder Encodeur Mood Humeur Media Média Bitrate Débit binaire Sample rate Échantillonnage Channels Canaux Tagging time Date de l’étiquetage Performer (%1) Interprète (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bits Performer Interprète Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Année Filename Nom du fichier Fetching lyrics via %1 Recherche des paroles via %1 (Polish Translations) (Traduction polonaise) (Portuguese Translations) (Traduction portugaise) Track listing Liste des pistes Read more on wikipedia Lire plus sur Wikipédia Open in browser Ouvrir dans le navigateur artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artiste|groupe|chanteur|vocaliste|musicien album|score|soundtrack Search pattern for an album, separated by | album|partition|bande Choose the wikipedia languages you want to use when searching for artist and album information. Choisissez les langues Wikipédia à utiliser lors de la recherche d'informations sur les artistes et albums. Cantata is playing a track Cantata est en train de lire une piste <b>INVALID</b> <b>INVALIDE</b> <i>(When different)</i> <i>(Si différent)</i> Artists:%1, Albums:%2, Songs:%3 Artistes:%1, Albums:%2, Pistes:%3 %1 free %1 libre Local Music Library Bibliothèque musicale locale Audio CD CD Audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Il n'y a pas assez d'espace libre sur le périphérique. Les pistes sélectionnées occupent %1, mais il ne reste que %2 de libre. Les pistes doivent être converties vers un format plus léger afin d'être copiées sur le périphérique. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Il n'y a pas assez d'espace libre sur le périphérique. Les pistes sélectionnées occupent %1, mais il ne reste que %2 de libre. Copy Songs To Device Copier les pistes sur le périphérique Copy Songs Copier les pistes Delete Songs Supprimer les pistes You have not configured the destination device. Continue with the default settings? Vous n'avez pas configuré le périphérique de destination. Continuer avec la configuration par défaut ? Not Configured Non configuré Use Defaults Utiliser la configuration par défaut You have not configured the source device. Continue with the default settings? Vous n'avez pas configuré le périphérique source. Continuer avec les paramètres par défaut ? Are you sure you wish to stop? Êtes vous sûr de vouloir arrêter ? Stop Arrêter Device has been removed! Le périphérique a été retiré ! Device is not connected! Le périphérique n'est pas connecté ! Device is busy? Le périphérique est occupé ? Device has been changed? Le périphérique a été changé ? Clearing unused folders Nettoyage des dossiers non utilisés Calculate ReplayGain for ripped tracks? Calculer le ReplayGain des pistes extraites ? ReplayGain ReplayGain Calculate Calculer The destination filename already exists! Le fichier de destination existe déjà ! Song already exists! La piste existe déjà ! Song does not exist! La piste n'existe pas ! Failed to create destination folder!<br/>Please check you have sufficient permissions. La création du dossier de destination a échoué !<br/>Veuillez vérifier que vous avez les permissions requises. Source file no longer exists? Le fichier source ne semble plus exister ? Failed to copy. Copie échouée. Failed to delete. Suppression impossible. Not connected to device. Non connecté au périphérique. Selected codec is not available. Le codec sélectionné n'est pas disponible. Transcoding failed. Le transcodage a échoué. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) La création du fichier temporaire a échoué.<br/>(Requis pour transcoder vers les périphériques MTP.) Failed to read source file. La création du fichier source a échoué. Failed to write to destination file. L'écriture du fichier cible a échoué. No space left on device. Plus d'espace restant sur le périphérique. Failed to update metadata. La mise à jour des métadonnées a échoué Failed to download track. Le téléchargement de la piste a échoué. Failed to lock device. Le verrouillage du périphérique a échoué. Local Music Library Properties Propriétés de la Bibliothèque Musicale Locale Error Erreur Skip Passer Auto Skip Saut automatique Retry Réessayer Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Album: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Piste: Source file: Fichier source: Destination file: Fichier de destination: File: Fichier: Calculating... Calcul en cours… %1 (Estimated) time (Estimated) %1 (Estimé) Time remaining: Temps restant: Saving cache Sauvegarde du cache Apply "Various Artists" Workaround Appliquer l'alternative "Artistes divers" Revert "Various Artists" Workaround Révoquer l'alternative "Artistes divers" Capitalize Mettre une majuscule Adjust Track Numbers Ajuster les numéros des pistes Tools Outils Apply "Various Artists" workaround? Appliquer l'alternative "Artistes divers" ? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Cela remplacera 'Artiste album' et 'Artiste' par "Artistes divers", et 'Titre' par "Artiste piste - Titre de la piste" Revert "Various Artists" workaround? Révoquer l'alternative "Artistes divers" ? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Lorsque la valeur de 'Artiste album' est la même que 'Artiste' et que celle de 'Titre' est au format "Artiste piste - Titre de la piste", 'Artiste' sera défini à partir de 'Titre' et 'Titre' sera réduit à la seule valeur du titre. e.g. Si 'Titre' est "Toto - Tata", alors 'Artiste' deviendra "Toto" et 'Titre' sera "Tata" Revert Réinitialiser Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Mettre une majuscule à 'Titre', 'Artiste', 'Artiste de l'album' et à 'Album' ? Adjust track number by: Ajuster les numéros des pistes par: Reading disc Lecture du disque CDDB CDDB MusicBrainz MusicBrainz Data Track Données de la piste Failed to open CD device L'ouverture du CD a échoué Track %1 Piste %1 Failed to create CDDB connection La connexion à CDDB a échoué No matches found in CDDB Recherche CDDB infructueuse CDDB error: %1 Erreur CDDB: %1 Multiple matches were found. Please choose the relevant one from below: Plusieurs résultats ont été trouvés. Veuillez sélectionner le résultat le plus pertinent dans la liste ci-dessous: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Titre Disc Selection Sélection du disque %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disque %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Mise à jour (%1)… Updating (%1%)... Mise à jour (%1%)… Device Properties Propriétés du périphérique Don't copy covers Ne pas copier les pochettes Embed cover within each file Ajouter la pochette à chaque fichier No maximum size Pas de taille maximum 400 pixels 400 pixels 300 pixels 300 pixels 200 pixels 200 pixels 100 pixels 100 pixels <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Lors de la copie de pistes vers un périphérique, au cours de laquelle la valeur de 'Artiste album' est remplacée par 'Artistes divers', Cantata remplacera l'étiquette 'Artiste' de toutes les pistes par 'Artistes divers' et l'étiquette 'Titre' de la piste par 'Artiste piste - Titre de la piste'.<hr/> Lors de la copie depuis un périphérique, Cantata vérifiera si les valeurs de 'Artiste album' et 'Artiste' sont bien 'Artistes divers'. Auquel cas, il essaiera d'extraire le véritable artiste de l'étiquette 'Titre', puis de supprimer le nom de l'artiste de l'étiquette 'Titre'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Si vous activez cette option, Cantata créera un cache de la librairie musicale du périphérique. Cela permettra d'accélérer les analyses de la librairie (puisque le cache évitera la lecture des étiquettes de chaque fichier de la librairie.)<hr/><b>NOTE:</b> Si vous utilisez une autre application pour mettre à jour la librairie du périphérique, ce cache deviendra obsolète. Pour remédier à cela, cliquez simplement sur l'icône 'rafraîchir' dans la liste des périphériques. Cela supprimera le fichier de cache et provoquera une nouvelle analyse du contenu du périphérique.</p> Do not transcode Ne pas convertir Transcode to %1 Convertir vers %1 %1 (%2 free) name (size free) %1 (%2 libre) Copy To Library Copier vers la Librairie Forget Device Oublier le périphérique Add Device Ajouter le périphérique Lookup album and track details? Voir les informations détaillées de l'album et des pistes ? Refresh Rafraîchir Via CDDB Via CDDB Via MusicBrainz Via MusicBrainz Which type of refresh do you wish to perform? Quel type de rafraîchissement souhaitez-vous effectuer ? Partial - Only new songs are scanned (quick) Partiel - Seules les nouvelles pistes sont analysées (rapide) Full - All songs are rescanned (slow) Complet - Toutes les pistes sont analysées (lent) Partial Partiel Full Complet Are you sure you wish to delete the selected songs? This cannot be undone. Êtes-vous sûr de vouloir supprimer les pistes sélectionnées ? Cette action est définitive. Are you sure you wish to forget '%1'? Êtes-vous sûr de vouloir oublier '%1' ? Are you sure you wish to eject Audio CD '%1 - %2'? Êtes-vous sûr de vouloir éjecter le CD Audio '%1 - %2' ? Eject Éjecter Are you sure you wish to disconnect '%1'? Êtes-vous sûr de vouloir déconnecter '%1' ? Disconnect Déconnecter Please close other dialogs first. Veuillez fermer au préalable les autres boites de dialogue. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://fr.wikipedia.org/wiki/Advanced_Audio_Coding">Advanced Audio Coding</a> (AAC, « Encodage Audio Avancé » en français) est un algorithme de compression audio avec perte de données ayant pour but d’offrir un meilleur ratio Qualité/débit binaire que le format plus ancien MPEG-1/2 Audio Layer 3 (plus connu sous le nom de MP3). Pour cette raison, il a été choisi par différentes firmes comme Apple ou RealNetworks. Expected average bitrate for variable bitrate encoding Taux de compression moyen estimé pour un encodage à taux variable Smaller file Fichier plus petit Better sound quality Meilleure qualité audio. Ogg Vorbis Ogg Vorbis Quality rating Niveau de qualité Opus Opus Apple Lossless Apple Lossless FLAC FLAC Compression level Niveau de compression Faster compression Compression rapide Windows Media Audio Windows Media Audio Filename Scheme Schéma de nommage Various Artists Example album artist Artistes Divers Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Les variables suivantes vont êtes remplacées par leurs valeurs respectives pour chaque nom de piste. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variable</em></th><th><em>Bouton</em></th><th><em>Description</em></th></tr> Updating... Mise à jour… Reading cache Lecture du cache %1 %2% Message percent %1 %2% Connecting to device... Connexion au périphérique No devices found Pas de périphérique trouvé Connected to device Connecté au périphérique Disconnected from device Déconnecté du périphérique Updating folders... Mise à jour des dossiers… Updating files... Mise à jour des fichiers… Updating tracks... Mise à jour des pistes… Not Connected Non connecté %1 (Disc %2) %1 (Disque %2) No matches found in MusicBrainz Aucun résultat trouvé dans MusicBrainz Connection Connexion Music Library Bibliothèque musicale A remote device named '%1' already exists! Please choose a different name. Un périphérique distant possède déjà le même nom '%1' Veuillez choisir un nom différent. Samba Share Partage Samba Samba Share (Auto-discover host and port) Partage Samba (découverte automatique de l'hôte et du port) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Dossiers montés localement Available Disponible Not Available Non disponible Failed to resolve connection details for %1 La récupération des détails de connexion pour %1 a échoué Connecting... Connection… Password prompting does not work when cantata is started from the commandline. La boite de dialogue pour le mot de passe n'est pas disponible quand Cantata est démarré depuis une console. No suitable ssh-askpass application installed! This is required for entering passwords. Aucune application ssh-sakpass est disponible ! Cette dépendance est requise pour la saisie du mot de passe. Mount point ("%1") is not empty! Le point de montage ("%1") nest pas vide ! "sshfs" is not installed! "sshfs" n'est pas installé ! Disconnecting... Déconnexion… "fusermount" is not installed! "fusermount" n'est pas installé ! Failed to connect to "%1" La connexion à "%1" a échoué Failed to disconnect from "%1" La déconnexion à "%1" a échoué Capacity Unknown Capacité inconnue Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Recherche Check Items Cocher les items Uncheck Items Décocher les items Synchronize Synchroniser Device and library are in sync. Le périphérique et la bibliothèque sont synchronisés. Not Scanned Non analysé (recommended) (recommandé) Empty filename. Nom de fichier vide Invalid filename. (%1) Nom de fichier invalide. (%1) Failed to save %1. La sauvegarde de %1 a échoué. Failed to delete rules file. (%1) La suppression des droits sur le fichier a échoué. (%1) Invalid command. (%1) Commande invalide. (%1) Could not remove active rules link. La suppression des règles actives ne peut être effectuée. Rules file, %1, does not exist. Le fichier de droits, %1, n'existe pas. Incorrect arguments supplied. L'argument renseigné est incorrect. Unknown method called. La méthode appelée est inconnue. Unknown error Erreur inconnue Start Dynamic Playlist Lancer la liste dynamique Stop Dynamic Mode Arrêter la liste dynamique Dynamic Playlists Listes de lecture dynamiques - Rating: %1..%2 - Note: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Vous devez installer "perl" sur votre système pour activer le mode dynamique de Cantata. Failed to locate rules file - %1 La localisation du fichier contenant les règles a échoué - %1 Saving rule Sauvegarde des règles Deleting rule Suppression des règles Awaiting response for previous command. (%1) En attente de la réponse pour la commande précédente. (%1) Failed to save %1. (%2) La sauvegarde de %1 a échoué. (%2) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Ajouter Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Éditer Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Supprimer Are you sure you wish to remove the selected rules? This cannot be undone. Êtes-vous sûr de vouloir supprimer les règles sélectionnées ? Cette action est définitive. Remove Dynamic Rules Supprimer les règles dynamiques Dynamic Rule Règle dynamique <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR</b>: L'année du début doit être inférieure à l'année de fin</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> La fourchette de dates est trop large (limite de %1 au maximum)</i> SimilarArtists Artistes similaires AlbumArtist Artiste de l'album Include Inclure Exclude Exclure (Exact) (Exacte) Dynamic Rules Règles dynamiques None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Vide About dynamic rules À propos des règles dynamiques Failed to save %1 La sauvegarde de %1 a échoué A set of rules named '%1' already exists! Overwrite? Un ensemble de règles nommé '%1' existe déjà ! Remplacer ? Overwrite Rules Remplacer les règles Saving %1 Sauvegarde de %1 Deleting... Suppression… Name Nom Item Count Nombre d'éléments Space Used Espace occupé Total space used: %1 Espace occupé total: %1 Covers Pochettes Scaled Covers Pochettes redimensionnées Backdrops Toile de fond Artist Information Informations sur l'artiste Album Information Informations sur l'album Track Information Informations sur la piste Stream Listings Liste de flux Podcast Directories Répertoires des podcasts Scrobble Tracks Scrobbler les pistes Delete All Tout supprimer Delete all '%1' items? Supprimer les '%1' éléments ? Delete Cache Items Supprimer tous les éléments en cache Delete items from all selected categories? Supprimer les éléments des catégories sélectionnées ? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Pochette courante CoverArt Archive Archive de CoverArt Image Image Downloading... Téléchargement… Image (%1 x %2 %3%) Image (width x height zoom%) Image (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Une image existe déjà pour cet artiste et ne peut être remplacée. A cover already exists for this album, and the file is not writeable. Une pochette existe déjà pour cet album et ne peut être remplacée. '%1' Artist Image '%1' Image de l'artiste '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Pochette d'album Failed to set cover! Could not download to temporary file! L'affectation de la pochette a échoué ! La pochette n'a pas pu être téléchargée vers les fichiers temporaires ! Failed to download image! Le téléchargement de l'image a échoué ! Load Local Cover Chargement d'une pochette locale File is already in list! Le fichier est déjà dans la liste ! Failed to read image! La lecture de l'image a échoué ! Display Afficher Failed to set cover! Could not make copy! L'affectation de la pochette a échoué ! La copie ne peut être créée ! Failed to set cover! Could not backup original! L'affectation de la pochette a échoué ! Le fichier original ne peut être sauvegardé ! Failed to set cover! Could not copy file to '%1'! L'affectation de la pochette a échoué ! Le fichier ne peut être copié vers '%1' ! Searching... Recherche en cours… Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Nom: Open In File Manager Ouvrir dans l'explorateur de fichiers Connection Established Connexion établie Connection Failed La connexion a échoué Grouped Albums Albums groupés Table Tableau Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Liste de lecture courante Folders Dossiers Playlists Listes de lecture Devices - UMS, MTP (e.g. Android), and AudioCDs Périphériques - UMS, MTP (ex: Android) et CD audio Search (via MPD) Rechercher (via MPD) Info - Current song information (artist, album, and lyrics) Info - Informations de la piste courante (artiste, album et paroles) Large Grand Small Petit Tab-bar Barre d'onglets Left Gauche Right Droite Top Haut Bottom Bas Notifications Notifications System default Valeurs par défaut Album, Artist, Year Album, Artiste, Année Album, Year, Artist Album, Année, Artiste Artist, Album, Year Artiste, Album, Année Artist, Year, Album Artiste, Année, Album Year, Album, Artist Année, Album, Artiste Year, Artist, Album Année, Artiste, Album Configure Cantata... Configurer Cantata… Preferences Préférences Quit Quitter About Cantata... Qt-only À propos de Cantata… Show Window Afficher la fenêtre Server information... Informations sur le serveur… Refresh Database Rafraîchir la base de données Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Connecter Collection Collection Outputs Sorties Stop After Track Arrêter après la piste Add To Stored Playlist Ajouter à la liste sauvegardée Add Stream URL Ajouter l'URL de flux Clear Nettoyer Center On Current Track Centrer sur la piste courante Expanded Interface Interface étendue Show Current Song Information Afficher les informations de la piste courante Full Screen Plein écran Random Aléatoire Repeat Répéter Single Single When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Quand le mode 'Single' est activé, la lecture est stoppée après la piste courante ou la piste est répétée si le mode 'Répéter' est activé. Consume Consommer When consume is activated, a song is removed from the play queue after it has been played. Quand le mode 'Consommer' est activé, la piste courante est retirée de la liste de lecture après sa lecture. Find in Play Queue Trouver dans la liste de lecture Play Stream Lire le flux Locate In Library Trouver dans la bibliothèque Expand All Tout étendre Collapse All Tout replier Devices Périphériques Info Infos Show Menubar Afficher la barre de menu &Music &Musique &Edit &Éditer &View &Voir &Queue &File &Settings &Paramètres &Help &Aide Set Rating Définir une note No Rating Pas de note Failed to locate any songs matching the dynamic playlist rules. Aucune piste respectant les choix définis pour la liste de lecture dynamique n'a été trouvée. Connecting to %1 Connecté à %1 Refresh MPD Database? Rafraîchir la base de données MPD ? About Cantata Qt-only À propos de Cantata Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Les images de fond sont issues de <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Les informations de lecture sont issues de <a href="http://www.wikipedia.org">Wikipedia</a> et <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> N'hésitez pas à envoyer vos propres images fan-art sur <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Un podcast est actuellement en téléchargement Le téléchargement sera abandonné si vous quittez maintenant. Abort download and quit Abandonner et quitter Enabled: %1 Activé: %1 Disabled: %1 Désactivé: %1 Server Information Informations du serveur <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Serveur</b></td></tr><tr><td align="right">Protocole:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Durée de fonctionnement:&nbsp;</td><td>%4</td></tr><tr><td align="right">Temps de lecture:&nbsp;</td><td>%5</td></tr><tr><td align="right">Formats:&nbsp;</td><td>%6</td></tr><tr><td align="right">Étiquettes:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Base de données</b></td></tr><tr><td align="right">Artistes:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Pistes:&nbsp;</td><td>%3</td></tr><tr><td align="right">Durée totale:&nbsp;</td><td>%4</td></tr><tr><td align="right">Mise à jour:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD a retourné l'erreur suivante: %1 Cantata Cantata Playback stopped La lecture a été interrompue Remove all songs from play queue? Supprimer toutes les pistes de la liste de lecture ? Priority Priorité Enter priority (0..255): Saisir une priorité (0…255): Playlist Name Nom de la liste de lecture Enter a name for the playlist: Entrez un nom pour la liste de lecture: '%1' is used to store favorite streams, please choose another name. Le nom '%1' est déjà utilisé pour enregistrer vos flux favoris, veuillez choisir un autre nom. A playlist named '%1' already exists! Add to that playlist? La liste de lecture '%1' existe déjà ! Ajouter à cette liste de lecture ? Existing Playlist Liste de lecture existante Auto Automatique <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Connecté à %1<br/>Les éléments ci-dessous sont liés à la collection MPD courante.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Non connecté !<br/>Les entrées ci-dessous ne peuvent être modifiées car Cantata n'est pas connecté à MPD.</i> Rename Renommer Remove Duplicates Supprimer les doublons Are you sure you wish to remove the selected playlists? This cannot be undone. Êtes-vous sûr de vouloir supprimer les listes de lecture sélectionnées ? Ce choix est définitif. Remove Playlists Supprimer les listes de lecture A playlist named '%1' already exists! Overwrite? Une liste nommée '%1' existe déjà ! Remplacer ? Overwrite Playlist Remplacer la liste de lecture Rename Playlist Renommer la liste de lecture Enter new name for playlist: Entrez un nouveau nom pour la liste de lecture: Cannot add songs from '%1' to '%2' Les pistes de '%1' à '%2' ne peuvent être ajoutées 1 Track 1 Piste %1 Pistes %1 Tracks 1 Track (%2) 1 Piste (%2) %1 Pistes (%2) %1 Tracks (%2) 1 Album 1 Album %1 Albums %1 Albums 1 Artist 1 Artiste %1 Artistes %1 Artists 1 Stream 1 Flux %1 Flux %1 Streams 1 Entry %1 Entrée %1 Entrées %1 Entries 1 Rule %1 Règle %1 Règles %1 Rules 1 Podcast 1 Podcast %1 Podcasts %1 Podcasts 1 Episode 1 Épisode %1 Épisodes %1 Episodes 1 Update available 1 Mise à jour disponible %1 Mises à jour disponibles %1 Updates available Collection Settings Paramètres de la collection Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Lecture Playback Settings Paramètres de lecture Interface Interface Interface Settings Paramètres de l'interface Scrobbling Scrobblage Scrobbling Settings Paramètres de scrobblage Audio CD Settings Paramètres des CD Audio Proxy Proxy Proxy Settings Qt-only Paramètres du proxy Shortcuts Qt-only Raccourcis Keyboard Shortcut Settings Qt-only Raccourcis clavier Cache Cache Cached Items Éléments dans le cache Cantata Preferences Préférences de Cantata Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Configurer Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Compositeur: Performer: Interprète: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Genre: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Commentaire: Date: Date: Modified: Modifié: Any: Tous: No tracks found. Pas de pistes trouvées: Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Hôte: Which type of collection do you wish to connect to? À quel type de collection souhaitez-vous vous connecter ? <i><b>NOTE:</b> %1</i> <i><b>NOTE:</b> %1</i> Add Collection Ajouter une Collection Standard Standard Basic Basique Delete '%1'? Supprimer '%1' ? Delete Supprimer New Collection %1 Nouvelle collection %1 Default Par défaut Previous Track Piste précédente Next Track Piste suivante Play/Pause Lecture/Pause Stop After Current Track Arrêter après cette piste Increase Volume Augmenter le volume Decrease Volume Baisser le volume Save As Enregistrer en tant que Add With Priority Ajouter en priorité Set Priority Définir la priorité Highest Priority (255) Priorité la plus forte (255) High Priority (200) Priorité forte (200) Medium Priority (125) Priorité moyenne (125) Low Priority (50) Priorité faible (50) Default Priority (0) Priorité par défaut (0) Custom Priority... Priorité personnalisée… Add To Playlist Ajouter à la liste de lecture Organize Files Organiser les fichiers Edit Track Information Éditer les informations de la piste Set Image Appliquer l'image Find Rechercher Add To Play Queue Ajouter à la liste courante Now playing En lecture Play Lecture Pause Pause Cue Sheet Liste Cue Playlist Liste de lecture Configure Device Configurer le périphérique Refresh Device Rafraîchir le périphérique Connect Device Connecter le périphérique Disconnect Device Déconnecter le périphérique Edit CD Details Éditer les propriétés du CD No Devices Attached Pas de périphérique connecté Not logged in Non connecté Logged in Connecté No subscriptions Pas de souscriptions You do not have an active subscription Vous n'avez aucune souscription active Logged in (expiry:%1) Connecté (expiration:%1) Session expired Session expirée %1 by %2 Album by Artist %1 par %2 New Playlist... Nouvelle liste de lecture… Smart Playlist Liste de lecture intelligente Length Durée Disc Disque Rating Note Undo Annuler Redo Refaire Shuffle Mélanger Sort By Trier par Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Artiste Album Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Titre de la piste TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Not Loaded Non chargé Loading... Chargement… Bookmarks Favoris IceCast IceCast Favorites Favoris Bookmark Category Catégorie des favoris Add Stream To Favorites Ajouter le flux aux favoris Streams Flux Unknown Inconnu "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed La connexion à %1 a échoué Connection to %1 failed - please check your proxy settings La connexion à %1 a échoué - Veuillez vérifier votre configuration proxy Connection to %1 failed - incorrect password La connexion à %1 a échoué - mot de passe incorrect Failed to send command to %1 - not connected L'envoie de la commande à %1 a échoué - non connecté Failed to load. Please check user "mpd" has read permission. Le chargement a échoué. Veuillez vérifier si "mpd" a les permissions de lecture. Failed to load. MPD can only play local files if connected via a local socket. Le chargement a échoué. MPD peut lire les pistes locales uniquement s'il est connecté à un socket local. Failed to send command. Disconnected from %1 L'envoie de la commande a échoué. Déconnecté de %1 Failed to rename <b>%1</b> to <b>%2</b> Le renommage de <b>%1</b> à <b>%2</b> a échoué Failed to save <b>%1</b> La sauvegarde de <b>%1</b> a échoué You cannot add parts of a cue sheet to a playlist! Vous ne pouvez pas ajouter des parties d'un fichier Cue à une liste de lecture ! You cannot add a playlist to another playlist! Il est impossible d'ajouter une liste de lecture dans une autre ! Failed to send '%1' to %2. Please check %2 is registered with MPD. L'envoie de '%1' à %2 a échoué. Veuillez vérifier si %2 est présent dans MPD. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Piste unique Personal Personnel Various Artists Artistes divers <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> sur <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> par <b>%2</b> sur <b>%3</b> No proxy Pas de proxy Use the system proxy settings Utiliser les paramètres proxy du système Manual proxy configuration Configuration manuelle du proxy Jamendo Settings Paramètres Jamendo MP3 MP3 Ogg Ogg Streaming format: Format du flux: Streaming Flux continu MP3 128k MP3 128K MP3 VBR MP3 VBR WAV WAV Magnatune Settings Paramètres Magnatune Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Identifiant: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Mot de passe: Membership: Adhésion: Downloads: Téléchargements: Failed to parse L'analyse a échoué Failed to download Le téléchargement a échoué RSS: RSS: Website: Site web: Podcast details Détails du podcast Select a podcast to display its details Choisissez un podcast pour afficher ses informations complémentaires Enter search term... Entrez les termes de la recherche… Failed to fetch podcasts from %1 La récupération des podcasts depuis %1 a échoué Failed to download directory listing La récupération de la liste des dossiers a échoué Failed to parse directory listing L'analyse de la liste des dossiers a échoué URL URL Enter podcast URL... Entrer l'URL du podcast Load Charger Enter podcast URL below, and press 'Load' Veuillez entrer l'URL du podcast ci-dessous, et cliquez sur 'Charger' Invalid URL! URL Invalide ! Subscribe Souscrire Enter URL Entrer l'URL Search %1 Rechercher %1 Search for podcasts on %1 Recherche de podcasts sur %1 Add Podcast Subscription Ajouter la souscription au podcast Browse %1 Parcourir %1 Browse %1 podcasts Parcourir les podcasts de %1 You are already subscribed to this podcast! Vous êtes désormais enregistré sur ce podcast! Subscription added Souscription ajoutée %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Téléchargement: %1%) Failed to parse %1 L'analyse de %1 a échoué Failed to download %1 Le téléchargement de %1 a échoué Check for new episodes: Vérifier l'existence de nouveaux épisodes: Download episodes to: Télécharger les épisodes vers: Download automatically: Télécharger automatiquement: Podcast Settings Paramètres des podcasts: Manually Manuellement Every 15 minutes Toutes les 15 minutes Every 30 minutes Toutes les 30 minutes Every hour Toutes heures Every 2 hours Toutes les 2 heures Every 6 hours Toutes les 6 heures Every 12 hours Toutes les 12 heures Every day Chaque jour Every week Chaque semaine Don't automatically download episodes Ne pas télécharger automatiquement les épisodes Latest episode Dernier épisode Latest %1 episodes %1 derniers épisodes All episodes Tous les épisodes Add Subscription Ajouter une souscription Remove Subscription Supprimer la souscription Unsubscribe from '%1'? Se désabonner de '%1' ? Do you wish to download the selected podcast episodes? Souhaitez-vous télécharger les épisodes podcast sélectionnés ? Cancel podcast episode downloads (both current and any that are queued)? Annuler le téléchargement des épisodes (y compris ceux en téléchargement et en attente) ? Do you wish to the delete downloaded files of the selected podcast episodes? Souhaitez vous supprimer les fichiers téléchargés des podcasts sélectionnés ? Do you wish to mark the selected podcast episodes as new? Souhaitez vous définir les podcasts sélectionnés comme nouveaux ? Do you wish to mark the selected podcast episodes as listened? Souhaitez vous définir les podcasts sélectionnés comme écoutés ? Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Image de fond Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Image de l'artiste Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Image personnalisée Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Flou: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Opacité: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) Désactiver le changement automatique ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Fond sombre Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Toujours regrouper en un seul panneau Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) N'afficher que le texte Wikipédia de base Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Disponible: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Sélectionner le fichier: Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Copier les pistes depuis: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Configuration nécessaire) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Copier les pistes vers: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Fichier de destination: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Remplacer les chansons To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) À copier: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Détails de l'album Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Année: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Disque: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Piste unique: Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Récupérer les informations de l'album et des pistes Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Vérifier initialement via: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) Hôte CDDB: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) Port CDDB: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Récupérer les informations dès l’insertion d'un CD Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Extraction audio Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Ne jamais sauter les erreurs de lecture These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) Ces paramètres ne sont valables et éditables que quand le périphérique est connecté. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Dossier musical: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Copier la pochette en tant que: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Taille maximum des pochettes: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Volume par défaut 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) Solution de contournement pour 'Artistes divers' Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Analyser automatiquement les pistes Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Utiliser le cache Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Nom des fichiers Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Schéma de nommage: Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Utiliser seulement des caractères ASCII Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Remplacer les espaces avec des tirets-bas Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) Ajouter 'The' aux nom des artistes Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Transcodage Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Convertir seulement si le fichier source est dans un format différent Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Exemple: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) À propos du schéma de nommage The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) L'artiste de l'album. Pour la plupart des albums, il aura la même valeur que <i>l'artiste des pistes</i>. Pour les compilations, il sera souvent défini comme <i>Artistes Divers</i>. The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) Le nom de l'album. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Titre de l'album The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) Le compositeur The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) L'artiste de chaque piste. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Artiste de la piste The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) Le titre de la piste (sans <i>l'artiste de la piste</i>). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) Le titre de la piste (avec <i>l'artiste de la piste</i> s'il est différent de <i>l'artiste de l'album</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Titre de la piste (+Artiste) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Piste numéro Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Piste # CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) Année de sortie de l'album. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) Genre de l'album. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) Ces paramètres ne sont modifiables que lorsque le périphérique est déconnecté. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Type: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Options Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Port: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Identifiant: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Domaine: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Partager: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) Si un mot de passe est saisi ici, il sera stocké <b>en clair</b> dans le fichier de configuration de Cantata. Si vous souhaitez re-saisir votre mot de passe à chaque fois, entrez '-'. Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Nom du service: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Dossier: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Paramètres supplémentaires: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) Aucune application ssh-sakpass est disponible ! Cette dépendance est requise pour la saisie du mot de passe. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Nom des règles dynamiques - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) et About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) À propos des règles dynamiques Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Inclure les pistes qui correspondent à: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Exclure les pistes qui correspondent à: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Artistes similaires à: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Artiste Album: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) De l'année: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Tous To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) À l'année: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Correspondance exacte Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) Veuillez ne saisir de valeurs que pour les étiquettes que vous souhaitez utiliser dans la recherche. Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Ajouter un fichier local Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Enregistrer les paroles téléchargées dans le dossier musical Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Enregistrer les images de fond dans le dossier musical Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Premier lancement de Cantanta Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Bienvenue sur Cantata <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Bienvenue sur Cantata</p></body></html> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Profil multi-utilisateurs/serveurs standard. Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Profil standard pour un simple utilisateur Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Détails de connexion Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Dossier musical Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Veuillez spécifier le dossier contenant votre collection musicale. <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata va télécharger les pochettes et paroles manquantes depuis Internet.</p><p>Pour chacune d'entre elles veuillez confirmer si vous souhaitez que Cantata les enregistre dans le dossier musique ou dans votre dossier de cache/de configuration.</p> Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Terminé ! Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Barre latérale Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Vues Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Style: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Position: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) N'afficher que les icônes, sans le texte Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Cacher automatiquement Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Regrouper les albums initialement Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Défiler jusqu'à la piste courante Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Pochette courante Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) Outils Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) Afficher le bouton stop Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) Afficher la pochette de la piste courante Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) Afficher la note de la piste External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Externe Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Afficher une notification lors du changement de piste Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Afficher un icône dans la zone de notifications Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Minimiser dans la zone de notification si la fenêtre est fermée On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) Au lancement Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Afficher la fenêtre Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) Cacher la fenêtre principale Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Récupérer l'état précédent General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Général Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) Récupérer les pochettes manquantes sur Last.fm Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) Adapter l'interface aux appareils tactiles Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) Informations sur la piste Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) Support des écrans haute résolution Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Langue : Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) L'activation de l'interface pour les appareils tactiles requiert un redémarrage de Cantata. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dynamique] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Sortir du mode plein écran Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Arrêter la lecture à la fermeture Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Sortie About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) À propos du ReplayGain Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Utilisez les cases ci-dessous pour contrôler les sorties actives. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Collection: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Nom de la pochette: HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) URL du flux HTTP: Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Mode: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy HTTP: SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) Proxy SOCKS: Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Utilisez les cases ci-dessous pour configurer la liste des services actifs. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Configurer le service Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Scrobbler avec: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Status: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Authentification Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Scrobbler les pistes: Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Afficher le bouton 'Aimer' Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Compte Premium Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Type de flux: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Session expirée: Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Recherche: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Raccourcis pour l'action choisie Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Par défaut: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Personnalisé: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Artiste de l'album: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Piste numéro: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Disque numéro: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) Note: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>Divers</i> Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Nom original New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Nouveau nom Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) Les notes seront perdues si les fichiers sont renommés. Your names NAME OF TRANSLATORS Jaussoin Timothée Your emails EMAIL OF TRANSLATORS edhelas@movim.eu Show All Tracks Afficher toutes les pistes Show Untagged Tracks Afficher les pistes non renseignées Remove From List Supprimer de la liste Album Gain Gain de l'album Track Gain Gain de la piste Album Peak Pic de l'album Track Peak Pic de la piste Scan Analyser Update ReplayGain tags in tracks? Mettre à jour les étiquettes ReplayGain dans les pistes ? Update Tags Mettre à jour les étiquettes Abort scanning of tracks? Arrêter l'analyse des pistes ? Abort Annuler Abort reading of existing tags? Annuler la lecture des étiquettes existantes ? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Analyser <b>toutes</b> les pistes ?<br/><br/><i>Toutes les pistes ont déjà une étiquette ReplayGain.</i> Untagged Tracks Pistes non étiquetées All Tracks Toutes les pistes Scanning tracks... Analyse des pistes… Reading existing tags... Lecture des étiquettes existantes… %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Étiquettes corrompues ?) Failed to update the tags of the following tracks: La mise à jour des étiquettes des pistes suivantes a échoué: Device is not connected. Le périphérique n'est pas connecté. %1 dB %1 dB Failed Échoué Original: %1 dB Originale: %1 dB Original: %1 Original: %1 Remove the selected tracks from the list? Supprimer la piste sélectionnée de la liste ? Remove Tracks Supprimer les pistes Invalid service Service incorrect Invalid method Méthode incorrecte Authentication failed L'authentification a échouée Invalid format Format incorrect Invalid parameters Paramètres incorrects Invalid resource specified La ressource spécifiée est incorrecte Operation failed L'opération a échoué Invalid session key La clef de session est incorrecte Invalid API key La clef de l'API est incorrecte Service offline Services hors ligne Last.fm is currently busy, please try again in a few minutes Last.fm est actuellement indisponible, veuillez réessayer dans quelques minutes %1 error: %2 %1 erreur: %2 %1: Loved Current Track %1: Piste courante aimée %1: Love Current Track %1: Aimer la piste courante %1 (via MPD) scrobbler name (via MPD) %1 (via MPD) Authenticating... Authentification… Authenticated Authentifié… Not Authenticated Non authentifié %1: Scrobble Tracks %1: Scrobbler les pistes MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Déconnexion URL: URL: Add Stream Ajouter un flux Edit Stream Éditer le flux Installed Installé Update available Mise à jour disponible Check the providers you wish to install/update. Veuillez vérifier les fournisseurs que vous souhaitez installer/mettre à jour. Install/Update Stream Providers Installer/Mettre à jour les fournisseurs de flux Downloading list... Téléchargement de la liste… Failed to download list of stream providers! La téléchargement de la liste des fournisseurs de flux a échoué. Installing/updating %1 Installation/Mise à jour de %1 Failed to install '%1' L'installation de '%1' a échoué Failed to download '%1' Le téléchargement de '%1' a échoué Install/update the selected stream providers? Installer/Mettre à jour les fournisseurs de flux sélectionnés ? Install the selected stream providers? Installer les fournisseurs de flux sélectionnés ? Update the selected stream providers? Mettre à jour le fournisseur de flux sélectionné ? Install/Update Installation/Mise à jour Abort installation/update? Abandonner l'installation/la mise à jour ? Downloading %1 Téléchargement de %1 Update all updateable providers Mettre à jour tous les fournisseurs disponibles Import Streams Into Favorites Importer les flux dans les favoris Export Favorite Streams Exporter les flux favoris Add New Stream To Favorites Ajouter un nouveau flux aux favoris Import Streams Importer les flux XML Streams (*.xml *.xml.gz *.cantata) Flux XML (*.xml *.xml.gz *.cantata) Export Streams Exporter les flux XML Streams (*.xml.gz) Flux XML (*.xml.gz) Failed to create '%1'! La création de '%1' a échoué ! Stream '%1' already exists! Le flux '%1' existe déjà ! A stream named '%1' already exists! Il existe déjà un flux nommé '%1' ! Bookmark added Marque-page ajouté Already bookmarked Déjà dans les marque-pages Already in favorites Déjà dans les favoris. Reload '%1' streams? Rafraîchir le flux '%1' ? Are you sure you wish to remove bookmark to '%1'? Êtes-vous sûr de vouloir supprimer '%1' des marque-pages ? Are you sure you wish to remove all '%1' bookmarks? Êtes-vous sûr de vouloir supprimer tous les marque-pages '%1' ? Are you sure you wish to remove the %1 selected streams? Êtes-vous sûr de vouloir supprimer les %1 flux sélectionnés ? Are you sure you wish to remove '%1'? Êtes-vous sûr de supprimer '%1' ? Added '%1'' to favorites Ajout de '%1' aux favoris Configure Streams Configurer le flux From File... Depuis le fichier… Download... Télécharger… Configure Provider Configurer le service: Install Installer Install Streams Installer des flux Cantata Streams (*.streams) Flux Cantata (*.streams) A category named '%1' already exists! Overwrite? Une catégorie nommée '%1' est déjà présente ! Remplacer ? Failed top open package file. L'ouverture de l'archive a échoué. Invalid file format! Type de fichier non pris en charge ! Failed to create stream category folder! La création du dossier de catégorie du flux a échoué ! Failed to save stream list! L'enregistrement de la liste des flux a échoué. Failed to remove streams folder! La suppression du dossier de flux a échoué ! &OK &OK &Cancel &Annuler &Yes &Oui &No &Non &Discard &Annuler &Save &Sauvegarder &Apply &Appliquer &Close &Fermer &Overwrite &Réécrire &Reset &Réinitialiser &Continue &Continuer &Delete &Effacer &Stop &Arrêter &Remove &Supprimer &Previous &Précédent &Next &Suivant Configure... Configurer… Password Mot de passe Please enter password: Veuillez saisir le mot de passe à nouveau: Close Fermer Warning Attention Question Question &Window &Fenêtre Minimize Minimiser Zoom Zoomer Select Folder Sélectionner le dossier Select File Sélectionner le fichier %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 Ko %1 MiB %1 Mo %1 GiB %1 Go Tags Étiquettes Set 'Album Artist' from 'Artist' Récupérer la valeur de 'Artiste de l'album' depuis 'Artiste' Read Ratings from File Lire la note à partir du fichier Write Ratings to File Écrire les notes sur les fichiers All tracks Toutes les pistes Read rating from music file? Lire la note de la piste ? Ratings Notes Read Ratings Lire les notes Read Rating Lire la note Read, and updated, ratings from the following tracks: Lire Write ratings for all tracks to the music files? Enregistrer les notes dans l'ensemble des pistes ? Write rating to music file? Écrire la note dans le fichier musical ? Write Ratings Écrire les notes Write Rating Écrire la note Failed to write ratings of the following tracks: L'écriture des notes sur les fichiers suivants a échoué: Failed to write rating to music file! L'écriture de la note sur la piste a échoué ! All tracks [modified] Toutes les pistes [modifier] %1 [modified] %1 [modifier] Would you also like to rename your song files, so as to match your tags? Souhaitez vous renommer vos fichiers musicaux afin de les faire correspondre avec leurs étiquettes ? Rename Files Renommer les fichiers Abort renaming of files? Abandonner le renommage des fichiers ? Source file does not exist! Le fichier source n'existe pas ! Destination file already exists! Le fichier de destination existe déjà ! Failed to create destination folder! La création du dossier cible a échoué ! Failed to rename '%1' to '%2' Le renommage de '%1' à '%2' a échoué Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Les notes des chansons ne sont pas enregistrées dans leur fichier audio mais dans la base de données d'étiquettes de MPD. Si vous renommez un fichier (ou le dossier le contenant) la note associée à la piste sera perdue. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Compositeur:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Interprète:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artiste:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Année:</b></td><td>%3</td></tr> Filter On Genre Filtrer sur le genre All Genres Tous les genres Go Back Retour Menu Menu (Stream) (Flux) Search... Rechercher… Close Search Bar Fermer la barre de recherche Logged into %1 Connecté sur %1 <b>NOT</b> logged into %1 <b>NON</b> connecté sur %1 Basic Tree (No Icons) Arbre basique (sans icônes) Simple Tree Arbre simple Detailed Tree Arbre détaillé List Liste Grid Grille Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cantata ne peut accéder aux musiques ! Veuillez vérifier le paramètre "Dossier musical" ainsi que la valeur du paramètre "music_directory" dans la configuration de MPD. Cannot access song files! Please check that the device is still attached. Cantata ne peut accéder aux musiques ! Veuillez vérifier si le périphérique est toujours attaché. Stretch Columns To Fit Window Adapter les colonnes à la fenêtre (Various) (Divers) Mute Muet Unmute Rétablir le son Volume %1% (Muted) Volume %1% (Silencieux) Volume %1% Volume %1% 1 Track Singular 1 piste %1 Tracks Plural (N!=1) %1 pistes 1 Track (%1) Singular 1 piste (%1) %1 Tracks (%2) Plural (N!=1) %1 pistes (%2) 1 Album Singular 1 album %1 Albums Plural (N!=1) %1 albums 1 Artist Singular 1 artiste %1 Artists Plural (N!=1) %1 artistes 1 Stream Singular 1 flux %1 Streams Plural (N!=1) %1 flux 1 Entry Singular 1 entrée %1 Entries Plural (N!=1) %1 entrées 1 Rule Singular 1 règle %1 Rules Plural (N!=1) %1 règles 1 Podcast Singular 1 podcast %1 Podcasts Plural (N!=1) %1 podcasts 1 Episode Singular 1 Épisode %1 Episodes Plural (N!=1) %1 Épisodes 1 Update available Singular 1 mise à jour disponible %1 Updates available Plural (N!=1) %1 mises à jour disponibles ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Copier les pistes depuis: Configure Configurer (Needs configuring) (Configuration nécessaire) Copy songs to: Copier les pistes vers: Destination format: Fichier de destination: Overwrite songs Remplacer les chansons To copy: À copier: <b>INVALID</b> <b>INVALIDE</b> <i>(When different)</i> <i>(Si différent)</i> Artists:%1, Albums:%2, Songs:%3 Artistes:%1, Albums:%2, Pistes:%3 %1 free %1 libre Local Music Library Bibliothèque musicale locale Audio CD CD Audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Il n'y a pas assez d'espace libre sur le périphérique. Les pistes sélectionnées occupent %1, mais il ne reste que %2 de libre. Les pistes doivent être converties vers un format plus léger afin d'être copiées sur le périphérique. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Il n'y a pas assez d'espace libre sur le périphérique. Les pistes sélectionnées occupent %1, mais il ne reste que %2 de libre. Copy Songs To Library Copy Songs To Device Copier les pistes sur le périphérique Copy Songs Copier les pistes Delete Songs Supprimer les pistes You have not configured the destination device. Continue with the default settings? Vous n'avez pas configuré le périphérique de destination. Continuer avec la configuration par défaut ? Not Configured Non configuré Use Defaults Utiliser la configuration par défaut You have not configured the source device. Continue with the default settings? Vous n'avez pas configuré le périphérique source. Continuer avec les paramètres par défaut ? Are you sure you wish to stop? Êtes vous sûr de vouloir arrêter ? Stop Arrêter Device has been removed! Le périphérique a été retiré ! Device is not connected! Le périphérique n'est pas connecté ! Device is busy? Le périphérique est occupé ? Device has been changed? Le périphérique a été changé ? Clearing unused folders Nettoyage des dossiers non utilisés Calculate ReplayGain for ripped tracks? Calculer le ReplayGain des pistes extraites ? ReplayGain ReplayGain Calculate Calculer The destination filename already exists! Le fichier de destination existe déjà ! Song already exists! La piste existe déjà ! Song does not exist! La piste n'existe pas ! Failed to create destination folder!<br/>Please check you have sufficient permissions. La création du dossier de destination a échoué !<br/>Veuillez vérifier que vous avez les permissions requises. Source file no longer exists? Le fichier source ne semble plus exister ? Failed to copy. Copie échouée. Failed to delete. Suppression impossible. Not connected to device. Non connecté au périphérique. Selected codec is not available. Le codec sélectionné n'est pas disponible. Transcoding failed. Le transcodage a échoué. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) La création du fichier temporaire a échoué.<br/>(Requis pour transcoder vers les périphériques MTP.) Failed to read source file. La création du fichier source a échoué. Failed to write to destination file. L'écriture du fichier cible a échoué. No space left on device. Plus d'espace restant sur le périphérique. Failed to update metadata. La mise à jour des métadonnées a échoué Failed to download track. Le téléchargement de la piste a échoué. Failed to lock device. Le verrouillage du périphérique a échoué. Local Music Library Properties Propriétés de la Bibliothèque Musicale Locale Error Erreur Skip Passer Auto Skip Saut automatique Retry Réessayer Artist: Artiste: Album: Album: Track: Piste: Source file: Fichier source: Destination file: Fichier de destination: File: Fichier: Saving cache Sauvegarde du cache Calculating... Calcul en cours… Time remaining: Temps restant: AlbumDetails Album Details Détails de l'album Artist: Artiste: Composer: Compositeur: Title: Titre: Genre: Genre: Year: Année: Disc: Disque: Single artist Piste unique: Tracks Pistes Track Piste Artist Artiste Title Titre AlbumDetailsDialog Audio CD CD Audio Apply "Various Artists" Workaround Appliquer l'alternative "Artistes divers" Revert "Various Artists" Workaround Révoquer l'alternative "Artistes divers" Capitalize Mettre une majuscule Adjust Track Numbers Ajuster les numéros des pistes Tools Outils Apply "Various Artists" workaround? Appliquer l'alternative "Artistes divers" ? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Cela remplacera 'Artiste album' et 'Artiste' par "Artistes divers", et 'Titre' par "Artiste piste - Titre de la piste" Revert "Various Artists" workaround? Révoquer l'alternative "Artistes divers" ? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Lorsque la valeur de 'Artiste album' est la même que 'Artiste' et que celle de 'Titre' est au format "Artiste piste - Titre de la piste", 'Artiste' sera défini à partir de 'Titre' et 'Titre' sera réduit à la seule valeur du titre. e.g. Si 'Titre' est "Toto - Tata", alors 'Artiste' deviendra "Toto" et 'Titre' sera "Tata" Revert Réinitialiser Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Mettre une majuscule à 'Titre', 'Artiste', 'Artiste de l'album' et à 'Album' ? Adjust track number by: Ajuster les numéros des pistes par: AlbumView Refresh Album Information Rafraîchir les informations de l'album Album Album Tracks Pistes ArtistView Refresh Artist Information Rafraîchir les informations de l'artiste Artist Artiste Albums Albums Web Links Liens Web Similar Artists Artistes similaires AudioCdDevice Reading disc Lecture du disque %n Tracks (%1) 1 piste (%1) %n pistes (%1) AudioCdSettings Album and Track Information Retrieval Récupérer les informations de l'album et des pistes Initially look up via: Vérifier initialement via: CDDB Host: Hôte CDDB: CDDB Port: Port CDDB: Lookup information as soon as CD is inserted Récupérer les informations dès l’insertion d'un CD Audio Extraction Extraction audio Full paranoia mode (best quality) Never skip on read error Ne jamais sauter les erreurs de lecture CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Liste Cue Playlist Liste de lecture CacheItem Deleting... Suppression… Calculating... Calcul en cours… CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Pochettes Scaled Covers Pochettes redimensionnées Backdrops Toile de fond Lyrics Paroles Artist Information Informations sur l'artiste Album Information Informations sur l'album Track Information Informations sur la piste Stream Listings Liste de flux Podcast Directories Répertoires des podcasts Wikipedia Languages Langues Wikipédia Scrobble Tracks Scrobbler les pistes Delete All Tout supprimer Delete all '%1' items? Supprimer les '%1' éléments ? Delete Cache Items Supprimer tous les éléments en cache Delete items from all selected categories? Supprimer les éléments des catégories sélectionnées ? CacheTree Name Nom Item Count Nombre d'éléments Space Used Espace occupé CddbInterface Data Track Données de la piste Failed to open CD device L'ouverture du CD a échoué Track %1 Piste %1 Failed to create CDDB connection La connexion à CDDB a échoué Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB Recherche CDDB infructueuse CDDB error: %1 Erreur CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Plusieurs résultats ont été trouvés. Veuillez sélectionner le résultat le plus pertinent dans la liste ci-dessous: Artist Artiste Title Titre Disc Selection Sélection du disque %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Disque %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Fournisseur de paroles Wikipedia Languages Langues Wikipédia Other Autre ContextWidget &Artist &Artiste Al&bum Al&bum &Track &Piste CoverDialog Search Recherche Add a local file Ajouter un fichier local Configure Configurer This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive Archive de CoverArt An image already exists for this artist, and the file is not writeable. Une image existe déjà pour cet artiste et ne peut être remplacée. A cover already exists for this album, and the file is not writeable. Une pochette existe déjà pour cet album et ne peut être remplacée. '%1' Artist Image '%1' Image de l'artiste '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Pochette d'album Failed to set cover! Could not download to temporary file! L'affectation de la pochette a échoué ! La pochette n'a pas pu être téléchargée vers les fichiers temporaires ! Failed to download image! Le téléchargement de l'image a échoué ! Load Local Cover Chargement d'une pochette locale Images (*.png *.jpg) Images (*.png *.jpg) File is already in list! Le fichier est déjà dans la liste ! Failed to read image! La lecture de l'image a échoué ! Display Afficher Remove Supprimer Failed to set cover! Could not make copy! L'affectation de la pochette a échoué ! La copie ne peut être créée ! Failed to set cover! Could not backup original! L'affectation de la pochette a échoué ! Le fichier original ne peut être sauvegardé ! Failed to set cover! Could not copy file to '%1'! L'affectation de la pochette a échoué ! Le fichier ne peut être copié vers '%1' ! Searching... Recherche en cours… CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Compositeur:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Interprète:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artiste:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Année:</b></td><td>%3</td></tr> CoverPreview Image Image Downloading... Téléchargement… Image (%1 x %2 %3%) Image (width x height zoom%) Image (%1 x %2 %3%) CustomActionDialog Name: Nom: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Ajouter Edit Éditer Remove Supprimer Name Nom Command Remove the selected commands? Device Updating (%1)... Mise à jour (%1)… Updating (%1%)... Mise à jour (%1%)… DevicePropertiesDialog Device Properties Propriétés du périphérique DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Ces paramètres ne sont valables et éditables que quand le périphérique est connecté. Name: Nom: Music folder: Dossier musical: Copy album covers as: Copier la pochette en tant que: Maximum cover size: Taille maximum des pochettes: Default volume: Volume par défaut 'Various Artists' workaround Solution de contournement pour 'Artistes divers' Automatically scan music when attached Analyser automatiquement les pistes Use cache Utiliser le cache Filenames Nom des fichiers Filename scheme: Schéma de nommage: VFAT safe Use only ASCII characters Utiliser seulement des caractères ASCII Replace spaces with underscores Remplacer les espaces avec des tirets-bas Append 'The' to artist names Ajouter 'The' aux nom des artistes If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Transcodage Only transcode if source file is of a different format Convertir seulement si le fichier source est dans un format différent Only transcode if source is FLAC/WAV Don't copy covers Ne pas copier les pochettes Embed cover within each file Ajouter la pochette à chaque fichier No maximum size Pas de taille maximum 400 pixels 400 pixels 300 pixels 300 pixels 200 pixels 200 pixels 100 pixels 100 pixels <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Lors de la copie de pistes vers un périphérique, au cours de laquelle la valeur de 'Artiste album' est remplacée par 'Artistes divers', Cantata remplacera l'étiquette 'Artiste' de toutes les pistes par 'Artistes divers' et l'étiquette 'Titre' de la piste par 'Artiste piste - Titre de la piste'.<hr/> Lors de la copie depuis un périphérique, Cantata vérifiera si les valeurs de 'Artiste album' et 'Artiste' sont bien 'Artistes divers'. Auquel cas, il essaiera d'extraire le véritable artiste de l'étiquette 'Titre', puis de supprimer le nom de l'artiste de l'étiquette 'Titre'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Si vous activez cette option, Cantata créera un cache de la librairie musicale du périphérique. Cela permettra d'accélérer les analyses de la librairie (puisque le cache évitera la lecture des étiquettes de chaque fichier de la librairie.)<hr/><b>NOTE:</b> Si vous utilisez une autre application pour mettre à jour la librairie du périphérique, ce cache deviendra obsolète. Pour remédier à cela, cliquez simplement sur l'icône 'rafraîchir' dans la liste des périphériques. Cela supprimera le fichier de cache et provoquera une nouvelle analyse du contenu du périphérique.</p> Do not transcode Ne pas convertir Encoder Encodeur Transcode to %1 Convertir vers %1 %1 (%2 free) name (size free) %1 (%2 libre) DevicesModel Configure Device Configurer le périphérique Refresh Device Rafraîchir le périphérique Connect Device Connecter le périphérique Disconnect Device Déconnecter le périphérique Edit CD Details Éditer les propriétés du CD Not Connected Non connecté No Devices Attached Pas de périphérique connecté DevicesPage Copy To Library Copier vers la Librairie Synchronise Forget Device Oublier le périphérique Add Device Ajouter le périphérique Lookup album and track details? Voir les informations détaillées de l'album et des pistes ? Refresh Rafraîchir Via CDDB Via CDDB Via MusicBrainz Via MusicBrainz Which type of refresh do you wish to perform? Quel type de rafraîchissement souhaitez-vous effectuer ? Partial - Only new songs are scanned (quick) Partiel - Seules les nouvelles pistes sont analysées (rapide) Full - All songs are rescanned (slow) Complet - Toutes les pistes sont analysées (lent) Partial Partiel Full Complet Are you sure you wish to delete the selected songs? This cannot be undone. Êtes-vous sûr de vouloir supprimer les pistes sélectionnées ? Cette action est définitive. Delete Songs Supprimer les pistes Are you sure you wish to forget '%1'? Êtes-vous sûr de vouloir oublier '%1' ? Are you sure you wish to eject Audio CD '%1 - %2'? Êtes-vous sûr de vouloir éjecter le CD Audio '%1 - %2' ? Eject Éjecter Are you sure you wish to disconnect '%1'? Êtes-vous sûr de vouloir déconnecter '%1' ? Disconnect Déconnecter Please close other dialogs first. Veuillez fermer au préalable les autres boites de dialogue. DigitallyImported Not logged in Non connecté Logged in Connecté Unknown error Erreur inconnue No subscriptions Pas de souscriptions You do not have an active subscription Vous n'avez aucune souscription active Logged in (expiry:%1) Connecté (expiration:%1) Session expired Session expirée DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Premium Account Compte Premium Username: Identifiant: Password: Mot de passe: Stream type: Type de flux: Status: Status: Login Authentification Session expiry: Session expirée: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Digitally Imported Settings MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated Non authentifié Authenticating... Authentification… Authenticated Authentifié… Logout Déconnexion DockMenu Play Lecture Pause Pause DynamicPlaylists Start Dynamic Playlist Lancer la liste dynamique Stop Dynamic Mode Arrêter la liste dynamique Dynamic Playlists Listes de lecture dynamiques Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Vous devez installer "perl" sur votre système pour activer le mode dynamique de Cantata. Failed to locate rules file - %1 La localisation du fichier contenant les règles a échoué - %1 Failed to remove previous rules file - %1 Failed to install rules file - %1 -> %2 Dynamizer has been terminated. Awaiting response for previous command. (%1) En attente de la réponse pour la commande précédente. (%1) Saving rule Sauvegarde des règles Deleting rule Suppression des règles Failed to save %1. (%2) La sauvegarde de %1 a échoué. (%2) Failed to delete rules file. (%1) La suppression des droits sur le fichier a échoué. (%1) Failed to control dynamizer state. (%1) Failed to set the current dynamic rules. (%1) DynamicPlaylistsPage Add Ajouter Edit Éditer Remove Supprimer Remote dynamizer is not running. Are you sure you wish to remove the selected rules? This cannot be undone. Êtes-vous sûr de vouloir supprimer les règles sélectionnées ? Cette action est définitive. Remove Dynamic Rules Supprimer les règles dynamiques FancyTabWidget Configure... Configurer… FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Enregistrer les paroles téléchargées dans le dossier musical Save downloaded backdrops in music folder Enregistrer les images de fond dans le dossier musical If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: Exemple: About filename schemes À propos du schéma de nommage The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> L'artiste de l'album. Pour la plupart des albums, il aura la même valeur que <i>l'artiste des pistes</i>. Pour les compilations, il sera souvent défini comme <i>Artistes Divers</i>. Album Artist Artiste Album The name of the album. Le nom de l'album. Album Title Titre de l'album The composer. Le compositeur Composer Compositeur The artist of each track. L'artiste de chaque piste. Track Artist Artiste de la piste The track title (without <i>Track Artist</i>). Le titre de la piste (sans <i>l'artiste de la piste</i>). Track Title Titre de la piste The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Le titre de la piste (avec <i>l'artiste de la piste</i> s'il est différent de <i>l'artiste de l'album</i>). Track Title (+Artist) Titre de la piste (+Artiste) The track number. Piste numéro Track # Piste # The album number of a multi-album album. Often compilations consist of several albums. CD # CD # The year of the album's release. Année de sortie de l'album. Year Année The genre of the album. Genre de l'album. Genre Genre Filename Scheme Schéma de nommage Various Artists Example album artist Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Les variables suivantes vont êtes remplacées par leurs valeurs respectives pour chaque nom de piste. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variable</em></th><th><em>Bouton</em></th><th><em>Description</em></th></tr> FolderPage Open In File Manager Ouvrir dans l'explorateur de fichiers Are you sure you wish to delete the selected songs? This cannot be undone. Êtes-vous sûr de vouloir supprimer les pistes sélectionnées ? Cette action est définitive. Delete Songs Supprimer les pistes FsDevice Updating... Mise à jour… Reading cache Lecture du cache Saving cache Sauvegarde du cache %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Filtrer sur le genre All Genres Tous les genres GroupedViewDelegate Audio CD CD Audio Streams Flux %n Track(s) 1 piste %n pistes InitialSettingsWizard Cantata First Run Premier lancement de Cantanta Welcome to Cantata Bienvenue sur Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Bienvenue sur Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup Profil multi-utilisateurs/serveurs standard. <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup Profil standard pour un simple utilisateur <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details Détails de connexion The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Host: Hôte: Password: Mot de passe: Music folder: Dossier musical: Connect Connecter The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Music folder Dossier musical Please choose the folder containing your music collection. Veuillez spécifier le dossier contenant votre collection musicale. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata va télécharger les pochettes et paroles manquantes depuis Internet.</p><p>Pour chacune d'entre elles veuillez confirmer si vous souhaitez que Cantata les enregistre dans le dossier musique ou dans votre dossier de cache/de configuration.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Enregistrer les paroles téléchargées dans le dossier musical Save downloaded backdrops in music folder Enregistrer les images de fond dans le dossier musical If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Finished! Terminé ! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. Not Connected Non connecté Connection Established Connexion établie Connection Failed La connexion a échoué Cantata will now terminate InputDialog Password Mot de passe Please enter password: Veuillez saisir le mot de passe à nouveau: InterfaceSettings Sidebar Barre latérale Views Vues Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Options Style: Style: Position: Position: Only show icons, no text N'afficher que les icônes, sans le texte Auto-hide Cacher automatiquement Play Queue Liste de lecture courante Initially collapse albums Regrouper les albums initialement Automatically expand current album Scroll to current track Défiler jusqu'à la piste courante Prompt before clearing Separate action (and shortcut) for play queue search Background Image Image de fond None Vide Current album cover Pochette courante Custom image: Image personnalisée Blur: Flou: 10px 10px Opacity: Opacité: 40% 40% Toolbar Outils Show stop button Afficher le bouton stop Show cover of current track Afficher la pochette de la piste courante Show track rating Afficher la note de la piste External Externe Enable MPRIS D-BUS interface Show popup messages when changing tracks Afficher une notification lors du changement de piste Show icon in notification area Afficher un icône dans la zone de notifications Minimize to notification area when closed Minimiser dans la zone de notification si la fenêtre est fermée On Start-up Au lancement Show main window Afficher la fenêtre Hide main window Cacher la fenêtre principale Restore previous state Récupérer l'état précédent Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks Piste unique If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General Général Fetch missing covers from Last.fm Récupérer les pochettes manquantes sur Last.fm Show delete action in context menus Enforce single-click activation of items Changing the style setting will require a re-start of Cantata. Make interface more touch friendly Adapter l'interface aux appareils tactiles Show song information tooltips Informations sur la piste Support retina displays Support des écrans haute résolution Language: Langue : Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Changing the 'touch friendly' setting will require a re-start of Cantata. L'activation de l'interface pour les appareils tactiles requiert un redémarrage de Cantata. Library Folders Dossiers Playlists Listes de lecture Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Périphériques - UMS, MTP (ex: Android) et CD audio Search (via MPD) Rechercher (via MPD) Info - Current song information (artist, album, and lyrics) Info - Informations de la piste courante (artiste, album et paroles) Large Grand Small Petit Tab-bar Barre d'onglets Left Gauche Right Droite Top Haut Bottom Bas Images (*.png *.jpg) Images (*.png *.jpg) 10px pixels 10px Notifications Notifications English (en) System default Valeurs par défaut %1% value% %1% %1 px pixels %1 px ItemView Go Back Retour Updating... Mise à jour… JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings Paramètres Jamendo MP3 MP3 Ogg Ogg Streaming format: Format du flux: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Vide Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm Continuer la lecture sur last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Nom Year Année Album, Artist, Year Album, Artiste, Année Album, Year, Artist Album, Année, Artiste Artist, Album, Year Artiste, Album, Année Artist, Year, Album Artiste, Année, Album Year, Album, Artist Année, Album, Artiste Year, Artist, Album Année, Artiste, Album Modified Date Group By Genre Genre Artist Artiste Album Album Are you sure you wish to delete the selected songs? This cannot be undone. Êtes-vous sûr de vouloir supprimer les pistes sélectionnées ? Cette action est définitive. Delete Songs Supprimer les pistes LyricSettings Choose the websites you want to use when searching for lyrics. Choisissez les sites sources pour la recherche de paroles. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Si Cantata n'a pas trouvé de paroles, ou n'a pas trouvé les bonnes, utilisez cette fenêtre pour entrer de nouveaux paramètres de recherche. Par exemple, la chanson dont vous cherchez les paroles est peut-être une reprise - dans ce cas, chercher les paroles de la version originale peut donner de meilleurs résultats. Si cette recherche donne de nouveaux résultats, ils seront toujours associés avec le titre et l'artiste de la version originale dans l'affichage de Cantata. Title: Titre: Artist: Artiste: Search For Lyrics Rechercher des paroles MPDConnection Unknown Inconnu Connection to %1 failed La connexion à %1 a échoué Connection to %1 failed - please check your proxy settings La connexion à %1 a échoué - Veuillez vérifier votre configuration proxy Connection to %1 failed - incorrect password La connexion à %1 a échoué - mot de passe incorrect Connecting to %1 Connecté à %1 Failed to send command to %1 - not connected L'envoie de la commande à %1 a échoué - non connecté Failed to load. Please check user "mpd" has read permission. Le chargement a échoué. Veuillez vérifier si "mpd" a les permissions de lecture. Failed to load. MPD can only play local files if connected via a local socket. Le chargement a échoué. MPD peut lire les pistes locales uniquement s'il est connecté à un socket local. MPD reported the following error: %1 MPD a retourné l'erreur suivante: %1 Failed to send command. Disconnected from %1 L'envoie de la commande a échoué. Déconnecté de %1 Failed to rename <b>%1</b> to <b>%2</b> Le renommage de <b>%1</b> à <b>%2</b> a échoué Failed to save <b>%1</b> La sauvegarde de <b>%1</b> a échoué You cannot add parts of a cue sheet to a playlist! Vous ne pouvez pas ajouter des parties d'un fichier Cue à une liste de lecture ! You cannot add a playlist to another playlist! Il est impossible d'ajouter une liste de lecture dans une autre ! Failed to send '%1' to %2. Please check %2 is registered with MPD. L'envoie de '%1' à %2 a échoué. Veuillez vérifier si %2 est présent dans MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Vide Streaming Flux continu MP3 128k MP3 128K MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Paramètres Magnatune Username: Identifiant: Password: Mot de passe: Membership: Adhésion: Downloads: Téléchargements: MainWindow [Dynamic] [Dynamique] Exit Full Screen Sortir du mode plein écran Configure Cantata... Configurer Cantata… Preferences Préférences Quit Quitter About Cantata... À propos de Cantata… Show Window Afficher la fenêtre Server information... Informations sur le serveur… Refresh Database Rafraîchir la base de données Refresh Rafraîchir Connect Connecter Collection Collection Outputs Sorties Stop After Track Arrêter après la piste Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist Ajouter à la liste sauvegardée Crop Others Add Stream URL Ajouter l'URL de flux Clear Nettoyer Center On Current Track Centrer sur la piste courante Expanded Interface Interface étendue Show Current Song Information Afficher les informations de la piste courante Full Screen Plein écran Random Aléatoire Repeat Répéter Single Single When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Quand le mode 'Single' est activé, la lecture est stoppée après la piste courante ou la piste est répétée si le mode 'Répéter' est activé. Consume Consommer When consume is activated, a song is removed from the play queue after it has been played. Quand le mode 'Consommer' est activé, la piste courante est retirée de la liste de lecture après sa lecture. Find in Play Queue Trouver dans la liste de lecture Play Stream Lire le flux Locate In Library Trouver dans la bibliothèque Play next Edit Track Information (Play Queue) Expand All Tout étendre Collapse All Tout replier Cancel Annuler Play Queue Liste de lecture courante Library Folders Dossiers Playlists Listes de lecture Internet Devices Périphériques Search Recherche Info Infos Show Menubar Afficher la barre de menu &Music &Musique &Edit &Éditer &View &Voir &Queue &File &Settings &Paramètres &Help &Aide Set Rating Définir une note No Rating Pas de note Failed to locate any songs matching the dynamic playlist rules. Aucune piste respectant les choix définis pour la liste de lecture dynamique n'a été trouvée. Connecting to %1 Connecté à %1 Refresh MPD Database? Rafraîchir la base de données MPD ? About Cantata À propos de Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Les images de fond sont issues de <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Les informations de lecture sont issues de <a href="http://www.wikipedia.org">Wikipedia</a> et <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> N'hésitez pas à envoyer vos propres images fan-art sur <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Un podcast est actuellement en téléchargement Le téléchargement sera abandonné si vous quittez maintenant. Abort download and quit Abandonner et quitter Please close other dialogs first. Veuillez fermer au préalable les autres boites de dialogue. Enabled: %1 Activé: %1 Disabled: %1 Désactivé: %1 Server Information Informations du serveur <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Serveur</b></td></tr><tr><td align="right">Protocole:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Durée de fonctionnement:&nbsp;</td><td>%4</td></tr><tr><td align="right">Temps de lecture:&nbsp;</td><td>%5</td></tr><tr><td align="right">Formats:&nbsp;</td><td>%6</td></tr><tr><td align="right">Étiquettes:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Base de données</b></td></tr><tr><td align="right">Artistes:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Pistes:&nbsp;</td><td>%3</td></tr><tr><td align="right">Durée totale:&nbsp;</td><td>%4</td></tr><tr><td align="right">Mise à jour:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD a retourné l'erreur suivante: %1 Cantata Cantata Playback stopped La lecture a été interrompue Remove all songs from play queue? Supprimer toutes les pistes de la liste de lecture ? Priority Priorité Enter priority (0..255): Saisir une priorité (0…255): Decrease priority for each subsequent track Playlist Name Nom de la liste de lecture Enter a name for the playlist: Entrez un nom pour la liste de lecture: '%1' is used to store favorite streams, please choose another name. Le nom '%1' est déjà utilisé pour enregistrer vos flux favoris, veuillez choisir un autre nom. A playlist named '%1' already exists! Add to that playlist? La liste de lecture '%1' existe déjà ! Ajouter à cette liste de lecture ? Existing Playlist Liste de lecture existante %n Track(s) 1 piste %n pistes %n Tracks (%1) 1 piste (%1) %n pistes (%1) MenuButton Menu Menu MessageOverlay Cancel Annuler Mpris (Stream) (Flux) MtpConnection Connecting to device... Connexion au périphérique No devices found Pas de périphérique trouvé Connected to device Connecté au périphérique Disconnected from device Déconnecté du périphérique Updating folders... Mise à jour des dossiers… Updating files... Mise à jour des fichiers… Updating tracks... Mise à jour des pistes… MtpDevice Not Connected Non connecté %1 free %1 libre MusicBrainz Failed to open CD device L'ouverture du CD a échoué Track %1 Piste %1 %1 (Disc %2) %1 (Disque %2) No matches found in MusicBrainz Aucun résultat trouvé dans MusicBrainz MusicLibraryModel Cue Sheet Liste Cue Playlist Liste de lecture %n Track(s) 1 piste %n pistes %n Artist(s) 1 artiste %n artistes %n Album(s) 1 album %n albums %n Tracks (%1) 1 piste (%1) %n pistes (%1) %1 by %2 Album by Artist %1 par %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) (Flux) OSXStyle &Window &Fenêtre Close Fermer Minimize Minimiser Zoom Zoomer OnlineDbService Downloading...%1% Parsing music list.... Failed to download Le téléchargement a échoué %n Artist(s) 1 artiste %n artistes OnlineDbWidget Group By Genre Genre Artist Artiste Configure Configurer The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Télécharger Re-download music listing? OnlineSearchService Searching... Recherche en cours… OnlineSearchWidget No tracks found. Pas de pistes trouvées: %n Tracks (%1) 1 piste (%1) %n pistes (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Utilisez les cases ci-dessous pour configurer la liste des services actifs. Configure Service Configurer le service OnlineView Song Information Informations sur la piste OnlineXmlParser Failed to parse L'analyse a échoué OpmlBrowsePage Reload Rafraîchir Failed to download directory listing La récupération de la liste des dossiers a échoué Failed to parse directory listing L'analyse de la liste des dossiers a échoué OtherSettings Background Image Image de fond None Vide Artist image Image de l'artiste Custom image: Image personnalisée Blur: Flou: 10px 10px Opacity: Opacité: 40% 40% Automatically switch to view after: Do not auto-switch Désactiver le changement automatique ms ms Dark background Fond sombre Darken background, and use white text, regardless of current color palette. Always collapse into a single pane Toujours regrouper en un seul panneau Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text N'afficher que le texte Wikipédia de base Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Images (*.png *.jpg) Images (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Sélectionner le dossier Select File Sélectionner le fichier PlayQueueModel Title Titre Artist Artiste Album Album # Track number Length Durée Disc Disque Year Année Original Year Genre Genre Priority Priorité Composer Compositeur Performer Interprète Rating Note Remove Duplicates Supprimer les doublons Undo Annuler Redo Refaire Shuffle Mélanger Tracks Pistes Albums Albums Sort By Trier par Album Artist Artiste Album Track Title Titre de la piste Track Number # (Track Number) PlayQueueView Remove Supprimer PlaybackSettings Playback Lecture Fa&deout on stop: None Vide ms ms Stop playback on exit Arrêter la lecture à la fermeture Inhibit suspend whilst playing If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Output Sortie <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Non connecté !<br/>Les entrées ci-dessous ne peuvent être modifiées car Cantata n'est pas connecté à MPD.</i> &Crossfade between tracks: s Replay &gain: About replay gain À propos du ReplayGain Use the checkboxes below to control the active outputs. Utilisez les cases ci-dessous pour contrôler les sorties actives. Track Piste Album Album Auto Automatique <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Connecté à %1<br/>Les éléments ci-dessous sont liés à la collection MPD courante.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> PlaylistRule Type: Type: Include songs that match the following: Inclure les pistes qui correspondent à: Exclude songs that match the following: Exclure les pistes qui correspondent à: Artist: Artiste: Artists similar to: Artistes similaires à: Album Artist: Artiste Album: Composer: Compositeur: Album: Album: Title: Titre: Genre Genre From Year: De l'année: Any Tous To Year: À l'année: Comment: Commentaire: Filename / path: Exact match Correspondance exacte Only enter values for the tags you wish to be search on. Veuillez ne saisir de valeurs que pour les étiquettes que vous souhaitez utiliser dans la recherche. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Règle dynamique Smart Rule Add Ajouter <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERROR</b>: L'année du début doit être inférieure à l'année de fin</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERROR:</b> La fourchette de dates est trop large (limite de %1 au maximum)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules Nom des règles dynamiques Add Ajouter Edit Éditer Remove Supprimer Songs with ratings between: - et Songs with duration between: seconds Number of songs in play queue: Order songs: About Rules À propos des règles dynamiques PlaylistRulesDialog Dynamic Rules Règles dynamiques None Vide No Limit About dynamic rules À propos des règles dynamiques Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 La sauvegarde de %1 a échoué A set of rules named '%1' already exists! Overwrite? Un ensemble de règles nommé '%1' existe déjà ! Remplacer ? Overwrite Rules Remplacer les règles Saving %1 Sauvegarde de %1 PlaylistsModel New Playlist... Nouvelle liste de lecture… Stored Playlists Standard playlists %n Tracks (%1) 1 piste (%1) %n pistes (%1) Smart Playlist Liste de lecture intelligente Plurals %n Track(s) 1 piste %n pistes %n Tracks (%1) 1 piste (%1) %n pistes (%1) %n Album(s) 1 album %n albums %n Artist(s) 1 artiste %n artistes %n Stream(s) 1 flux %n flux %n Entry(s) 1 entrée %n entrées %n Rule(s) 1 règle %n règles %n Podcast(s) 1 podcast %n podcasts %n Episode(s) 1 Épisode %n Épisodes %n Update(s) available 1 mise à jour disponible %n mises à jour disponibles PodcastPage RSS: RSS: Website: Site web: Podcast details Détails du podcast Select a podcast to display its details Choisissez un podcast pour afficher ses informations complémentaires PodcastSearchDialog Subscribe Souscrire Enter URL Entrer l'URL Manual podcast URL Search %1 Rechercher %1 Search for podcasts on %1 Recherche de podcasts sur %1 Add Podcast Subscription Ajouter la souscription au podcast Browse %1 Parcourir %1 Browse %1 podcasts Parcourir les podcasts de %1 You are already subscribed to this podcast! Vous êtes désormais enregistré sur ce podcast! Subscription added Souscription ajoutée PodcastSearchPage Enter search term... Entrez les termes de la recherche… Search Recherche Failed to fetch podcasts from %1 La récupération des podcasts depuis %1 a échoué There was a problem parsing the response from %1 PodcastService Subscribe to RSS feeds %n Podcast(s) 1 podcast %n podcasts %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) 1 Épisode %n Épisodes (Downloading: %1%) (Téléchargement: %1%) Failed to parse %1 L'analyse de %1 a échoué Cantata only supports audio podcasts! %1 contains only video podcasts. Failed to download %1 Le téléchargement de %1 a échoué PodcastSettingsDialog Check for new episodes: Vérifier l'existence de nouveaux épisodes: Download episodes to: Télécharger les épisodes vers: Download automatically: Télécharger automatiquement: Podcast Settings Paramètres des podcasts: Manually Manuellement Every 15 minutes Toutes les 15 minutes Every 30 minutes Toutes les 30 minutes Every hour Toutes heures Every 2 hours Toutes les 2 heures Every 6 hours Toutes les 6 heures Every 12 hours Toutes les 12 heures Every day Chaque jour Every week Chaque semaine Don't automatically download episodes Ne pas télécharger automatiquement les épisodes Latest episode Dernier épisode Latest %1 episodes %1 derniers épisodes All episodes Tous les épisodes PodcastUrlPage URL URL Enter podcast URL... Entrer l'URL du podcast Load Charger Enter podcast URL below, and press 'Load' Veuillez entrer l'URL du podcast ci-dessous, et cliquez sur 'Charger' Invalid URL! URL Invalide ! Failed to fetch podcast! Failed to parse podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. PodcastWidget Add Subscription Ajouter une souscription Remove Subscription Supprimer la souscription Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Se désabonner de '%1' ? Do you wish to download the selected podcast episodes? Souhaitez-vous télécharger les épisodes podcast sélectionnés ? Cancel podcast episode downloads (both current and any that are queued)? Annuler le téléchargement des épisodes (y compris ceux en téléchargement et en attente) ? Do you wish to the delete downloaded files of the selected podcast episodes? Souhaitez vous supprimer les fichiers téléchargés des podcasts sélectionnés ? Do you wish to mark the selected podcast episodes as new? Souhaitez vous définir les podcasts sélectionnés comme nouveaux ? Do you wish to mark the selected podcast episodes as listened? Souhaitez vous définir les podcasts sélectionnés comme écoutés ? Refresh all subscriptions? Refresh Rafraîchir Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track Cantata est en train de lire une piste PreferencesDialog Collection Collection Collection Settings Paramètres de la collection Playback Lecture Playback Settings Paramètres de lecture Downloaded Files Downloaded Files Settings Interface Interface Interface Settings Paramètres de l'interface Info Infos Info View Settings Scrobbling Scrobblage Scrobbling Settings Paramètres de scrobblage Audio CD CD Audio Audio CD Settings Paramètres des CD Audio Proxy Proxy Proxy Settings Paramètres du proxy Shortcuts Raccourcis Keyboard Shortcut Settings Raccourcis clavier Cache Cache Cached Items Éléments dans le cache Custom Actions Cantata Preferences Préférences de Cantata Configure Configurer ProxySettings Mode: Mode: Type: Type: HTTP Proxy Proxy HTTP: SOCKS Proxy Proxy SOCKS: Host: Hôte: Port: Port: Username: Identifiant: Password: Mot de passe: No proxy Pas de proxy Use the system proxy settings Utiliser les paramètres proxy du système Manual proxy configuration Configuration manuelle du proxy QObject Track listing Liste des pistes Read more on wikipedia Lire plus sur Wikipédia Open in browser Ouvrir dans le navigateur <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href="http://fr.wikipedia.org/wiki/Advanced_Audio_Coding">Advanced Audio Coding</a> (AAC, « Encodage Audio Avancé » en français) est un algorithme de compression audio avec perte de données ayant pour but d’offrir un meilleur ratio Qualité/débit binaire que le format plus ancien MPEG-1/2 Audio Layer 3 (plus connu sous le nom de MP3). Pour cette raison, il a été choisi par différentes firmes comme Apple ou RealNetworks. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Expected average bitrate for variable bitrate encoding Taux de compression moyen estimé pour un encodage à taux variable Smaller file Fichier plus petit Better sound quality Meilleure qualité audio. <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Quality rating Niveau de qualité Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Bitrate Débit binaire Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Compression level Niveau de compression Faster compression Compression rapide Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Empty filename. Nom de fichier vide Invalid filename. (%1) Nom de fichier invalide. (%1) Failed to save %1. La sauvegarde de %1 a échoué. Failed to delete rules file. (%1) La suppression des droits sur le fichier a échoué. (%1) Invalid command. (%1) Commande invalide. (%1) Could not remove active rules link. La suppression des règles actives ne peut être effectuée. Active rules is not a link. Could not create active rules link. Rules file, %1, does not exist. Le fichier de droits, %1, n'existe pas. Incorrect arguments supplied. L'argument renseigné est incorrect. Unknown method called. La méthode appelée est inconnue. Unknown error Erreur inconnue Artist Artiste SimilarArtists Artistes similaires AlbumArtist Artiste de l'album Composer Compositeur Comment Commentaire Album Album Title Titre Genre Genre Date Date File Include Inclure Exclude Exclure (Exact) (Exacte) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Pochette courante CoverArt Archive Archive de CoverArt Grouped Albums Albums groupés Table Tableau Parse in Library view, and show in Folders view Only show in Folders view Do not list %1 Tracks Plural (N!=1) %1 pistes 1 Track (%1) Singular 1 piste (%1) %1 Tracks (%2) Plural (N!=1) %1 pistes (%2) %1 Albums Plural (N!=1) %1 albums %1 Artists Plural (N!=1) %1 artistes %1 Streams Plural (N!=1) %1 flux %1 Entries Plural (N!=1) %1 entrées %1 Rules Plural (N!=1) %1 règles %1 Podcasts Plural (N!=1) %1 podcasts %1 Episodes Plural (N!=1) %1 Épisodes %1 Updates available Plural (N!=1) %1 mises à jour disponibles Previous Track Piste précédente Next Track Piste suivante Play/Pause Lecture/Pause Stop Arrêter Stop After Current Track Arrêter après cette piste Stop After Track Arrêter après la piste Increase Volume Augmenter le volume Decrease Volume Baisser le volume Save As Enregistrer en tant que Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Ajouter en priorité Set Priority Définir la priorité Highest Priority (255) Priorité la plus forte (255) High Priority (200) Priorité forte (200) Medium Priority (125) Priorité moyenne (125) Low Priority (50) Priorité faible (50) Default Priority (0) Priorité par défaut (0) Custom Priority... Priorité personnalisée… Add To Playlist Ajouter à la liste de lecture Organize Files Organiser les fichiers Edit Track Information Éditer les informations de la piste ReplayGain ReplayGain Copy Songs To Device Copier les pistes sur le périphérique Delete Songs Supprimer les pistes Set Image Appliquer l'image Remove Supprimer Find Rechercher Add To Play Queue Ajouter à la liste courante Parse error loading cache file, please check your songs tags. Other Autre Default Par défaut "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Piste unique Personal Personnel Unknown Inconnu Various Artists Album artist Artiste de l'album Performer Interprète Track number Piste numéro Disc number Disque numéro Year Année Orignal Year Length Durée <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> sur <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> par <b>%2</b> sur <b>%3</b> Invalid service Service incorrect Invalid method Méthode incorrecte Authentication failed L'authentification a échouée Invalid format Format incorrect Invalid parameters Paramètres incorrects Invalid resource specified La ressource spécifiée est incorrecte Operation failed L'opération a échoué Invalid session key La clef de session est incorrecte Invalid API key La clef de l'API est incorrecte Service offline Services hors ligne Last.fm is currently busy, please try again in a few minutes Last.fm est actuellement indisponible, veuillez réessayer dans quelques minutes Rate-limit exceeded General Général Digitally Imported Local and National Radio (ListenLive) &OK &OK &Cancel &Annuler &Yes &Oui &No &Non &Discard &Annuler &Save &Sauvegarder &Apply &Appliquer &Close &Fermer &Help &Aide &Overwrite &Réécrire &Reset &Réinitialiser &Continue &Continuer &Delete &Effacer &Stop &Arrêter &Remove &Supprimer &Previous &Précédent &Next &Suivant Close Fermer Error Erreur Information Informations Warning Attention Question Question %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 Ko %1 MiB %1 Mo %1 GiB %1 Go Basic Tree (No Icons) Arbre basique (sans icônes) Simple Tree Arbre simple Detailed Tree Arbre détaillé List Liste Grid Grille %n Track(s) 1 piste %n pistes %n Tracks (%1) 1 piste (%1) %n pistes (%1) %n Album(s) 1 album %n albums %n Artist(s) 1 artiste %n artistes %n Stream(s) 1 flux %n flux %n Entry(s) 1 entrée %n entrées %n Rule(s) 1 règle %n règles %n Podcast(s) 1 podcast %n podcasts %n Episode(s) 1 Épisode %n Épisodes %n Update(s) available 1 mise à jour disponible %n mises à jour disponibles RemoteDevicePropertiesDialog Device Properties Propriétés du périphérique Connection Connexion Music Library Bibliothèque musicale Add Device Ajouter le périphérique A remote device named '%1' already exists! Please choose a different name. Un périphérique distant possède déjà le même nom '%1' Veuillez choisir un nom différent. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Ces paramètres ne sont modifiables que lorsque le périphérique est déconnecté. Type: Type: Name: Nom: Options Options Host: Hôte: Port: Port: User: Identifiant: Domain: Domaine: Password: Mot de passe: Share: Partager: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Si un mot de passe est saisi ici, il sera stocké <b>en clair</b> dans le fichier de configuration de Cantata. Si vous souhaitez re-saisir votre mot de passe à chaque fois, entrez '-'. Service name: Nom du service: Folder: Dossier: Extra Options: Paramètres supplémentaires: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. Aucune application ssh-sakpass est disponible ! Cette dépendance est requise pour la saisie du mot de passe. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Samba Share Partage Samba Samba Share (Auto-discover host and port) Partage Samba (découverte automatique de l'hôte et du port) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Dossiers montés localement RemoteFsDevice Available Disponible Not Available Non disponible Failed to resolve connection details for %1 La récupération des détails de connexion pour %1 a échoué Connecting... Connection… Password prompting does not work when cantata is started from the commandline. La boite de dialogue pour le mot de passe n'est pas disponible quand Cantata est démarré depuis une console. No suitable ssh-askpass application installed! This is required for entering passwords. Aucune application ssh-sakpass est disponible ! Cette dépendance est requise pour la saisie du mot de passe. Mount point ("%1") is not empty! Le point de montage ("%1") nest pas vide ! "sshfs" is not installed! "sshfs" n'est pas installé ! Disconnecting... Déconnexion… "fusermount" is not installed! "fusermount" n'est pas installé ! Failed to connect to "%1" La connexion à "%1" a échoué Failed to disconnect from "%1" La déconnexion à "%1" a échoué Updating tracks... Mise à jour des pistes… Not Connected Non connecté Capacity Unknown Capacité inconnue %1 free %1 libre RgDialog ReplayGain ReplayGain Show All Tracks Afficher toutes les pistes Show Untagged Tracks Afficher les pistes non renseignées Remove From List Supprimer de la liste Artist Artiste Album Album Title Titre Album Gain Gain de l'album Track Gain Gain de la piste Album Peak Pic de l'album Track Peak Pic de la piste Scan Analyser Update ReplayGain tags in tracks? Mettre à jour les étiquettes ReplayGain dans les pistes ? Update Tags Mettre à jour les étiquettes Abort scanning of tracks? Arrêter l'analyse des pistes ? Abort Annuler Abort reading of existing tags? Annuler la lecture des étiquettes existantes ? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Analyser <b>toutes</b> les pistes ?<br/><br/><i>Toutes les pistes ont déjà une étiquette ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Untagged Tracks Pistes non étiquetées All Tracks Toutes les pistes Scanning tracks... Analyse des pistes… Reading existing tags... Lecture des étiquettes existantes… %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Étiquettes corrompues ?) Failed to update the tags of the following tracks: La mise à jour des étiquettes des pistes suivantes a échoué: Device has been removed! Le périphérique a été retiré ! Device is not connected. Le périphérique n'est pas connecté. Device is busy? Le périphérique est occupé ? %1 dB %1 dB Failed Échoué Original: %1 dB Originale: %1 dB Original: %1 Original: %1 Remove the selected tracks from the list? Supprimer la piste sélectionnée de la liste ? Remove Tracks Supprimer les pistes RulesPlaylists - Rating: %1..%2 - Note: %1..%2 Album Artist Artiste Album Artist Artiste Album Album Composer Compositeur Date Date Genre Genre Rating Note File Age Random Aléatoire %n Rule(s) 1 règle %n règles , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 erreur: %2 ScrobblingLove %1: Loved Current Track %1: Piste courante aimée %1: Love Current Track %1: Aimer la piste courante ScrobblingSettings Scrobble using: Scrobbler avec: Username: Identifiant: Password: Mot de passe: Status: Status: Login Authentification Scrobble tracks Scrobbler les pistes: Show 'Love' button Afficher le bouton 'Aimer' %1 (via MPD) scrobbler name (via MPD) %1 (via MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Authenticating... Authentification… Authenticated Authentifié… Not Authenticated Non authentifié ScrobblingStatus %1: Scrobble Tracks %1: Scrobbler les pistes SearchModel # (Track Number) SearchPage Locate In Library Trouver dans la bibliothèque Artist: Artiste: Composer: Compositeur: Performer: Interprète: Album: Album: Title: Titre: Genre: Genre: Comment: Commentaire: Date: Date: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Modifié: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Fichier: Any: Tous: No tracks found. Pas de pistes trouvées: %n Tracks (%1) 1 piste (%1) %n pistes (%1) SearchWidget Search... Rechercher… Close Search Bar Fermer la barre de recherche ServerSettings Collection: Collection: Name: Nom: Host: Hôte: Password: Mot de passe: Music folder: Dossier musical: Cover filename: Nom de la pochette: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> HTTP stream URL: URL du flux HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? À quel type de collection souhaitez-vous vous connecter ? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Ajouter une Collection Standard Standard Basic Basique Delete '%1'? Supprimer '%1' ? Delete Supprimer New Collection %1 Nouvelle collection %1 Default Par défaut ServiceStatusLabel Logged into %1 Connecté sur %1 <b>NOT</b> logged into %1 <b>NON</b> connecté sur %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Recherche: Shortcut for Selected Action Raccourcis pour l'action choisie Default: Par défaut: None Vide Custom: Personnalisé: SinglePageWidget Refresh Rafraîchir View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Ajouter Edit Éditer Remove Supprimer Are you sure you wish to remove the selected rules? This cannot be undone. Êtes-vous sûr de vouloir supprimer les règles sélectionnées ? Cette action est définitive. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cantata ne peut accéder aux musiques ! Veuillez vérifier le paramètre "Dossier musical" ainsi que la valeur du paramètre "music_directory" dans la configuration de MPD. Cannot access song files! Please check that the device is still attached. Cantata ne peut accéder aux musiques ! Veuillez vérifier si le périphérique est toujours attaché. SongView Lyrics Paroles Information Informations Metadata Métadonnées Scroll Lyrics Refresh Lyrics Rafraîchir les paroles Edit Lyrics Éditer les paroles Delete Lyrics File Supprimer le fichier contenant les paroles Refresh Track Information Rafraîchir les informations sur la piste Cancel Annuler Track Piste Reload lyrics? Reload from disk, or delete disk copy and download? Recharger les paroles ? Recharger depuis le disque, ou effacer la copie disque et télécharger ? Reload Rafraîchir Reload From Disk Rafraîchir à partir du disque Download Télécharger Current playing song has changed, still perform search? La piste courante a changé, continuer la recherche ? Song Changed Piste modifiée Perform Search Effectuer la recherche Delete lyrics file? Supprimer les paroles ? Delete File Supprimer le fichier Artist Artiste Album artist Artiste de l'album Composer Compositeur Lyricist Parolier Conductor Chef d'orchestre Remixer Remixeur Album Album Subtitle Sous-titre Track number Piste numéro Disc number Disque numéro Genre Genre Date Date Original date Date originale Comment Commentaire Copyright Copyright Label Label Catalogue number Numéro du catalogue Title sort Trier par titre Artist sort Trier par artiste Album artist sort Trier par artiste de l'album Album sort Trier par album Encoded by Encodé par Encoder Encodeur Mood Humeur Media Média Bitrate Débit binaire Sample rate Échantillonnage Channels Canaux Tagging time Date de l’étiquetage Performer (%1) Interprète (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bits Performer Interprète Year Année Filename Nom du fichier Fetching lyrics via %1 Recherche des paroles via %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Calcul en cours… Total space used: %1 Espace occupé total: %1 SqlLibraryModel %n Artist(s) 1 artiste %n artistes %n Album(s) 1 album %n albums %n Tracks (%1) 1 piste (%1) %n pistes (%1) Cue Sheet Liste Cue Playlist Liste de lecture StoredPlaylistsPage Rename Renommer Remove Duplicates Supprimer les doublons Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Êtes-vous sûr de vouloir supprimer les listes de lecture sélectionnées ? Ce choix est définitif. Remove Playlists Supprimer les listes de lecture Playlist Name Nom de la liste de lecture Enter a name for the playlist: Entrez un nom pour la liste de lecture: A playlist named '%1' already exists! Overwrite? Une liste nommée '%1' existe déjà ! Remplacer ? Overwrite Playlist Remplacer la liste de lecture Rename Playlist Renommer la liste de lecture Enter new name for playlist: Entrez un nouveau nom pour la liste de lecture: Cannot add songs from '%1' to '%2' Les pistes de '%1' à '%2' ne peuvent être ajoutées StreamDialog Add stream to favourites Name: Nom: URL: URL: Add Stream Ajouter un flux Edit Stream Éditer le flux <i><b>ERROR:</b> Invalid protocol</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Installé Update available Mise à jour disponible Check the providers you wish to install/update. Veuillez vérifier les fournisseurs que vous souhaitez installer/mettre à jour. Install/Update Stream Providers Installer/Mettre à jour les fournisseurs de flux Downloading list... Téléchargement de la liste… Failed to download list of stream providers! La téléchargement de la liste des fournisseurs de flux a échoué. Installing/updating %1 Installation/Mise à jour de %1 Failed to install '%1' L'installation de '%1' a échoué Failed to download '%1' Le téléchargement de '%1' a échoué Install/update the selected stream providers? Installer/Mettre à jour les fournisseurs de flux sélectionnés ? Install the selected stream providers? Installer les fournisseurs de flux sélectionnés ? Update the selected stream providers? Mettre à jour le fournisseur de flux sélectionné ? Install/Update Installation/Mise à jour Abort installation/update? Abandonner l'installation/la mise à jour ? Abort Annuler %n Update(s) available 1 mise à jour disponible %n mises à jour disponibles Downloading %1 Téléchargement de %1 Update all updateable providers Mettre à jour tous les fournisseurs disponibles StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Search for radio streams Enter string to search Not Loaded Non chargé Loading... Chargement… %n Entry(s) 1 entrée %n entrées StreamSearchPage Added '%1'' to favorites Ajout de '%1' aux favoris StreamsBrowsePage Import Streams Into Favorites Importer les flux dans les favoris Export Favorite Streams Exporter les flux favoris Add New Stream To Favorites Ajouter un nouveau flux aux favoris Edit Éditer Seatch For Streams Configure Configurer Digitally Imported Service name Import Streams Importer les flux XML Streams (*.xml *.xml.gz *.cantata) Flux XML (*.xml *.xml.gz *.cantata) Export Streams Exporter les flux XML Streams (*.xml.gz) Flux XML (*.xml.gz) Failed to create '%1'! La création de '%1' a échoué ! Stream '%1' already exists! Le flux '%1' existe déjà ! A stream named '%1' already exists! Il existe déjà un flux nommé '%1' ! Bookmark added Marque-page ajouté Already bookmarked Déjà dans les marque-pages Already in favorites Déjà dans les favoris. Reload '%1' streams? Rafraîchir le flux '%1' ? Are you sure you wish to remove bookmark to '%1'? Êtes-vous sûr de vouloir supprimer '%1' des marque-pages ? Are you sure you wish to remove all '%1' bookmarks? Êtes-vous sûr de vouloir supprimer tous les marque-pages '%1' ? Are you sure you wish to remove the %1 selected streams? Êtes-vous sûr de vouloir supprimer les %1 flux sélectionnés ? Are you sure you wish to remove '%1'? Êtes-vous sûr de supprimer '%1' ? Added '%1'' to favorites Ajout de '%1' aux favoris StreamsModel Bookmarks Favoris TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Favoris Bookmark Category Catégorie des favoris Add Stream To Favorites Ajouter le flux aux favoris Configure Digitally Imported Reload Rafraîchir Streams Flux Radio stations Not Loaded Non chargé Loading... Chargement… %n Entry(s) 1 entrée %n entrées StreamsSettings Use the checkboxes below to configure the list of active providers. Built-in categories are shown in italic, and these cannot be removed. Configure Streams Configurer le flux From File... Depuis le fichier… Download... Télécharger… Configure Provider Configurer le service: Install Installer Remove Supprimer Install Streams Installer des flux Cantata Streams (*.streams) Flux Cantata (*.streams) A category named '%1' already exists! Overwrite? Une catégorie nommée '%1' est déjà présente ! Remplacer ? Failed top open package file. L'ouverture de l'archive a échoué. Invalid file format! Type de fichier non pris en charge ! Failed to create stream category folder! La création du dossier de catégorie du flux a échoué ! Failed to save stream list! L'enregistrement de la liste des flux a échoué. Are you sure you wish to remove '%1'? Êtes-vous sûr de supprimer '%1' ? Failed to remove streams folder! La suppression du dossier de flux a échoué ! SyncCollectionWidget Search Recherche Check Items Cocher les items Uncheck Items Décocher les items SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Synchroniser Device and library are in sync. Le périphérique et la bibliothèque sont synchronisés. Loading all songs from library, please wait...%1%... Local Music Library Properties Propriétés de la Bibliothèque Musicale Locale Device has been removed! Le périphérique a été retiré ! Device has been changed? Le périphérique a été changé ? Device is busy? Le périphérique est occupé ? TableView Stretch Columns To Fit Window Adapter les colonnes à la fenêtre Left Gauche Center Right Droite Alignment TagEditor Track: Piste: Title: Titre: Artist: Artiste: Album artist: Artiste de l'album: Composer: Compositeur: Album: Album: Track number: Piste numéro: Disc number: Disque numéro: Genre: Genre: Year: Année: Rating: Note: <i>(Various)</i> <i>Divers</i> Comment: Commentaire: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Tags Étiquettes Tools Outils Apply "Various Artists" Workaround Appliquer l'alternative "Artistes divers" Revert "Various Artists" Workaround Révoquer l'alternative "Artistes divers" Set 'Album Artist' from 'Artist' Récupérer la valeur de 'Artiste de l'album' depuis 'Artiste' Capitalize Mettre une majuscule Adjust Track Numbers Ajuster les numéros des pistes Read Ratings from File Lire la note à partir du fichier Write Ratings to File Écrire les notes sur les fichiers All tracks Toutes les pistes Apply "Various Artists" workaround to <b>all</b> tracks? Apply "Various Artists" workaround? Appliquer l'alternative "Artistes divers" ? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Revert "Various Artists" workaround <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> Revert Réinitialiser Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Album Artist from Artist Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Adjust the value of each track number by: Adjust track number by: Ajuster les numéros des pistes par: Read ratings for all tracks from the music files? Read rating from music file? Lire la note de la piste ? Ratings Notes Read Ratings Lire les notes Read Rating Lire la note Read, and updated, ratings from the following tracks: Lire Not all Song ratings have been read from MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Song rating has not been read from MPD! Write ratings for all tracks to the music files? Enregistrer les notes dans l'ensemble des pistes ? Write rating to music file? Écrire la note dans le fichier musical ? Write Ratings Écrire les notes Write Rating Écrire la note Failed to write ratings of the following tracks: L'écriture des notes sur les fichiers suivants a échoué: Failed to write rating to music file! L'écriture de la note sur la piste a échoué ! All tracks [modified] Toutes les pistes [modifier] %1 [modified] %1 [modifier] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Étiquettes corrompues ?) Failed to update the tags of the following tracks: La mise à jour des étiquettes des pistes suivantes a échoué: Would you also like to rename your song files, so as to match your tags? Souhaitez vous renommer vos fichiers musicaux afin de les faire correspondre avec leurs étiquettes ? Rename Files Renommer les fichiers Rename Renommer Device has been removed! Le périphérique a été retiré ! Device is not connected. Le périphérique n'est pas connecté. Device is busy? Le périphérique est occupé ? TagSpinBox (Various) (Divers) ThinSplitter Reset Spacing Réinitialiser l'espacement TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Disponible: Selected: Sélectionner le fichier: TrackOrganiser Filenames Nom des fichiers Filename scheme: Schéma de nommage: VFAT safe Use only ASCII characters Utiliser seulement des caractères ASCII Replace spaces with underscores Remplacer les espaces avec des tirets-bas Append 'The' to artist names Ajouter 'The' aux nom des artistes Original Name Nom original New Name Nouveau nom Ratings will be lost if a file is renamed. Les notes seront perdues si les fichiers sont renommés. Organize Files Organiser les fichiers Rename Renommer Remove From List Supprimer de la liste Abort renaming of files? Abandonner le renommage des fichiers ? Abort Annuler Source file does not exist! Le fichier source n'existe pas ! Skip Passer Auto Skip Saut automatique Destination file already exists! Le fichier de destination existe déjà ! Failed to create destination folder! La création du dossier cible a échoué ! Failed to rename '%1' to '%2' Le renommage de '%1' à '%2' a échoué Remove the selected tracks from the list? Supprimer la piste sélectionnée de la liste ? Remove Tracks Supprimer les pistes Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Les notes des chansons ne sont pas enregistrées dans leur fichier audio mais dans la base de données d'étiquettes de MPD. Si vous renommez un fichier (ou le dossier le contenant) la note associée à la piste sera perdue. Device has been removed! Le périphérique a été retiré ! Device is not connected. Le périphérique n'est pas connecté. Device is busy? Le périphérique est occupé ? TrayItem Cantata Cantata Now playing En lecture UltimateLyricsProvider (Polish Translations) (Traduction polonaise) (Portuguese Translations) (Traduction portugaise) UmsDevice Not Scanned Non analysé Not Connected Non connecté %1 free %1 libre ValueSlider (recommended) (recommandé) View Cancel Annuler VolumeSlider Mute Muet Unmute Rétablir le son Volume %1% (Muted) Volume %1% (Silencieux) Volume %1% Volume %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artiste|groupe|chanteur|vocaliste|musicien album|score|soundtrack Search pattern for an album, separated by | album|partition|bande WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Choisissez les langues Wikipédia à utiliser lors de la recherche d'informations sur les artistes et albums. Reload Rafraîchir cantata-2.2.0/translations/cantata_hu.ts000066400000000000000000025035721316350454000203450ustar00rootroot00000000000000 Refresh Album Information Albuminformációk frissítése Album Lemez Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Számok Refresh Artist Information Előadói információk frissítése Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Előadó Albums Albumok Web Links Web-hivatkozások Similar Artists Hasonló művészek Lyrics Providers Szövegtárak Wikipedia Languages Wikipédia nyelvek Other Továbbiak Reset Spacing Szüneteket alaphelyzetbe &Artist Előadó Al&bum Al&bum &Track Szám Read more on last.fm Továbbiakat a last.fm-en olvashatsz If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Ha a Cantata nem találja a dalszöveget, vagy nem megfelelőt talált, ezt a párbeszédet használd új keresési feltételek meghatározására. Például, a jelenlegi szám feldolgozás - ha így lenne, a dalszöveg keresése az eredeti előadóval segíthet. Ha a kereső új dalszöveget talál, hozzákapcsolható az eredeti dalhoz és előadóhoz, ahogy a Cantata jelzi. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Cím: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Előadó: Search For Lyrics Dalszöveg keresése Choose the websites you want to use when searching for lyrics. Válaszd ki a weboldalt, amit a dalszöveg keresésekor használni akarsz. Song Information Dalinformációk Images (*.png *.jpg) Képek (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics Dalszövegek Information Információ Metadata Metaadat Scroll Lyrics Dalszöveg görgetése Refresh Lyrics Dalszöveg frissítése Edit Lyrics Dalszöveg szerkesztése Delete Lyrics File Dalszöveg-fájl törlése Refresh Track Information Száminformációk frissítése Cancel Kilépés Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Szám Reload lyrics? Reload from disk, or delete disk copy and download? Dalszöveg újratöltése? Lemezről töltsem újra, vagy töröljem a lemezről és töltsem le? Reload Újratöltés Reload From Disk Újratöltés lemezről Download Letöltés Current playing song has changed, still perform search? A dal, amit játszik megváltozott, továbbra is keresel? Song Changed A dal megváltozott Perform Search Keresés Delete lyrics file? Törölöd a dalszöveg fájlt. Delete File Fájl törlése Album artist Album előadói Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Zeneszerző Lyricist Dalszövegírók Conductor Rendező Remixer Újrakeverő Subtitle Alcím Track number Szám sorszáma Disc number Lemez sorszáma Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Műfaj Date Dátum Original date Eredeti dátum Comment Megjegyzés Copyright Copyright Label Címke Catalogue number Katalógusszám Title sort Cím szerint rendez Artist sort Előadó szerint rendez Album artist sort Album előadó szerint rendez Album sort Album szerint rendez Encoded by Kódolta Encoder Kódoló Mood Mood Media Média Bitrate Bitráta Sample rate Mintavételi arány Channels Csatornák Tagging time Időjelzés Performer (%1) Előadó (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bit Performer Szereplő Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Év Filename Fájlnév Fetching lyrics via %1 Dalszöveg leszedése innen %1 (Polish Translations) (Lengyel fordítás) (Portuguese Translations) (Portugál fordítás) Track listing Számlista Read more on wikipedia Továbbiakat a Wikipédiából. Open in browser Megnyitás böngészőben artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | előadó|együttes|énekes|vokál|zenész album|score|soundtrack Search pattern for an album, separated by | album|kotta|hangsáv Choose the wikipedia languages you want to use when searching for artist and album information. Válaszd ki a Wikipédiában, az előadó, vagy album információinak keresésekor használni kívánt nyelvet Cantata is playing a track A Cantata éppen számot játszik le <b>INVALID</b> <b>ÉRVÉNYTELEN</b> <i>(When different)</i> <i>(Ha eltérő)</i> Artists:%1, Albums:%2, Songs:%3 Előadó:%1, Lemez:%2, Dal:%3 %1 free %1 szabad Local Music Library Helyi zenekönyvtár Audio CD Hang CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Nincs elegendő hely a célmeghajtón. A kiválasztott dal %1 helyet foglal, de csak %2 hely van. A dalt kisebb méretre kell csomagolni, hogy sikeresen másolható legyen. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Nincs elegendő hely a célmeghajtón. A kiválasztott dal %1 helyet foglal, de csak %2 hely van. Copy Songs To Device Számok másolása az eszközre Copy Songs Dalok másolása Delete Songs Dalok törlése You have not configured the destination device. Continue with the default settings? Nem állítottad be a célmeghajtót- Folytassuk az alapbeállításokkal? Not Configured Nincs beállítva Use Defaults Alapbeállítások alkalmazása You have not configured the source device. Continue with the default settings? Nem állítottad be a célmeghajtót- Folytassuk az alapbeállításokkal? Are you sure you wish to stop? Biztosan le szeretnéd állítani? Stop Állj Device has been removed! Az eszköz el lett távolítva! Device is not connected! Az eszköz nincs csatlakoztatva! Device is busy? Az eszköz foglalt? Device has been changed? Az eszköz megváltozott? Clearing unused folders A használaton kívüli könyvtárak tisztítása Calculate ReplayGain for ripped tracks? Kiszámítsam a lejátszási szintet leszedett számokra? ReplayGain Lejátszási szint Calculate Számítás The destination filename already exists! A cél fájlnéve már létezik!<hr/>%1 Song already exists! A dal már létezik! Song does not exist! A dal nem létezik! Failed to create destination folder!<br/>Please check you have sufficient permissions. Nem sikerült létrehozni a célkönyvtárat!<br/>Ellenőrizd a jogosultságod szintjét.<hr/>%1 Source file no longer exists? A forrásfájl már nem létezik?<br/><br/<hr/>%1 Failed to copy. Nem sikerült másolni. Failed to delete. Nem sikerült törölni. Not connected to device. Nincs csatlakoztatva az eszközhöz. Selected codec is not available. A kiválasztott codec nem elérhető. Transcoding failed. Átkódolás sikertelen. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Nem sikerült létrehozni az átmeneti fájlt.<br/>(MTP eszközre átkódoláshoz kell.) Failed to read source file. Nem sikerül olvasni a forrásfájlt. <br/><br/<hr/>%1 Failed to write to destination file. Nem sikerül írni a célfájlba.<br/><br/<hr/>%1 No space left on device. Nem maradt hely az eszközön. Failed to update metadata. Nem sikerült frissíteni a metaadatokat. Failed to download track. A szám letöltése sikertelen. Failed to lock device. Nem sikerült zárolni az eszközt. Local Music Library Properties Helyi zenekönyvtár tulajdonságok Error Hiba Skip Kihagy Auto Skip Automatikusan kihagy Retry Újrapróbál Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Album Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Szám: Source file: Forrás fájl: Destination file: Célfájl: File: Fájl: Calculating... Számol... %1 (Estimated) time (Estimated) %1 (Becsült) Time remaining: Hátralévő idő: Saving cache A cache mentése Apply "Various Artists" Workaround Alkalmazza a "Vegyes előadók" munkakörnyezetet Revert "Various Artists" Workaround Visszaállítja a "Vegyes előadók" munkakörnyezetet Capitalize Váltás nagybetűre Adjust Track Numbers Számok sorszámának beállítása Tools Eszközök Apply "Various Artists" workaround? Alkalmazza a "Vegyes előadók" munkakörnyezetet? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Ez a "Album előadót" és "Előadót" "Vegyes előadók"-ra állítja, és a "Címet" "Előadó Szám - Szám címe"-re Revert "Various Artists" workaround? Visszaállítsa a "Vegyes előadók" munkamenetet? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Ahol az "Album előadó" megegyezik az "Előadó és a Címmel, illetve "Cím" "SzámElőadó -SzámCím" formájú, az Előadót a "Cím"-ből veszi és a "Cím" maga egyszerűen a cím lesz, pl. ha a "Cím" "Wibble - Wobble", akkor az "Előadó" egyszerűen "Wibble", a "Cím" pedig "Wobble" lesz. Revert Visszaállít Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Nagybetűsítse a 'Cím', 'Előadó, 'Album előadó' és 'Album' első betűjét? Adjust track number by: A szám sorszámának módosítás ennyivel: Reading disc Lemez olvasása CDDB CDDB MusicBrainz MusicBrainz Data Track Adatsáv Failed to open CD device Nem sikerült megnyitni a CD-eszközt Track %1 Szám %1 Failed to create CDDB connection Nem sikerült CDDB kapcsolatot létesíteni No matches found in CDDB Nincs egyezés a CDDB-ben CDDB error: %1 CDDB hiba: %1 Multiple matches were found. Please choose the relevant one from below: Több egyezést találtam. Kérem válaszd ki a megfelelőt az alábbiakból: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Cím Disc Selection Lemezválogatás %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Lemez %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Frissítés (%1)... Updating (%1%)... Frissítés (%1%)... Device Properties Eszközjellemzők Don't copy covers Ne másolja a borítókat Embed cover within each file Borító beágyazás minden fájlba No maximum size Nincs maximális méret 400 pixels 400 pixel 300 pixels 300 pixel 200 pixels 200 pixel 100 pixels 100 pixel <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Amikor az eszközre számokat másol, és az 'Album Előadó' 'Vegyes előadók', a Cantata az 'Előadó' címke az összes számnál 'Vegyes előadók'-ra lesz állítva és a számok 'Cím" jelzője 'SzámElőadó - SzámCím'-re változik.<hr/> Eszközre másolás során a Cantata ellenőrzi, hogy az 'Album Előadó' és az 'Előadó' egyaránt 'Vegyes Előadó'-e. Ha igen, megpróbálja a "Cím" címkéből előállítani az igazi előadót és eltávolítani az előadó nevét a 'Cím' jelzőből.<!p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Ha ezt engedélyezed, a Cantata az eszközök zenekönyvtárainak létrehoz cache-t. Ez felgyorsítja a következő könyvtár átvizsgálásokat (mert a cache fájlját használja ahelyett, hogy az egyes fájlok címkéit kellene végig olvasnia.)<hr/><b>Megjegyzés:</b> Ha egy másik alkalmazással frissíted az eszközök könyvtárait, akkor ez a cache elavulttá válik. Ennek helyesbítésére egyszerűen kattints a 'frissítés' ikonra az eszközlistán. Ennek hatására a cache fájlt eltávolítja és az eszköz tartalmát újra átnézi.</p> Do not transcode Ne kódolja át Transcode to %1 Átkódolás erre %1 %1 (%2 free) name (size free) %1 (%2 szabad) Copy To Library Másolás könyvtárba Forget Device Eszköz elfelejtése Add Device Eszköz hozzáadása Lookup album and track details? Keressem az album és szám részleteit? Refresh Frissítés Via CDDB CDDB útján Via MusicBrainz MusicBrainz útján Which type of refresh do you wish to perform? Milyen típusú frissítést szeretnél? Partial - Only new songs are scanned (quick) Részleges - csak az új számokat nézi át (gyors) Full - All songs are rescanned (slow) Teljes - minden számot újra átnéz (lassú) Partial Részleges Full Teljes Are you sure you wish to delete the selected songs? This cannot be undone. Biztosan törölni szeretnéd a kiválasztott dalokat? Ezt nem lehet visszavonni! Are you sure you wish to forget '%1'? Biztosan el akarod vetni? %1'? Are you sure you wish to eject Audio CD '%1 - %2'? Biztosan ki akarod adatni a hang CD-t '%1 - %2'? Eject Kiadás Are you sure you wish to disconnect '%1'? Biztosan le akarod választani '%1'? Disconnect Leválasztás Please close other dialogs first. Légy szíves zárd be előbb a másik párbeszédet! <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) szabadalmaztatott veszteséges digitális hang codec.<br>AAC azonos bitrátánál általában jobb hangminőséget ad mint az MP3. Ésszerű választás lehet iPod-okhoz és más hordozható médialejátszókhoz. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>AAC</b> kódoló támogatj a <a href=http://hu.wikipedia.org/wiki/Változó_bitráta>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br>Ezért a csúszkán lévő bitrátaérték csak becslése <a href=http://www.ffmpeg.org/faq.html#SEC21>az átlagos értéknek <b>150kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>120kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>200kb/s</b> fölött túlzó lehet. Expected average bitrate for variable bitrate encoding Változó bitrátás kódolás várható bitrátája Smaller file Kisebb fájl Better sound quality Jobb hangminőség <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://hu.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) veszteséges adattömörítést alkalmazó szabadalmaztatott digitális hang codec, <br>Tökéletlenségei ellenére a felhasználói hang tárolás elterjedt formátuma, és a hordozható zenejátszók széles köre támogatja. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>MP3</b> kódoló támogatja a <a href=http://en.wikipedia.org/wiki/MP3#VBR>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br><b>160kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>120kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>205kb/s</b> fölött túlzó lehet. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://hu.wikipedia.org/wiki/Ogg>Ogg Vorbis</a> nyílt jogtiszta veszteséges hangtömörítő hang codec. <br> Az MP3-nál kisebb fájlokat produkál azonos, vagy jobb minőség mellett. Az Ogg Vorbis általában kiváló választás, különösen azokat hordozható zenelejátszók esetén. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>Ogg Vorbis</b> kódoló támogatja <a href=http://hu.wikipedia.org/wiki/Vorbis>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek;ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br>A Vorbis kódoló -1 és 10 közötti minőségi besorolást használ egy adott elvárt hangminőség meghatározására A bitráta érték a csúszkán csak durva becslése (Vorbis adja meg) a kódolt szám minőségének. A valóságban az újabb Vorbis verziók bitrátája ennél kisebb. <b><b>5</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>3</b> alatt bárki számára alkalmatlan lehet és bármi <b>8</b> fölött túlzó lehet. Quality rating Minőségi besorolás Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://hu.wikipedia.org/wiki/Opus_(hangformátum)>Opus</a> szabadalommentes, veszteséges adattömörítést alkalmazó digitális audio codec. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>Opus</b> kódoló támogatja a <a href=http://hu.wikipedia.org/wiki/Változó_bitráta>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint a szám egésze során állandó bitráta. <br>Ezért a csúszkán lévő bitrátaérték csak becslése az átkódolt szám átlagos bitrátájának.<br><b>128kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>100kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>256kb/s</b> fölött túlzó lehet. Apple Lossless Apple veszteségmentes <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) veszteségmentes digitális zenetömörítő audio codec. <br>Csak a FLAC-ot nem támogató Apple zenelejátszók és lejátszók számára javasolt FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://hu.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) egy veszteségmentes, nyílt és jogdíjmentes digitális zenetömörítő codec. <br>Ha a zenédet hangminőség-romlás nélkül akarod tárolni, FLAC kitűnő választás. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. A FLAC <a href=http://flac.sourceforge.net/documentation_tools_flac.html>tömörítési szintjét</a> 0 és 8 közötti egész érték jelzi a <b>FLAC</b> általi a kódolás során a fájl mérete és a tömörítés sebessége közötti kompromisszumot. <br/>A tömörítés szintjét <b>0</b>-ra állítva a tömörítési idő rövid lesz, de viszonylag nagy fájlt eredményez. <br/>Más részről ha a tömörítés szintje <b>8<b>, a tömörítés nagyon lassú lesz, de a legkisebb mérete adja. <br/> Mellesleg, mivel definíciója szerint a FLAC veszteségmentes kódoló, a kimeneti hangfájl minősége a tömörítés értékétől függetlenül azonos lesz.</br>Minden <b>5<b> fölötti érték miközben drámaian növeli a tömörítési időt, a fájlméret csak kicsit lesz kisebb és nem ajánlott. Compression level Tömörítési szint Faster compression Gyorsabb tömörítés Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) Microsoft által kifejlesztett, szabadalmaztatott veszteséges hangtömörítési eljárás. <br>Csak azon hordozható zenejátszók esetén ajánlott, amelyek nem támogatják az Ogg Vorbis-t. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A szabadalmi korlátozások és a jogvédett kódoló visszafejtésének nehézségei következtében a Cantata által használt <b>WMA</b> kódoló <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>állandó bitrátát (CBR)</a> alkalmaz. <br>Ennek következtében a csúszkán a szám kódolásának jelzett bitrátája elég pontos becslés. <br><b>136kb/s<!b> jó választás zenék hordozható lejátszón történő hallgatására. <br>Minden <b>112kb/s</b> alatt nem kielégítő zenét eredményez és bármi <b>182kb/s</b> fölött valószínűleg túllövés. Filename Scheme Fájlnév-séma Various Artists Example album artist Vegyes előadók Wibble Example artist Zengetés Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. A következő változók lesznek lecserélve a megfelelő értelemben minden egyes szám nevében. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Változó</em></th><th><em>Gomb</em></th><th><em>Leírás</em></th></tr> Updating... Frissítés... Reading cache Cache olvasása %1 %2% Message percent %1 %2% Connecting to device... Csatlakozás az eszközhöz... No devices found Nem találtam eszközt Connected to device Csatlakozva az eszközhöz Disconnected from device Leválasztva az eszközről Updating folders... Könyvtárak frissítése Updating files... Fájlok frissítése... Updating tracks... számok frissítése... Not Connected Nincs csatlakozva %1 (Disc %2) %1 (Lemez %2) No matches found in MusicBrainz Nincs találat a MusicBrainz-ben Connection Csatlakozás Music Library Zenekönyvtár A remote device named '%1' already exists! Please choose a different name. A '%1' nevű távoli eszköz már létezik! Kérlek válassz másik nevet. Samba Share Szamba-megosztás Samba Share (Auto-discover host and port) Szamba-megosztás (kiszolgáló és port automatikus felderítése) Secure Shell (sshfs) Biztonságos héj (sshfs) Locally Mounted Folder Helyileg csatolt könyvtár Available Elérhető Not Available Nem elérhető Failed to resolve connection details for %1 Nem sikerült megállapítani a csatlakozási részleteket erre %1 Connecting... Csatlakozás... Password prompting does not work when cantata is started from the commandline. A jelszóbekérés nem működik, amikor a Cantata-t parancssorból indítják. No suitable ssh-askpass application installed! This is required for entering passwords. Nincs megfelelő ssh-jelszóbekérő alkalmazás telepítve. A jelszó beviteléhez szükséges. Mount point ("%1") is not empty! Csatolási pont ("%1") nem üres! "sshfs" is not installed! "sshfs" nincs telepítve! Disconnecting... Leválasztás... "fusermount" is not installed! "fusermount" nincs telepítve! Failed to connect to "%1" Nem sikerült csatlakozni ehhez "%1" Failed to disconnect from "%1" Nem sikerült leválasztani erről "%1" Capacity Unknown Kapacitás ismeretlen Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Keresés Check Items Tételek kiválasztása Uncheck Items Tételek kiválasztásának törlése Synchronize Szinkronizálás Device and library are in sync. Az eszköz és a könyvtár szinkronban van. Not Scanned Nincs átnézve (recommended) (ajánlott) Empty filename. Üres fájlnév Invalid filename. (%1) Érvénytelen fájlnév. (%1) Failed to save %1. Nem sikerült menteni %1. Failed to delete rules file. (%1) Nem sikerült törölni a dinamika szabálylistát. (%1) Invalid command. (%1) Érvénytelen parancs. (%1) Could not remove active rules link. Nem tudtam eltávolítani az aktív szabályok hivatkozást. Active rules is not a link. Az aktív szabályok nem hivatkozás. Could not create active rules link. Nem sikerült létrehozni az aktív szabályok hivatkozást. Rules file, %1, does not exist. A(z) %1 szabályok fájl nem létezik. Incorrect arguments supplied. Érvénytelen értékmegadás. Unknown method called. Ismeretlen eljárás hívása. Unknown error Ismeretlen hiba Start Dynamic Playlist Dinamikus lejátszási lista indítása Stop Dynamic Mode Dinamikus mód leállítása Dynamic Playlists Dinamikus lejátszási lista - Rating: %1..%2 - Besorolás: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Telepítened kell a "perl"-t a rendszeredre, a Cantata dinamikus módjának működéséhez. Failed to locate rules file - %1 Nem találom a szabályok fájlt - %1 Failed to remove previous rules file - %1 Nem sikerült eltávolítani a korábbi szabályok fájlt - %1 Failed to install rules file - %1 -> %2 Nem sikerült telepíteni a szabályok fájlt - %1 -> %2 Dynamizer has been terminated. A dinamizáló befejeződött. Saving rule Szabályok mentése Deleting rule Szabályok törlése Awaiting response for previous command. (%1) Választ vár a korábbi parancshoz. (%1) Failed to save %1. (%2) Nem sikerült menteni %1. (%2) Failed to control dynamizer state. (%1) Nem sikerült ellenőrizni a dinamizáló állapotát. (%1) Failed to set the current dynamic rules. (%1) Nem sikerült helyreállítani jelenlegi dinamika-szabályokat. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Hozzáad Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Szerkesztés Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Eltávolítás Remote dynamizer is not running. A távoli dinamizáló nem fut. Are you sure you wish to remove the selected rules? This cannot be undone. Biztosan eltávolítod a kiválasztott szabályokat? Ezt nem lehet visszavonni. Remove Dynamic Rules Dinamikaszabályok eltávolítása Dynamic Rule Dinamikaszabály <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>HIBA</b>: Az 'Évtől...' kisebb legyen mint az 'Évig...'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>HIBA:</b> A dátumtartomány túl nagy (legfeljebb %1 év lehet)</i> SimilarArtists Hasonló előadók AlbumArtist AlbumElőadó Include Befoglal Exclude Kizár (Exact) (Pontosan) Dynamic Rules Dinamikaszabályok None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Nincs About dynamic rules A dinamikus szabályokról Failed to save %1 Nem sikerült menteni %1 A set of rules named '%1' already exists! Overwrite? A '%1' nevű szabálycsoport mér létezik! Felülírjam? Overwrite Rules Szabályok felülírása Saving %1 Mentés %1 Deleting... Törlés... Name Név Item Count Elemek számolása Space Used Felhasznált hely Total space used: %1 Teljes felhasznált terület: %1 Covers Borítók Scaled Covers Méretarányos borítók Backdrops Hátterek Artist Information Előadói információk Album Information Albuminformációk Track Information Száminformációk Stream Listings Hangfolyamok (stream-ek) listája Podcast Directories Podcast-könyvtárak Scrobble Tracks Számok feljegyzése Delete All Minden törlése Delete all '%1' items? Törli mind a '%1' tételt? Delete Cache Items Cache tételek törlése Delete items from all selected categories? Az összes kiválasztott kategóriából törölsz elemeket? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Aktuális borító CoverArt Archive CoverArt archívum Image Kép Downloading... Letöltés... Image (%1 x %2 %3%) Image (width x height zoom%) Kép (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Az előadóról már van kép és a fájl írásvédett. A cover already exists for this album, and the file is not writeable. Az album borítója már létezik és a fájl írásvédett. '%1' Artist Image %1 Előadó képe '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Album borító Failed to set cover! Could not download to temporary file! Nem sikerült beállítani a borítót! Nem tudom letölteni az átmeneti fájlt! Failed to download image! A képet nem tudtam letölteni! Load Local Cover Helyi borító letöltése File is already in list! A fájl már szerepel a listában! Failed to read image! Nem tudtam olvasni a képet! Display Mutatás Failed to set cover! Could not make copy! Nem sikerül beállítani a borítót! Nem sikerült másolni! Failed to set cover! Could not backup original! Nem sikerül beállítani a borítót! Nem sikerült menteni az eredetit! Failed to set cover! Could not copy file to '%1'! Nem sikerül beállítani a borítót! Nem sikerült ebbe: '%1' másolni a fájlt! Searching... Keresés... Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Név: Open In File Manager Megnyitás fájlkezelőben Connection Established Kapcsolat felállt Connection Failed Csatlakozás sikertelen Grouped Albums Csoportosított lemezek Table Táblázat Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Sor lejátszása Folders Könyvtárak Playlists Lejátszási listák Devices - UMS, MTP (e.g. Android), and AudioCDs Eszközök - UMS, MPT (pl. Android) és hang CD-k. Search (via MPD) Keresés (MPD-n keresztül) Info - Current song information (artist, album, and lyrics) Infó. - a jelenlegi szám adatai (előadó, album és dalszöveg) Large Nagy Small Kicsi Tab-bar Lapsáv (fülek) Left Bal Right Jobb Top Felső Bottom Alsó Notifications Értesítések System default Rendszer alapbeállítása Album, Artist, Year Album, Előadó, Év Album, Year, Artist Album, Év, Előadó Artist, Album, Year Előadó, Album, Év Artist, Year, Album Előadó, Év, Album Year, Album, Artist Év, Album, Előadó Year, Artist, Album Év, Előadó, Album Configure Cantata... Cantata beállítása... Preferences Testreszabás Quit Megszakít About Cantata... Qt-only Cantata névjegye... Show Window Ablak mutatása Server information... Kiszolgáló-információk Refresh Database Adatbázis-frissítés Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Csatlakozás Collection Gyűjtemény Outputs Kimenetek Stop After Track A szám után leáll Add To Stored Playlist A tárolt lejátszási listához hozzáad Add Stream URL Stream URL hozzáadása Clear Töröl Center On Current Track Központosítás az aktuális számra Expanded Interface Lejátszó kinyitása Show Current Song Information A lejátszó kibontása Full Screen Teljes képernyő Random Véletlenszerűen Repeat Ismétlés Single Egyszeri When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. 'Egyszeri' beállítása esetén a lejátszás az aktuális dal után leáll, vagy a dal ismétlődik, ha az 'Ismétlés' engedélyezett. Consume Elhasznál When consume is activated, a song is removed from the play queue after it has been played. Amikor az 'Elhasznál' aktív, a dal a lejátszása után kikerül a lejátszási sorból. Find in Play Queue Keresés a lejátszási sorban Play Stream Hangfolyam (stream) lejátszása Locate In Library Könyvtárban megkeres Expand All Mind kibontása Collapse All Mind elrejtése Devices Eszközök Info Info Show Menubar Menüsáv megjelenítése &Music Zene &Edit Szerkesztés &View Nézet &Queue Sor &Settings Beállítások &Help Segítség Set Rating Besorolás beállítása No Rating Nincs besorolás Failed to locate any songs matching the dynamic playlist rules. A dinamikus lejátszási lista feltételeinek megfelelő fájl nem található. Connecting to %1 Csatlakozás ehhez %1 Refresh MPD Database? Adatbázist frissítsem? About Cantata Qt-only Cantata névjegye Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only A tartalmi nézet háttere innen származik <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only A tartalmi nézet metaadatai innen származnak <a href="http://www.wikipedia.org">Wikipédia</a> és <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Fontold meg a kedvenc zenéd feltöltését ide <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Egy podcast letöltése folyamatban van. A kilépés megszakítja az adatfolyam letöltését. Abort download and quit Letöltés megszakítása és kilépés Enabled: %1 Engedélyezve: %1 Disabled: %1 Nincs engedélyezve: %1 Server Information Szerver-információk <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Szerver</b></td></tr><tr><td align="right">Protokoll:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Idő:&nbsp;</td><td>%4</td></tr><tr><td align="right">Játszott:&nbsp;</td><td>%5</td></tr><tr><td align="right">Kezelők:&nbsp;</td><td>%6</td></tr><tr><td align="right">Címkék:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Adatbázis</b></td></tr><tr><td align="right">Előadók:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albumok:&nbsp;</td><td>%2</td></tr><tr><td align="right">Dalok:&nbsp;</td><td>%3</td></tr><tr><td align="right">Időtartam:&nbsp;</td><td>%4</td></tr><tr><td align="right">Frissítve:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 Az MPD a következő hibát jelezte: %1 Cantata Cantata Playback stopped Lejátszás leállt Remove all songs from play queue? A lejátszási sorból minden számot töröl? Priority Prioritás Enter priority (0..255): Add meg a prioritást (0-255): Playlist Name Lejátszási lista neve Enter a name for the playlist: Írj be egy nevet a lejátszási listának: '%1' is used to store favorite streams, please choose another name. '%1' lefoglalva a kedvenc adatfolyamok tárolására, kérlek válassz másik nevet! A playlist named '%1' already exists! Add to that playlist? A(z) '%1' nevű lejátszási lista már létezik Hozzáadjam ahhoz a listához? Existing Playlist Létező lejátszási lista Auto Automatikus <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>csatlakozva ehhez %1<br/>Alábbi elemeket a jelenleg kapcsolt MPD-gyűjteményre érvényesíti.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Nincs csatlakoztatva!<br/>Az alábbi elemek nem módosíthatóak, mivel a Cantata ben csatlakozik az MPD-hez.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> A Replay Gain egy 2001-ben abból a célból kiadott ajánlott szabvány, hogy beszabályozza az MP3 és Ogg Vorbis-hez hasonló számítógépes hangformátum várható hangerejét. Szám/album alapon dolgozik, és mára növekvő számú lejátszó támogatja..<br/><br/> A következő Replay Gain (lejátszási szint)beállítások használhatók <ul><li><i>Nincs</i> - értelemszerű.</li><li><i>Szám</i> - A hangerőt a szám ReplayGain jelölője szerint állítja be.</li><li><i>Album</i> - A hangerő beállításához az album ReplayGain jelölőjét használja.</li><li><i>Auto</i> - Amennyiben véletlenszerű lejátszás van, akkor a hangerőt a szám ReplayGain jelölője szerint, más esetben pedig az album jelölője szerint állítja be.</li></ul> Rename Átnevezés Remove Duplicates Másolatok eltávolítása Are you sure you wish to remove the selected playlists? This cannot be undone. Biztos el akarod távolítani a kiválasztott lejátszási listát? Ez nem vonható vissza. Remove Playlists Lejátszási lista eltávolítása A playlist named '%1' already exists! Overwrite? A(z) '%1' nvű lejátszási lista már létezik! Felülírod? Overwrite Playlist Lejátszási lista felülírása Rename Playlist Lejátszási lista eltávolítása Enter new name for playlist: Adj új nevet a lejátszási listának: Cannot add songs from '%1' to '%2' A dalok innen '%1'nem adhatók ide '%2' 1 Track 1 Szám %1 Tracks 1 Track (%2) 1 Szám (%2) %1 Tracks (%2) 1 Album 1 Album %1 Albums 1 Artist 1 Előadó %1 Artists 1 Stream 1 Adatfolyam %1 Streams 1 Entry 1 elem %1 Entries 1 Rule 1 Szabály %1 Rules 1 Podcast 1 Podcast %1 Podcasts 1 Episode 1 Rész %1 Episodes 1 Update available 1 Van frissítés %1 Updates available Collection Settings Gyűjtemény beállításai Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Lejátszás Playback Settings Lejátszás beállításai Interface Felület Interface Settings A felület beállításai Scrobbling Zenefeljegyzés Scrobbling Settings Zenefeljegyzés beállításai Audio CD Settings Hang CD beállításai Proxy Proxy Proxy Settings Qt-only Proxybeállítások Shortcuts Qt-only Gyorsbillentyűk Keyboard Shortcut Settings Qt-only Gyorsbillentyűk beállításai Cache Cache Cached Items Cache elemei Cantata Preferences A Cantata beállításai Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Beállítás Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Zeneszerző: Performer: Szereplő: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Műfaj: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Megjegyzés: Date: Dátum: Modified: Módosítva: Any: Bármely: No tracks found. Számokat nem találtam. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Elhelyező: Which type of collection do you wish to connect to? Milyen típusú válogatáshoz szeretnél csatlakozni? <i><b>NOTE:</b> %1</i> <i><b>Megjegyzés:</b> %1</i> Add Collection Gyűjteményt hozzáad Standard Szabványos Basic Alap Delete '%1'? Törlöd a(z) '%1'-t? Delete Törlés New Collection %1 Új gyűjtemény %1 Default Alapbeállítás Previous Track Előző szám Next Track Következő szám Play/Pause Lejátszás/Szünet Stop After Current Track A mostani szám után leáll Increase Volume Hangerő növelése Decrease Volume Hangerő csökkentése Save As Mentés mint... Add With Priority Hozzáad prioritással Set Priority Prioritás beállítása Highest Priority (255) Legmagasabb prioritás (255) High Priority (200) Magas prioritás (200) Medium Priority (125) Közepes prioritás (125) Low Priority (50) Alacsony prioritás (50) Default Priority (0) Alap prioritás (0) Custom Priority... Tetszőleges prioritás... Add To Playlist Lejátszási listához ad Organize Files Fájlok rendezése Edit Track Information Száminformációk szerkesztése Set Image Kép beállítása Find Keres Add To Play Queue Lejátszási listához hozzáad Now playing Most megy Play Lejátszás Pause Szünet Cue Sheet Kulcslap Playlist Lejátszási lista Configure Device Eszköz beállítása Refresh Device Eszköz frissítése Connect Device Eszköz csatlakoztatása Disconnect Device Eszköz leválasztása Edit CD Details CD-adatok szerkesztése No Devices Attached Nincs csatlakoztatott eszköz Not logged in Nincs bejelentkezve Logged in Bejelentkezve No subscriptions Nincs előfizetés You do not have an active subscription Nincs aktív előfizetésed Logged in (expiry:%1) Bejelentkezve (lejárat:%1) Session expired Munkamenet lejárt %1 by %2 Album by Artist %1 előadja %2 New Playlist... Új lejátszási lista... Smart Playlist Okos lejátszási lista Length Időtartam Disc Lemez Rating Besorolás Undo Visszavon Redo Újra végrehajt Shuffle Összekeverés Sort By Rendezés ... szerint Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Album előadója Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Szám címe TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble??? Not Loaded Nincs betöltve Loading... Betöltés... Bookmarks Könyvjelzők IceCast IceCast Favorites Kedvencek Bookmark Category Könyvjelző-kategóriák Add Stream To Favorites Hangfolyam hozzáadása a kedvencekhez Streams Stream-ek Unknown Ismeretlen "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed Csatlakozás ehhez %1 sikertelen Connection to %1 failed - please check your proxy settings Kapcsolódás %1 -hez nem sikerült - kérem ellenőrizd a proxy beállításait. Connection to %1 failed - incorrect password Csatlakozás ehhez %1 sikertelen - hibás jelszó Failed to send command to %1 - not connected Csatlakozás ehhez %1 sikertelen - nincs csatlakozva Failed to load. Please check user "mpd" has read permission. Betöltés sikertelen. Ellenőrizd a felhasználó "mpd" olvasási jogát. Failed to load. MPD can only play local files if connected via a local socket. Betöltés sikertelen. Az MPD csak helyi fájlokat játszik le, ha helyi csatlakozóra csatolva. Failed to send command. Disconnected from %1 Sikertelen parancsküldés. Leválasztva innen: %1 Failed to rename <b>%1</b> to <b>%2</b> Sikertelen átnevezés erről <b>%1</b> erre <b>%2</b> Failed to save <b>%1</b> Sikertelen mentés You cannot add parts of a cue sheet to a playlist! Kulcslap részletét nem adhatod a lejátszási listához! You cannot add a playlist to another playlist! Nem adhatsz lejátszási listát másik listához! Failed to send '%1' to %2. Please check %2 is registered with MPD. Nem sikerült %1 elküldése %2 -re. Ellenőrizd, hogy %2 MPD-be be lett-e jegyezve. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Egyedüli számok Personal Személyes Various Artists Vegyes előadók <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> a <b>%2</b>-ön <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> <b>%2</b>-vel <b>%3</b>-on No proxy Nincs proxy Use the system proxy settings Rendszer proxybeállításainak használata Manual proxy configuration Kézi proxybeállítás Jamendo Settings Jamendo beállítás MP3 MP3 Ogg Ogg Streaming format: A hangfolyam formátuma: Streaming Hangfolyam MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Magnatune Settings Magnatune-beállítások Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Felhasználónév: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Jelszó: Membership: Tagság: Downloads: Letöltések: Failed to parse Elemzés sikertelen Failed to download Letöltés sikertelen RSS: RSS: Website: Weblap: Podcast details A podcast részletei Select a podcast to display its details Válaszd ki a podcast-ot, amiek a részleteit megjelenítenéd Enter search term... Írd be a keresett kifejezést... Failed to fetch podcasts from %1 Nem sikerült podcast-ot letölteni erről %1 There was a problem parsing the response from %1 A(z) %1 válaszának elemzésével probléma adódott. Failed to download directory listing A könyvtárlistát nem sikerült letölteni Failed to parse directory listing Könyvtárlista elemzése sikertelen URL URL Enter podcast URL... Írd be a podcast URL-jét... Load Betölt Enter podcast URL below, and press 'Load' Írd be a podcast URL-jét és 'Betölt' gombot nyomd le. Invalid URL! Érvénytelen URL! Failed to fetch podcast! Nem sikerült podcast-ot letölteni Failed to parse podcast. A podcast elemzése sikertelen Cantata only supports audio podcasts! The URL entered contains only video podcasts. A Cantata csak audio podcast-ok lejátszását támogatja! Az URL csak videó podcast-okat tartalmaz. Subscribe Feliratkozás Enter URL Írd be a URL-t: Manual podcast URL Podcast kézi URL Search %1 Keresés %1 Search for podcasts on %1 Podcast keresése itt: %1 Add Podcast Subscription Podcast leírásának hozzáadása Browse %1 Böngészés %1 Browse %1 podcasts A(z) %1 podcast-ok böngészése You are already subscribed to this podcast! Már feliratkoztál erre a podcsat-ra! Subscription added Előfizetés hozzáadva %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Letölt: %1%) Failed to parse %1 Elemzés sikertelen %1 Cantata only supports audio podcasts! %1 contains only video podcasts. A Cantata csak audio podcast-ok lejátszását támogatja! A(z) %1 csak videó podcast-okat tartalmaz. Failed to download %1 Letöltés sikertelen %1 Check for new episodes: Új részek keresése: Download episodes to: Részek letöltése ide: Download automatically: Automatikus letöltés: Podcast Settings Podcast beállítások Manually Kézzel Every 15 minutes Minden 15. percben Every 30 minutes Minden 30. percben Every hour Minden órában Every 2 hours Minden 2. órában Every 6 hours Minden 6. órában Every 12 hours Minden 12. órában Every day Minden nap Every week Minden héten Don't automatically download episodes Ne töltse le automatikusan a részeket. Latest episode A legfrissebb rész Latest %1 episodes A legfrissebb %1 részek All episodes Az összes rész Add Subscription Előfizetés hozzáadása Remove Subscription Előfizetés eltávolítása Unsubscribe from '%1'? Leiratkozol a(z) '%1'-ről? Do you wish to download the selected podcast episodes? Biztosan eltávolítanád a kiválasztott %1 podcast epizódot? Cancel podcast episode downloads (both current and any that are queued)? Podcast-rész letöltését megszakítja (mind a folyamatban lévőt, mind a várakozót)? Do you wish to the delete downloaded files of the selected podcast episodes? Letörölnéd a következő kiválasztott podcast részek letöltött fájljait? Do you wish to mark the selected podcast episodes as new? Megjelölöd a kiválasztott podcast-részt újnak? Do you wish to mark the selected podcast episodes as listened? Megjelölöd a kiválasztott podcast-részt meghallgatottnak? Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Háttérkép Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) A művész képe Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Egyéni kép: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Elmosás: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Áttetszőség: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) A nézet automatikus váltása ez után: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) Ne legyen automatikus váltás ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Fekete háttér Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Az aktuális színösszeállítástól függetlenül a háttér sötétítése és fehér szöveg alkalmazása. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Mindig csukd össze egyetlen táblába Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) Csak az 'Előadó', 'Album', vagy 'Szám' mutatása, még ha elegendő hely van mindháromhoz. Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Csak az alapvető Wikipédia-szöveg mutatása Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) A Cantata a wikipédia oldalainak csökkentett változatát mutatja (képek, linkek stb. nélkül). Ez a vágás nem mindig 100%-ig pontos, mivel a Cantata alapból csak a bevezetőt mutatja. Ha a teljes cikk megjelenítését választod, akkor beolvasási gondok lehetnek. Emellett el kell távolítanod az összes (a 'Cache' oldal használatával) beolvasott cikket. no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Elérhető: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Kiválasztva: Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Dalok másolása innen: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Beállítandó) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Dalok másolása ide: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Célformátum: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Dalok felülírása To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Másolandó: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Album részletei Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Év: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Lemez: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Egyetlen előadó: Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Album- és száminformációk visszakeresése Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Elsőként evvel keressen: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) CDDB Host: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) CDDB Port: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Információk keresése a CD beillesztése után Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Audi kivonatolás Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Teljes paranoia mód (legjobb minőség) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Olvasási hiba átlépése - soha These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) Ezek a beállítások csak akkor érvényesek és szerkeszthetők, amikor az eszköz csatlakozott. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Zenekönyvtár: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Másolja az albumborítókat mint: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Borító maximális mérete: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Alaphangerő: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) 'Válogatás' munkakörnyezet: Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Zene automatikus keresése csatlakoztatáskor Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Cache használata Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Fájlnevek Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Fájlnév-séma: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) VFAT tároló Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Kizárólag ASCII-es karakterek használata Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Szóközök cseréje aláhúzásra Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) A 'The' hozzáfűzése az előadói nevekhez. Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Átkódolás Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Átkódolás csak akkor, ha a forrásfájl eltérő formátumú Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Példa: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) A fájlnév-sémákról The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) Az album előadója. A legtöbb album esetén, ez ugyanaz, mint a <i>Szám előadója</i>. Válogatások esetén ez többnyire <i>Válogatott előadók</i>-at jelent. The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) Az album címe. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Album címe The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) Zeneszerző. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) Az egyes számok előadói. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Szám előadója The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) A szám címe (a <i>Szám előadója</i> nélkül). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) A szám címe (a <i>Szám előadója</i>-val, ha az eltér az más, mint az<i>Album előadója</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Szám címe (+Artist) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Szám sorszáma Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Szám # The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) Többlemezes albumban a lemez sorszáma. A válogatások sokszor több lemezből állnak. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) Az album kiadásának éve. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) Az album műfaja. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) Ezek a beállítások csak akkor szerkeszthetők, amikor az eszköz nem csatlakozik. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Típus: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Opciók Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Port: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Felhasználó: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Kiszolgáló: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Megosztás: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) Az itt beírt jelszót <b>titkosítatlanul</b> tárolja a Cantata beállító fájljában. Ha a megosztás előtt akarod a jelszót bekéretni, akkor azt állítsd '-' -ra. Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Szolgáltatás neve: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Könyvtár: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Extra opciók: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) Az sshfs működéséből eredően jelszó beviteléhez egy megfelelő ssh-askpass alkalamzás (ksshaskpass, ssh-askpass-gnome, stb.) szükséges. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) Ez a párbeszéd csak távoli (pl. Samba-n keresztül csatlakozott), vagy helyi csatolású eszközök, hozzáadásához és leválasztásához való. A szokáos, USB-n csatlakozott médialejátszókat a Cantata csatlakozáskor automatikusan megjeleníti. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) A dinamikus szabályok neve - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) másodperc About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) Szabályokról Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Beleértve a következőknek megfelelő dalokat: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Kizárva a következőknek megfelelő dalokat: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Ehhez hasonló előadók: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Album előadó: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) Évből: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Akármi To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) Évig Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Pontos egyezés Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) Csak a keresni szándékozott címkeértékeket írd be. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) Műfajokra érvénes, ha csillaggal zárod le, akkor az több műfajt is jelent. Pl. a 'rock*' lehet 'Hard Rock' és 'Rock and Roll' is. Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Helyi fájl hozzáadása. Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Letöltött dalszövegek mentése a zenekönyvtárban Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Letöltött hátterek mentése a a zenekönyvtárban If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) Ha a Cantata-t választod borítók, szövegek, vagy hátterek zenekönyvtárban tárolására és nincs írási jogod arra, akkor a Cantata átirányítja a személyes cache könyvtáradba a mentést. Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Cantata első futása Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Légy üdvözölve a Cantata-ban <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>Cantata zenelejátszó kiszolgálóhoz (MPD) való, sokrétű és felhasználóbarát. Az MPD rugalmas, nagy tudású kiszolgáló oldali zenelejátszó alkalmazás.</p><p>Az MPD-ről további információk az MPD honlapján <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Ez a varázs végigvezet a Cantata megfelelő működéséhez szükséges alapbeállításokon.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Légy üdvözölve a Cantata-ban.</p></body></html> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Szabványos többfelhasználós, vagy szerver beállítások. Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Egyfelhasználós alapbeállítások Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Gyűjtemény részletei The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Az alábbiak a Cantata által megkívánt alapvető beállítások. Kérlek add meg a megfelelő adatokat és a 'Csatlakozás' gombbal ellenőrizd a kapcsolatot. The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) A 'Zenekönyvtár' beállítása borítók, dalszövegek stb. kereséséhez kell. Ha az MPD példányod távoli kiszolgálón van, ezt HTTP-címre is állítható. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Zenekönyvtár Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Válaszd ki a zenegyűjteményedet tartalmazó könyvtárat. <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata az Internetről letölti a hiányzó borítókat és dalszövegeket.</p><p>Ezek mindegyikéhez külön nyugtázni kell, hogy a Cantata az adott fájlokat a zenekönyvtárban, vagy a személyes cache, vagy config könyvtáradba mentse.</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) A 'Zenekönyvtár' HTTP-címre mutat és a Cantata aktuálisan nem képes feltölteni fájlokat a külső HTTP szerverre. Ezért a fenti beállításokat kijelöletlenül kell hagyni. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Befejezve! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata most már be lett állítva!<br/><br/>A Cantata konfigurációs párbeszéde használható a Cantata megjelenésének testreszabására csakúgy, mint extra MPD hostok stb. hozzáadására. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) A Cantata a számokat albumba rendezi az 'AlbumElőadó' címke alapján, ha van, ellenkező esetben az 'Előadó' címkét használja. Ha az album válogatás, akkor az 'AlbumElőadó' a működéshez a címkét a csoportosításhoz <b>be kell</b> állítani. A 'Válogatás' használata javasolt ez esetben. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>Figyelmeztetés: jelenleg a felhasználói csoport tagja vagy. A Cantata jobban fog működni (album borítók, dalszövegek stb. mentésében megfelelő jogosultságok mellett), ha te (vagy a rendszergazdád) hozzá ad(od magad) ehhez a csoporthoz. Ha te adod magad hozzá, akkor ki, és be kell jelentkezned ezek életbe léptetéséhez. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Oldalsáv Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Nézetek Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Stílus: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Pozíció: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Csak ikonok mutatása, szöveg nélkül Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Automatikus elrejtés Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Induláskor az albumok becsukása Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Aktuális album automatikus megnyitása Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Aktuális számhoz görgetés Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) Törlés előtt jelezzen Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) Önálló művelet (és billentyűparancs) a lejátszási sor kereséséhez Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Aktuális album borítója Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) Eszközsáv Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) A stop gomb mutatása Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) Az aktuális szám borítójának megjelenítése Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) A szám értékelésének megjelenítése External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Külső Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) A számok váltáskor legyen felugró üzenet Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Ikon megjelenítése az értesítési területen Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Bezáráskor az értesítési területre csukja össze On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) Elinduláskor Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) A fő menüt mutassa Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) A fő ablak elrejtése Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Az előző állapot helyreállítása General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Általános: Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) A hiányzó borítók letöltése a Last.fm-ről Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Törlési művelet megjelenítése a helyi menükben Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Egykattintásos elemaktiválás alkalmazása <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>Ez a Cantata megjelenését az alábbiak szerint megváltoztatja: <ul><li>a Lejátszás és szabályozó gombok 33%-kal szélesebbek</li><li>a nézetek 'válthatóak'</li><li>elemek vonszolásához a bal felső sarok 'érintése' szükséges</li><li>a görgetősáv csak pár pixel széles</li><li>a műveletek (pl.. 'Lejátszási sorhoz fűz') mindig látható (nem csak ha az egérmutató rajta van)</li><li>a tekerés gomboknál a felirat mellett + és - jelek</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) A felületet alakítsd sokkal érintésbarátabbra Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) Dalinformációk-tipp megjelenítése Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) Retina megjelenítők támogatása Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Nyelv: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) A 'Egykattintásos elemaktiválás alkalmazása' beállítása a Cantata újraindítását igényli. Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) A nyelvi beállítások változtatása a Cantata újraindítását igényli. Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) Az 'Érintésbarát' beállítás változtatása a Cantata újraindítását igényli. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) A retina megjelenítő-támogatás bekapcsolása a retina képernyőkön élesebb ikonokat eredményez, de a nem retina képernyőkön az ikonok elmosódottabbak lehetnek. A változtatás a Cantata újraindítását igényli. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dinamikus] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Kilépés a teljes képernyőből Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Kilépéskor a lejátszás megállítása Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) Lejátszás közbeni felfüggesztés megakadályozás If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) A Stop gomb lenyomása és tartása hatására menüben válaszható ki, hogy a leállás azonnali, vagy az aktuális szám után legyen. (A stop gomb a Megjelenés/Eszközsáv résznél aktiválható.) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Kimenet s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) A lejátszási szintről Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Az aktív kimenet beállítására használd az lenti kijelölő gombokat. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Gyűjtemény: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Borító fájlneve: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Fájlnév (kiterjesztés nélkül) lementi a borítót aszerint.<br/>Ha üresen marad 'borítót' használja.<br/><br/><i>A jelenlegi számnál az %előadó% album előadóra kerül lecserélésre, és az %album% pedig az album nevére.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) HTTP hangfolyam URL: 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) A 'HTTP adatfolyam URL' csak HTTP kimenetére beállított MPD esetén használatos, és ha szeretnéd a Cantata-t, hogy képes legyen az adatfolyam lejátszására. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) Ha a 'Zenekönyvtár' beállítását megváltoztatod, akkor a zenei adatbázist kézzel kell frissíteni. Ez 'Előadó'-, vagy 'Album'-nézetben a 'Adatbázisfrissítés' gomb lenyomásával érhető el. Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Mód: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP Proxy SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS Proxy Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Az aktív szolgáltatások beállítására használd az lenti kijelölő gombokat. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Szolgáltatás beállítása Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Karcolás használata: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Státusz: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Bejelentkezés Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Karcolás-számok Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Mutassa a 'Tetszik' gombot You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Előfizetés nélkül, ingyen hallgathatsz zenéket, de a Prémium-tagok sokkal jobb minőségű stream-eket hallgathatnak, hirdetések nélkül. Látogasd meg <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>, hogy prémium tagságra frissíts. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Prémium fiók Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Stream típusa: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Munkamenet lejárta: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Ezek a beállítások a Digitally Imported, JazzRadio.com, RockRadio.com, és Sky.fm-re érvényesek. If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) Ha felhasználói adatokat megadod, akkor a 'DI' státusz az adatfolyam-lista alatt megjelenik. Ez mutatja, hogy be vagy-e, vagy sem jelentkezve. Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Az aktív szolgáltatók beállítására használd az lenti kijelölő gombokat. Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) Az előre meghatározott kategóriák dőlttel szedettek és nem távolíthatóak el. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Keresés: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Billentyűparancs a kiválasztott művelethez Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Alapbeállítás: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Egyéni: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Album előadója: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Szám sorszáma: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Lemez száma: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) Besorolás (minősítés): <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(Vegyes)</i> Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) A besorolást (minősítést) külső adatbázis tárolja és <b>nem</b> dal fájlja. Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Eredeti név New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Új név Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) A besorolások elvesznek, ha a fájlt átnevezed. Your names NAME OF TRANSLATORS Török Árpád Your emails EMAIL OF TRANSLATORS torar@freemail.hu Show All Tracks Összes szám mutatása Show Untagged Tracks A címke nélküli számok mutatása Remove From List Listából eltávolít Album Gain Album hangereje Track Gain Szám hangereje Album Peak Album csúcs Track Peak Szám csúcs Scan Átnéz Update ReplayGain tags in tracks? A számokban a lejátszási színt címkéket frissíted? Update Tags Címkék frissítése Abort scanning of tracks? Számok átnézését felfüggeszted? Abort Megszakít Abort reading of existing tags? A beállított címkék beolvasását beszakítod? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Az <b>összes</b> számot átnézi?<br/><br/><i>Minden szám rendelkezik Lejátszási Szint jelölővel.</i> Do you wish to scan all tracks, or only tracks without existing tags? Átnézzem az összes számot, vagy csak azokat, amiknek nincs címkéjük? Untagged Tracks Címke nélküli számok All Tracks Minden szám Scanning tracks... Számok átnézése... Reading existing tags... Létező címkék beolvasása... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Hibás címkék?) Failed to update the tags of the following tracks: A következő számok címkéit nem sikerült frissíteni: Device is not connected. Az eszköz nem csatlakozik. %1 dB %1 dB Failed Sikertelen Original: %1 dB Eredeti: %1 dB Original: %1 Eredeti: %1 Remove the selected tracks from the list? A kiválasztott számokat eltávolítsam a listából? Remove Tracks Számok eltávolítása Invalid service Érvénytelen szolgáltatás Invalid method Érvénytelen eljárás Authentication failed Az azonosítás sikeretlen Invalid format Érvénytlen formátum Invalid parameters Érvénytelen paraméterek Invalid resource specified Érvénytlen forrásmeghatározás Operation failed Sikertelen művelet Invalid session key Érvénytelen munkamenet-kulcs Invalid API key Érvénytelen API-kulcs Service offline Szolgáltatás offline Last.fm is currently busy, please try again in a few minutes A last.fm pillanatnyilag foglalt, próbáld meg néhány perc múlva. Rate-limit exceeded A díjhatárt átlépted %1 error: %2 %1 hiba: %2 %1: Loved Current Track %1: aktuális szám tetszett %1: Love Current Track %1: az aktuális szám tetszik %1 (via MPD) scrobbler name (via MPD) %1 (MPD-n keresztül) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Amennyiben olyan zenefeljegyzést használsz, mint '(MPD-n keresztül)' (mint %1), akkor ennek már megnyitottnak ls futónak kell lennie. A Cantata csak ezen keresztül tudja 'kedvelni' a számokat és nem kapcsolható ki, vagy be a zenefeljegyzés. Authenticating... Azonosítás... Authenticated Azonosítva Not Authenticated Nincs azonosítva %1: Scrobble Tracks %1: számok feljegyzése Digitally Imported Settings Digitally Imported beállításai MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Kijelentkezés URL: URL: Add Stream Stream hozzáadása Edit Stream Stream szerkesztése <i><b>ERROR:</b> Invalid protocol</i> <i><b>Hiba:</b> érvénytelen protokoll</i> Installed Telepítve Update available Frissítés elérhető Check the providers you wish to install/update. Ellenőrizd a telepítés, frissítés szolgáltatóit. Install/Update Stream Providers Adatfolyam szolgáltatóinak telepítése, frissítése Downloading list... Lista letöltése... Digitally Imported Digitally Imported Local and National Radio (ListenLive) Helyi és nemzeti rádió (ListenLive) Failed to download list of stream providers! Nem tudtam letölteni az adatfolyam szolgáltatók listáját! Installing/updating %1 Telepítés, frissítés %1 Failed to install '%1' A(z) '%1' telepítése sikertelen Failed to download '%1' A(z) '%1' letöltése sikertelen Install/update the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat telepíti, vagy frissíti? Install the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat telepíti? Update the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat frissíti? Install/Update Telepít, frissít Abort installation/update? Telepítés, frissítés megszakítása? Downloading %1 Letöltés %1 Update all updateable providers Az összes frissíthető szolgáltató frissítése Import Streams Into Favorites Stream importálása kedvencek közé Export Favorite Streams Kedvenc stream-ek exportálása Add New Stream To Favorites Új stream hozzáadása a kedvencekhez Digitally Imported Service name Digitally Imported Import Streams Stream-ek importálása XML Streams (*.xml *.xml.gz *.cantata) XML stream-ek (*.xml *.xml.gz *.cantata) Export Streams Stream-ek exportálása XML Streams (*.xml.gz) XML adatfolyamok (*.xml.gz) Failed to create '%1'! A(z) '%1'-t nem sikerült létrehozni! Stream '%1' already exists! A(z) '%1' adatfolyam már létezik! A stream named '%1' already exists! A(z) '%1' nevű adatfolyam már létezik! Bookmark added Könyvjelző hozzáadva Already bookmarked Már van könyvjelzője Already in favorites Már szerepel a kedvencek között Reload '%1' streams? A(z) '%1' adatfolyamot újratölti? Are you sure you wish to remove bookmark to '%1'? Biztosan eltávolítaná a(z) '%1' könyvjelzőjét? Are you sure you wish to remove all '%1' bookmarks? Biztosan eltávolítaná az összes '%1' könyvjelzőt? Are you sure you wish to remove the %1 selected streams? Biztosan eltávolítanád a kiválasztott %1 stream-eket? Are you sure you wish to remove '%1'? Biztosan eltávolítaná a(z) '%1'-t? Added '%1'' to favorites %1 hozzáadva a kedvencekhez Configure Streams Hangfolyam beállítása From File... Fájlból... Download... Letöltve... Configure Provider Szolgáltató beállítása Install Telepítés Install Streams Stream-ek telelpítése Cantata Streams (*.streams) Cantata stream-ek (*.streams) A category named '%1' already exists! Overwrite? A(z) '%1' nevű kategória már létezik! Felülírja? Failed top open package file. A csomagfájl megnyitása sikertelen. Invalid file format! Érvénytelen fájlformátum! Failed to create stream category folder! Nem sikerült létrehozni a steram-kategória könyvtárát!<br/>%1 Failed to save stream list! Nem sikerült menteni a stream-ek litáját! Failed to remove streams folder! Nem sikerült eltávolítani a stream-ek könyvtárát &OK &Ok &Cancel Elvet &Yes Igen &No &Nem &Discard Visszavon &Save Ment &Apply &Alkalmaz &Close Bezár &Overwrite Felülír &Reset &Reset &Continue Folytat &Delete Töröl &Stop Állj &Remove Eltávolít &Previous Előző &Next Következő Configure... Beállítás... Password Jelszó Please enter password: Kérem a jelszót: Close Bezár Warning Figyelem Question Kérdés &Window Ablak Minimize Minimalizál Zoom Zoom Select Folder Könyvtárválasztás Select File Fájl-választás %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags Címkék Set 'Album Artist' from 'Artist' 'Album Előadó' beállítása 'Előadó'-ból Read Ratings from File A besorolás olvasása fájlból Write Ratings to File Besorolás kiírása fájlba All tracks Minden szám Apply "Various Artists" workaround to <b>all</b> tracks? "Válogatott előadók" munkakörnyezet alkalmazása az <b>összes</b> számra? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Ez a "Album előadót" és "Előadót" "Vegyes előadók"-ra állítja, és a "Címet" "Előadó Szám - Szám címe"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Visszaállítod a "Vegyes előadók" munkakörnyezetet az <b>összes</b> számra? Revert "Various Artists" workaround Visszaállítja a "Vegyes előadók" munkakörnyezetet <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Ahol az "Album előadó" megegyezik az "Előadó és a Címmel, illetve "Cím" "SzámElőadó -SzámCím" formátumú, az Előadót a "Cím"-ből veszi és a "Cím" maga egyszerűen a cím lesz, pl. <br/><br/>ha a "Cím" "Wibble - Wobble", akkor az "Előadó" egyszerűen "Wibble", a "Cím" pedig "Wobble" lesz.</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 'Album Előadó'-t állítasz az 'Előadó' helyett (ha az 'Album Előadó' üres) <b>az összes</b> számnál? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Beállítod 'Album előadót' az 'Előadó' helyett (ha az 'Album Előadó' üres)? Album Artist from Artist Album Előadó-ról Előadóra Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Nagybetűsítse a (pl. 'Cím', 'Előadó, 'Előadó' stb.) első betűjét az <b>összes</b> számnak? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Nagybetűsítse a szövegmező (pl. 'Cím', 'Előadó, 'Előadó' stb.) első betűjét? Adjust the value of each track number by: Az összes szám sorszámának módosítása ennyivel: Read ratings for all tracks from the music files? Az összes szám besorolásának kiolvasása a zenei fájlból? Read rating from music file? A besorolás kiolvasása a zenei fájlból? Ratings Besorolások Read Ratings Besorolások olvasása Read Rating Besorolások olvasása Read, and updated, ratings from the following tracks: A következő fájlok besorolás kiolvasva és frissítve: Not all Song ratings have been read from MPD! Nem minden dal besorolását olvasta ki az MPD-ről! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. A dalok besorolása nem a dalfájlokban, hanem az MPD-hez 'ragasztott' adatbázisban vannak. Hogy ezek az adott fájlban kerüljenek, a Cantata-nak előbb ki kell olvasni azokat.az MPD-ből. Song rating has not been read from MPD! A dalbesorolást az MPD-ből nem olvasta be! Write ratings for all tracks to the music files? Az összes szám besorolását az zenei fájlokba írja? Write rating to music file? A besorolást a zenefájlba írja? Write Ratings A besorolások kiírása Write Rating A besorolások kiírása Failed to write ratings of the following tracks: A következő számok besorolását nem tudtam kiírni? Failed to write rating to music file! Nem tudtam kiírni a besorolást a zenefájlba! All tracks [modified] Minden szám [módosítva] %1 [modified] %1 [módosítva] Would you also like to rename your song files, so as to match your tags? Átnevezed a dalok fájljait is, hogy egyezzenek a címkéiddel? Rename Files Fájlok átnevezése Abort renaming of files? Fájlátnevezés megszakítása? Source file does not exist! A forrás nem létezik! Destination file already exists! A célfájl már létezik! Failed to create destination folder! Nem sikerült létrehozni a célkönyvtárat! Failed to rename '%1' to '%2' '%1'-t nem sikerült átnevezni '%2'-re Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. A dalok besorolása nem a dalfájlokban, hanem az MPD-hez 'ragasztott' adatbázisban van tárolva. Ha a fájlt (vagy a könyvtárat, amiben van) átnevezed, akkor a dalra vonatkozó besorolás elveszik. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right">Zeneszerző:</td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Előadó</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Előadó:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Év:</b></td><td>%3</td></tr> Filter On Genre Műfaj szerinti szűrés All Genres Minden műfaj Go Back Visszalépés Menu Menü (Stream) (Stream) Search... Keresés... Close Search Bar Keresősáv bezárása Logged into %1 Bejelentkezve ide: %1 <b>NOT</b> logged into %1 <b>NEM VAGY</b> bejelentkezve a ide: %1 Basic Tree (No Icons) Alap fastruktúra (nincs ikon) Simple Tree Egyszerű fa Detailed Tree Részletes fa List Lista Grid Rács (osztás) Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. A dalfájl nem érhető el! Kérem ellenőrizd a "Zenekönyvtár" beállítását és az MPD "zenekönyvtárának" beállítását. Cannot access song files! Please check that the device is still attached. A dalfájlokat nem lehet elérni!! Kérem ellenőrizd, hogy az eszköz csatlakoztatva van-e még! Stretch Columns To Fit Window Oszlop igazítás az ablakhoz illeszkedéshez (Various) (Vegyes) Mute Némít Unmute Némítás kikapcsolása Volume %1% (Muted) Hangerő %1% (Némítva) Volume %1% Hangerő %1% 1 Track Singular 1 Szám %1 Tracks Plural (N!=1) %1 Számok 1 Track (%1) Singular 1 Szám (%1) %1 Tracks (%2) Plural (N!=1) %1 Számok (%2) 1 Album Singular 1 Album %1 Albums Plural (N!=1) %1 Album 1 Artist Singular 1 Előadó %1 Artists Plural (N!=1) %1 Előadók 1 Stream Singular 1 Stream %1 Streams Plural (N!=1) %1 Stream-ek 1 Entry Singular 1 Tétel %1 Entries Plural (N!=1) %1 Tételek 1 Rule Singular 1 Szabály %1 Rules Plural (N!=1) %1 Szabályok 1 Podcast Singular 1 Podcast %1 Podcasts Plural (N!=1) %1 Podcast 1 Episode Singular 1 Rész %1 Episodes Plural (N!=1) %1 Részek 1 Update available Singular 1 frissítés érhető el %1 Updates available Plural (N!=1) %1 számú frissítés érhető el ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Dalok másolása innen: Configure Beállítás (Needs configuring) (Beállítandó) Copy songs to: Dalok másolása ide: Destination format: Célformátum: Overwrite songs Dalok felülírása To copy: Másolandó: <b>INVALID</b> <b>ÉRVÉNYTELEN</b> <i>(When different)</i> <i>(Ha eltérő)</i> Artists:%1, Albums:%2, Songs:%3 Előadó:%1, Lemez:%2, Dal:%3 %1 free %1 szabad Local Music Library Helyi zenekönyvtár Audio CD Hang CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Nincs elegendő hely a célmeghajtón. A kiválasztott dal %1 helyet foglal, de csak %2 hely van. A dalt kisebb méretre kell csomagolni, hogy sikeresen másolható legyen. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Nincs elegendő hely a célmeghajtón. A kiválasztott dal %1 helyet foglal, de csak %2 hely van. Copy Songs To Library Copy Songs To Device Számok másolása az eszközre Copy Songs Dalok másolása Delete Songs Dalok törlése You have not configured the destination device. Continue with the default settings? Nem állítottad be a célmeghajtót- Folytassuk az alapbeállításokkal? Not Configured Nincs beállítva Use Defaults Alapbeállítások alkalmazása You have not configured the source device. Continue with the default settings? Nem állítottad be a célmeghajtót- Folytassuk az alapbeállításokkal? Are you sure you wish to stop? Biztosan le szeretnéd állítani? Stop Állj Device has been removed! Az eszköz el lett távolítva! Device is not connected! Az eszköz nincs csatlakoztatva! Device is busy? Az eszköz foglalt? Device has been changed? Az eszköz megváltozott? Clearing unused folders A használaton kívüli könyvtárak tisztítása Calculate ReplayGain for ripped tracks? Kiszámítsam a lejátszási szintet leszedett számokra? ReplayGain Lejátszási szint Calculate Számítás The destination filename already exists! A cél fájlnéve már létezik!<hr/>%1 Song already exists! A dal már létezik! Song does not exist! A dal nem létezik! Failed to create destination folder!<br/>Please check you have sufficient permissions. Nem sikerült létrehozni a célkönyvtárat!<br/>Ellenőrizd a jogosultságod szintjét.<hr/>%1 Source file no longer exists? A forrásfájl már nem létezik?<br/><br/<hr/>%1 Failed to copy. Nem sikerült másolni. Failed to delete. Nem sikerült törölni. Not connected to device. Nincs csatlakoztatva az eszközhöz. Selected codec is not available. A kiválasztott codec nem elérhető. Transcoding failed. Átkódolás sikertelen. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Nem sikerült létrehozni az átmeneti fájlt.<br/>(MTP eszközre átkódoláshoz kell.) Failed to read source file. Nem sikerül olvasni a forrásfájlt. <br/><br/<hr/>%1 Failed to write to destination file. Nem sikerül írni a célfájlba.<br/><br/<hr/>%1 No space left on device. Nem maradt hely az eszközön. Failed to update metadata. Nem sikerült frissíteni a metaadatokat. Failed to download track. A szám letöltése sikertelen. Failed to lock device. Nem sikerült zárolni az eszközt. Local Music Library Properties Helyi zenekönyvtár tulajdonságok Error Hiba Skip Kihagy Auto Skip Automatikusan kihagy Retry Újrapróbál Artist: Előadó: Album: Album Track: Szám: Source file: Forrás fájl: Destination file: Célfájl: File: Fájl: Saving cache A cache mentése Calculating... Számol... Time remaining: Hátralévő idő: AlbumDetails Album Details Album részletei Artist: Előadó: Composer: Zeneszerző: Title: Cím: Genre: Műfaj: Year: Év: Disc: Lemez: Single artist Egyetlen előadó: Tracks Számok Track Szám Artist Előadó Title Cím AlbumDetailsDialog Audio CD Hang CD Apply "Various Artists" Workaround Alkalmazza a "Vegyes előadók" munkakörnyezetet Revert "Various Artists" Workaround Visszaállítja a "Vegyes előadók" munkakörnyezetet Capitalize Váltás nagybetűre Adjust Track Numbers Számok sorszámának beállítása Tools Eszközök Apply "Various Artists" workaround? Alkalmazza a "Vegyes előadók" munkakörnyezetet? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Ez a "Album előadót" és "Előadót" "Vegyes előadók"-ra állítja, és a "Címet" "Előadó Szám - Szám címe"-re Revert "Various Artists" workaround? Visszaállítsa a "Vegyes előadók" munkamenetet? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Ahol az "Album előadó" megegyezik az "Előadó és a Címmel, illetve "Cím" "SzámElőadó -SzámCím" formájú, az Előadót a "Cím"-ből veszi és a "Cím" maga egyszerűen a cím lesz, pl. ha a "Cím" "Wibble - Wobble", akkor az "Előadó" egyszerűen "Wibble", a "Cím" pedig "Wobble" lesz. Revert Visszaállít Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Nagybetűsítse a 'Cím', 'Előadó, 'Album előadó' és 'Album' első betűjét? Adjust track number by: A szám sorszámának módosítás ennyivel: AlbumView Refresh Album Information Albuminformációk frissítése Album Lemez Tracks Számok ArtistView Refresh Artist Information Előadói információk frissítése Artist Előadó Albums Albumok Web Links Web-hivatkozások Similar Artists Hasonló művészek AudioCdDevice Reading disc Lemez olvasása %n Tracks (%1) %n Szám (%1) AudioCdSettings Album and Track Information Retrieval Album- és száminformációk visszakeresése Initially look up via: Elsőként evvel keressen: CDDB Host: CDDB Host: CDDB Port: CDDB Port: Lookup information as soon as CD is inserted Információk keresése a CD beillesztése után Audio Extraction Audi kivonatolás Full paranoia mode (best quality) Teljes paranoia mód (legjobb minőség) Never skip on read error Olvasási hiba átlépése - soha CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Kulcslap Playlist Lejátszási lista CacheItem Deleting... Törlés... Calculating... Számol... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Borítók Scaled Covers Méretarányos borítók Backdrops Hátterek Lyrics Dalszövegek Artist Information Előadói információk Album Information Albuminformációk Track Information Száminformációk Stream Listings Hangfolyamok (stream-ek) listája Podcast Directories Podcast-könyvtárak Wikipedia Languages Wikipédia nyelvek Scrobble Tracks Számok feljegyzése Delete All Minden törlése Delete all '%1' items? Törli mind a '%1' tételt? Delete Cache Items Cache tételek törlése Delete items from all selected categories? Az összes kiválasztott kategóriából törölsz elemeket? CacheTree Name Név Item Count Elemek számolása Space Used Felhasznált hely CddbInterface Data Track Adatsáv Failed to open CD device Nem sikerült megnyitni a CD-eszközt Track %1 Szám %1 Failed to create CDDB connection Nem sikerült CDDB kapcsolatot létesíteni Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB Nincs egyezés a CDDB-ben CDDB error: %1 CDDB hiba: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Több egyezést találtam. Kérem válaszd ki a megfelelőt az alábbiakból: Artist Előadó Title Cím Disc Selection Lemezválogatás %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Lemez %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Szövegtárak Wikipedia Languages Wikipédia nyelvek Other Továbbiak ContextWidget &Artist Előadó Al&bum Al&bum &Track Szám CoverDialog Search Keresés Add a local file Helyi fájl hozzáadása. Configure Beállítás This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive CoverArt archívum An image already exists for this artist, and the file is not writeable. Az előadóról már van kép és a fájl írásvédett. A cover already exists for this album, and the file is not writeable. Az album borítója már létezik és a fájl írásvédett. '%1' Artist Image %1 Előadó képe '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Album borító Failed to set cover! Could not download to temporary file! Nem sikerült beállítani a borítót! Nem tudom letölteni az átmeneti fájlt! Failed to download image! A képet nem tudtam letölteni! Load Local Cover Helyi borító letöltése Images (*.png *.jpg) Képek (*.png *.jpg) File is already in list! A fájl már szerepel a listában! Failed to read image! Nem tudtam olvasni a képet! Display Mutatás Remove Eltávolítás Failed to set cover! Could not make copy! Nem sikerül beállítani a borítót! Nem sikerült másolni! Failed to set cover! Could not backup original! Nem sikerül beállítani a borítót! Nem sikerült menteni az eredetit! Failed to set cover! Could not copy file to '%1'! Nem sikerül beállítani a borítót! Nem sikerült ebbe: '%1' másolni a fájlt! Searching... Keresés... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right">Zeneszerző:</td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Előadó</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Előadó:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Év:</b></td><td>%3</td></tr> CoverPreview Image Kép Downloading... Letöltés... Image (%1 x %2 %3%) Image (width x height zoom%) Kép (%1 x %2 %3%) CustomActionDialog Name: Név: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Hozzáad Edit Szerkesztés Remove Eltávolítás Name Név Command Remove the selected commands? Device Updating (%1)... Frissítés (%1)... Updating (%1%)... Frissítés (%1%)... DevicePropertiesDialog Device Properties Eszközjellemzők DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Ezek a beállítások csak akkor érvényesek és szerkeszthetők, amikor az eszköz csatlakozott. Name: Név: Music folder: Zenekönyvtár: Copy album covers as: Másolja az albumborítókat mint: Maximum cover size: Borító maximális mérete: Default volume: Alaphangerő: 'Various Artists' workaround 'Válogatás' munkakörnyezet: Automatically scan music when attached Zene automatikus keresése csatlakoztatáskor Use cache Cache használata Filenames Fájlnevek Filename scheme: Fájlnév-séma: VFAT safe VFAT tároló Use only ASCII characters Kizárólag ASCII-es karakterek használata Replace spaces with underscores Szóközök cseréje aláhúzásra Append 'The' to artist names A 'The' hozzáfűzése az előadói nevekhez. If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Átkódolás Only transcode if source file is of a different format Átkódolás csak akkor, ha a forrásfájl eltérő formátumú Only transcode if source is FLAC/WAV Don't copy covers Ne másolja a borítókat Embed cover within each file Borító beágyazás minden fájlba No maximum size Nincs maximális méret 400 pixels 400 pixel 300 pixels 300 pixel 200 pixels 200 pixel 100 pixels 100 pixel <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Amikor az eszközre számokat másol, és az 'Album Előadó' 'Vegyes előadók', a Cantata az 'Előadó' címke az összes számnál 'Vegyes előadók'-ra lesz állítva és a számok 'Cím" jelzője 'SzámElőadó - SzámCím'-re változik.<hr/> Eszközre másolás során a Cantata ellenőrzi, hogy az 'Album Előadó' és az 'Előadó' egyaránt 'Vegyes Előadó'-e. Ha igen, megpróbálja a "Cím" címkéből előállítani az igazi előadót és eltávolítani az előadó nevét a 'Cím' jelzőből.<!p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Ha ezt engedélyezed, a Cantata az eszközök zenekönyvtárainak létrehoz cache-t. Ez felgyorsítja a következő könyvtár átvizsgálásokat (mert a cache fájlját használja ahelyett, hogy az egyes fájlok címkéit kellene végig olvasnia.)<hr/><b>Megjegyzés:</b> Ha egy másik alkalmazással frissíted az eszközök könyvtárait, akkor ez a cache elavulttá válik. Ennek helyesbítésére egyszerűen kattints a 'frissítés' ikonra az eszközlistán. Ennek hatására a cache fájlt eltávolítja és az eszköz tartalmát újra átnézi.</p> Do not transcode Ne kódolja át Encoder Kódoló Transcode to %1 Átkódolás erre %1 %1 (%2 free) name (size free) %1 (%2 szabad) DevicesModel Configure Device Eszköz beállítása Refresh Device Eszköz frissítése Connect Device Eszköz csatlakoztatása Disconnect Device Eszköz leválasztása Edit CD Details CD-adatok szerkesztése Not Connected Nincs csatlakozva No Devices Attached Nincs csatlakoztatott eszköz DevicesPage Copy To Library Másolás könyvtárba Synchronise Forget Device Eszköz elfelejtése Add Device Eszköz hozzáadása Lookup album and track details? Keressem az album és szám részleteit? Refresh Frissítés Via CDDB CDDB útján Via MusicBrainz MusicBrainz útján Which type of refresh do you wish to perform? Milyen típusú frissítést szeretnél? Partial - Only new songs are scanned (quick) Részleges - csak az új számokat nézi át (gyors) Full - All songs are rescanned (slow) Teljes - minden számot újra átnéz (lassú) Partial Részleges Full Teljes Are you sure you wish to delete the selected songs? This cannot be undone. Biztosan törölni szeretnéd a kiválasztott dalokat? Ezt nem lehet visszavonni! Delete Songs Dalok törlése Are you sure you wish to forget '%1'? Biztosan el akarod vetni? %1'? Are you sure you wish to eject Audio CD '%1 - %2'? Biztosan ki akarod adatni a hang CD-t '%1 - %2'? Eject Kiadás Are you sure you wish to disconnect '%1'? Biztosan le akarod választani '%1'? Disconnect Leválasztás Please close other dialogs first. Légy szíves zárd be előbb a másik párbeszédet! DigitallyImported Not logged in Nincs bejelentkezve Logged in Bejelentkezve Unknown error Ismeretlen hiba No subscriptions Nincs előfizetés You do not have an active subscription Nincs aktív előfizetésed Logged in (expiry:%1) Bejelentkezve (lejárat:%1) Session expired Munkamenet lejárt DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Előfizetés nélkül, ingyen hallgathatsz zenéket, de a Prémium-tagok sokkal jobb minőségű stream-eket hallgathatnak, hirdetések nélkül. Látogasd meg <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>, hogy prémium tagságra frissíts. Premium Account Prémium fiók Username: Felhasználónév: Password: Jelszó: Stream type: Stream típusa: Status: Státusz: Login Bejelentkezés Session expiry: Munkamenet lejárta: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Ezek a beállítások a Digitally Imported, JazzRadio.com, RockRadio.com, és Sky.fm-re érvényesek. If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Ha felhasználói adatokat megadod, akkor a 'DI' státusz az adatfolyam-lista alatt megjelenik. Ez mutatja, hogy be vagy-e, vagy sem jelentkezve. Digitally Imported Settings Digitally Imported beállításai MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated Nincs azonosítva Authenticating... Azonosítás... Authenticated Azonosítva Logout Kijelentkezés DockMenu Play Lejátszás Pause Szünet DynamicPlaylists Start Dynamic Playlist Dinamikus lejátszási lista indítása Stop Dynamic Mode Dinamikus mód leállítása Dynamic Playlists Dinamikus lejátszási lista Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Telepítened kell a "perl"-t a rendszeredre, a Cantata dinamikus módjának működéséhez. Failed to locate rules file - %1 Nem találom a szabályok fájlt - %1 Failed to remove previous rules file - %1 Nem sikerült eltávolítani a korábbi szabályok fájlt - %1 Failed to install rules file - %1 -> %2 Nem sikerült telepíteni a szabályok fájlt - %1 -> %2 Dynamizer has been terminated. A dinamizáló befejeződött. Awaiting response for previous command. (%1) Választ vár a korábbi parancshoz. (%1) Saving rule Szabályok mentése Deleting rule Szabályok törlése Failed to save %1. (%2) Nem sikerült menteni %1. (%2) Failed to delete rules file. (%1) Nem sikerült törölni a dinamika szabálylistát. (%1) Failed to control dynamizer state. (%1) Nem sikerült ellenőrizni a dinamizáló állapotát. (%1) Failed to set the current dynamic rules. (%1) Nem sikerült helyreállítani jelenlegi dinamika-szabályokat. (%1) DynamicPlaylistsPage Add Hozzáad Edit Szerkesztés Remove Eltávolítás Remote dynamizer is not running. A távoli dinamizáló nem fut. Are you sure you wish to remove the selected rules? This cannot be undone. Biztosan eltávolítod a kiválasztott szabályokat? Ezt nem lehet visszavonni. Remove Dynamic Rules Dinamikaszabályok eltávolítása FancyTabWidget Configure... Beállítás... FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Letöltött dalszövegek mentése a zenekönyvtárban Save downloaded backdrops in music folder Letöltött hátterek mentése a a zenekönyvtárban If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Ha a Cantata-t választod borítók, szövegek, vagy hátterek zenekönyvtárban tárolására és nincs írási jogod arra, akkor a Cantata átirányítja a személyes cache könyvtáradba a mentést. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: Példa: About filename schemes A fájlnév-sémákról The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Az album előadója. A legtöbb album esetén, ez ugyanaz, mint a <i>Szám előadója</i>. Válogatások esetén ez többnyire <i>Válogatott előadók</i>-at jelent. Album Artist Album előadója The name of the album. Az album címe. Album Title Album címe The composer. Zeneszerző. Composer Zeneszerző The artist of each track. Az egyes számok előadói. Track Artist Szám előadója The track title (without <i>Track Artist</i>). A szám címe (a <i>Szám előadója</i> nélkül). Track Title Szám címe The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). A szám címe (a <i>Szám előadója</i>-val, ha az eltér az más, mint az<i>Album előadója</i>). Track Title (+Artist) Szám címe (+Artist) The track number. Szám sorszáma Track # Szám # The album number of a multi-album album. Often compilations consist of several albums. Többlemezes albumban a lemez sorszáma. A válogatások sokszor több lemezből állnak. CD # CD # The year of the album's release. Az album kiadásának éve. Year Év The genre of the album. Az album műfaja. Genre Műfaj Filename Scheme Fájlnév-séma Various Artists Example album artist Vegyes előadók Wibble Example artist Zengetés Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. A következő változók lesznek lecserélve a megfelelő értelemben minden egyes szám nevében. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Változó</em></th><th><em>Gomb</em></th><th><em>Leírás</em></th></tr> FolderPage Open In File Manager Megnyitás fájlkezelőben Are you sure you wish to delete the selected songs? This cannot be undone. Biztosan törölni szeretnéd a kiválasztott dalokat? Ezt nem lehet visszavonni! Delete Songs Dalok törlése FsDevice Updating... Frissítés... Reading cache Cache olvasása Saving cache A cache mentése %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Műfaj szerinti szűrés All Genres Minden műfaj GroupedViewDelegate Audio CD Hang CD Streams Stream-ek %n Track(s) %n Szám InitialSettingsWizard Cantata First Run Cantata első futása Welcome to Cantata Légy üdvözölve a Cantata-ban <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata zenelejátszó kiszolgálóhoz (MPD) való, sokrétű és felhasználóbarát. Az MPD rugalmas, nagy tudású kiszolgáló oldali zenelejátszó alkalmazás.</p><p>Az MPD-ről további információk az MPD honlapján <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Ez a varázs végigvezet a Cantata megfelelő működéséhez szükséges alapbeállításokon.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Légy üdvözölve a Cantata-ban.</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup Szabványos többfelhasználós, vagy szerver beállítások. <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup Egyfelhasználós alapbeállítások <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details Gyűjtemény részletei The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Az alábbiak a Cantata által megkívánt alapvető beállítások. Kérlek add meg a megfelelő adatokat és a 'Csatlakozás' gombbal ellenőrizd a kapcsolatot. Host: Elhelyező: Password: Jelszó: Music folder: Zenekönyvtár: Connect Csatlakozás The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. A 'Zenekönyvtár' beállítása borítók, dalszövegek stb. kereséséhez kell. Ha az MPD példányod távoli kiszolgálón van, ezt HTTP-címre is állítható. Music folder Zenekönyvtár Please choose the folder containing your music collection. Válaszd ki a zenegyűjteményedet tartalmazó könyvtárat. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata az Internetről letölti a hiányzó borítókat és dalszövegeket.</p><p>Ezek mindegyikéhez külön nyugtázni kell, hogy a Cantata az adott fájlokat a zenekönyvtárban, vagy a személyes cache, vagy config könyvtáradba mentse.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Letöltött dalszövegek mentése a zenekönyvtárban Save downloaded backdrops in music folder Letöltött hátterek mentése a a zenekönyvtárban If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Ha a Cantata-t választod borítók, szövegek, vagy hátterek zenekönyvtárban tárolására és nincs írási jogod arra, akkor a Cantata átirányítja a személyes cache könyvtáradba a mentést. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. A 'Zenekönyvtár' HTTP-címre mutat és a Cantata aktuálisan nem képes feltölteni fájlokat a külső HTTP szerverre. Ezért a fenti beállításokat kijelöletlenül kell hagyni. Finished! Befejezve! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata most már be lett állítva!<br/><br/>A Cantata konfigurációs párbeszéde használható a Cantata megjelenésének testreszabására csakúgy, mint extra MPD hostok stb. hozzáadására. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. A Cantata a számokat albumba rendezi az 'AlbumElőadó' címke alapján, ha van, ellenkező esetben az 'Előadó' címkét használja. Ha az album válogatás, akkor az 'AlbumElőadó' a működéshez a címkét a csoportosításhoz <b>be kell</b> állítani. A 'Válogatás' használata javasolt ez esetben. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Figyelmeztetés: jelenleg a felhasználói csoport tagja vagy. A Cantata jobban fog működni (album borítók, dalszövegek stb. mentésében megfelelő jogosultságok mellett), ha te (vagy a rendszergazdád) hozzá ad(od magad) ehhez a csoporthoz. Ha te adod magad hozzá, akkor ki, és be kell jelentkezned ezek életbe léptetéséhez. Not Connected Nincs csatlakozva Connection Established Kapcsolat felállt Connection Failed Csatlakozás sikertelen Cantata will now terminate InputDialog Password Jelszó Please enter password: Kérem a jelszót: InterfaceSettings Sidebar Oldalsáv Views Nézetek Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Opciók Style: Stílus: Position: Pozíció: Only show icons, no text Csak ikonok mutatása, szöveg nélkül Auto-hide Automatikus elrejtés Play Queue Sor lejátszása Initially collapse albums Induláskor az albumok becsukása Automatically expand current album Aktuális album automatikus megnyitása Scroll to current track Aktuális számhoz görgetés Prompt before clearing Törlés előtt jelezzen Separate action (and shortcut) for play queue search Önálló művelet (és billentyűparancs) a lejátszási sor kereséséhez Background Image Háttérkép None Nincs Current album cover Aktuális album borítója Custom image: Egyéni kép: Blur: Elmosás: 10px 10px Opacity: Áttetszőség: 40% 40% Toolbar Eszközsáv Show stop button A stop gomb mutatása Show cover of current track Az aktuális szám borítójának megjelenítése Show track rating A szám értékelésének megjelenítése External Külső Enable MPRIS D-BUS interface Show popup messages when changing tracks A számok váltáskor legyen felugró üzenet Show icon in notification area Ikon megjelenítése az értesítési területen Minimize to notification area when closed Bezáráskor az értesítési területre csukja össze On Start-up Elinduláskor Show main window A fő menüt mutassa Hide main window A fő ablak elrejtése Restore previous state Az előző állapot helyreállítása Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks Egyedüli számok If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General Általános: Fetch missing covers from Last.fm A hiányzó borítók letöltése a Last.fm-ről Show delete action in context menus Törlési művelet megjelenítése a helyi menükben Enforce single-click activation of items Egykattintásos elemaktiválás alkalmazása Changing the style setting will require a re-start of Cantata. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>Ez a Cantata megjelenését az alábbiak szerint megváltoztatja: <ul><li>a Lejátszás és szabályozó gombok 33%-kal szélesebbek</li><li>a nézetek 'válthatóak'</li><li>elemek vonszolásához a bal felső sarok 'érintése' szükséges</li><li>a görgetősáv csak pár pixel széles</li><li>a műveletek (pl.. 'Lejátszási sorhoz fűz') mindig látható (nem csak ha az egérmutató rajta van)</li><li>a tekerés gomboknál a felirat mellett + és - jelek</li></ul></p> Make interface more touch friendly A felületet alakítsd sokkal érintésbarátabbra Show song information tooltips Dalinformációk-tipp megjelenítése Support retina displays Retina megjelenítők támogatása Language: Nyelv: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. A 'Egykattintásos elemaktiválás alkalmazása' beállítása a Cantata újraindítását igényli. Changing the language setting will require a re-start of Cantata. A nyelvi beállítások változtatása a Cantata újraindítását igényli. Changing the 'touch friendly' setting will require a re-start of Cantata. Az 'Érintésbarát' beállítás változtatása a Cantata újraindítását igényli. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. A retina megjelenítő-támogatás bekapcsolása a retina képernyőkön élesebb ikonokat eredményez, de a nem retina képernyőkön az ikonok elmosódottabbak lehetnek. A változtatás a Cantata újraindítását igényli. Library Folders Könyvtárak Playlists Lejátszási listák Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Eszközök - UMS, MPT (pl. Android) és hang CD-k. Search (via MPD) Keresés (MPD-n keresztül) Info - Current song information (artist, album, and lyrics) Infó. - a jelenlegi szám adatai (előadó, album és dalszöveg) Large Nagy Small Kicsi Tab-bar Lapsáv (fülek) Left Bal Right Jobb Top Felső Bottom Alsó Images (*.png *.jpg) Képek (*.png *.jpg) 10px pixels 10px Notifications Értesítések English (en) System default Rendszer alapbeállítása %1% value% %1% %1 px pixels %1 px ItemView Go Back Visszalépés Updating... Frissítés... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings Jamendo beállítás MP3 MP3 Ogg Ogg Streaming format: A hangfolyam formátuma: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Nincs Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm Továbbiakat a last.fm-en olvashatsz LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Név Year Év Album, Artist, Year Album, Előadó, Év Album, Year, Artist Album, Év, Előadó Artist, Album, Year Előadó, Album, Év Artist, Year, Album Előadó, Év, Album Year, Album, Artist Év, Album, Előadó Year, Artist, Album Év, Előadó, Album Modified Date Group By Genre Műfaj Artist Előadó Album Lemez Are you sure you wish to delete the selected songs? This cannot be undone. Biztosan törölni szeretnéd a kiválasztott dalokat? Ezt nem lehet visszavonni! Delete Songs Dalok törlése LyricSettings Choose the websites you want to use when searching for lyrics. Válaszd ki a weboldalt, amit a dalszöveg keresésekor használni akarsz. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Ha a Cantata nem találja a dalszöveget, vagy nem megfelelőt talált, ezt a párbeszédet használd új keresési feltételek meghatározására. Például, a jelenlegi szám feldolgozás - ha így lenne, a dalszöveg keresése az eredeti előadóval segíthet. Ha a kereső új dalszöveget talál, hozzákapcsolható az eredeti dalhoz és előadóhoz, ahogy a Cantata jelzi. Title: Cím: Artist: Előadó: Search For Lyrics Dalszöveg keresése MPDConnection Unknown Ismeretlen Connection to %1 failed Csatlakozás ehhez %1 sikertelen Connection to %1 failed - please check your proxy settings Kapcsolódás %1 -hez nem sikerült - kérem ellenőrizd a proxy beállításait. Connection to %1 failed - incorrect password Csatlakozás ehhez %1 sikertelen - hibás jelszó Connecting to %1 Csatlakozás ehhez %1 Failed to send command to %1 - not connected Csatlakozás ehhez %1 sikertelen - nincs csatlakozva Failed to load. Please check user "mpd" has read permission. Betöltés sikertelen. Ellenőrizd a felhasználó "mpd" olvasási jogát. Failed to load. MPD can only play local files if connected via a local socket. Betöltés sikertelen. Az MPD csak helyi fájlokat játszik le, ha helyi csatlakozóra csatolva. MPD reported the following error: %1 Az MPD a következő hibát jelezte: %1 Failed to send command. Disconnected from %1 Sikertelen parancsküldés. Leválasztva innen: %1 Failed to rename <b>%1</b> to <b>%2</b> Sikertelen átnevezés erről <b>%1</b> erre <b>%2</b> Failed to save <b>%1</b> Sikertelen mentés You cannot add parts of a cue sheet to a playlist! Kulcslap részletét nem adhatod a lejátszási listához! You cannot add a playlist to another playlist! Nem adhatsz lejátszási listát másik listához! Failed to send '%1' to %2. Please check %2 is registered with MPD. Nem sikerült %1 elküldése %2 -re. Ellenőrizd, hogy %2 MPD-be be lett-e jegyezve. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Nincs Streaming Hangfolyam MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Magnatune-beállítások Username: Felhasználónév: Password: Jelszó: Membership: Tagság: Downloads: Letöltések: MainWindow [Dynamic] [Dinamikus] Exit Full Screen Kilépés a teljes képernyőből Configure Cantata... Cantata beállítása... Preferences Testreszabás Quit Megszakít About Cantata... Cantata névjegye... Show Window Ablak mutatása Server information... Kiszolgáló-információk Refresh Database Adatbázis-frissítés Refresh Frissítés Connect Csatlakozás Collection Gyűjtemény Outputs Kimenetek Stop After Track A szám után leáll Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist A tárolt lejátszási listához hozzáad Crop Others Add Stream URL Stream URL hozzáadása Clear Töröl Center On Current Track Központosítás az aktuális számra Expanded Interface Lejátszó kinyitása Show Current Song Information A lejátszó kibontása Full Screen Teljes képernyő Random Véletlenszerűen Repeat Ismétlés Single Egyszeri When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. 'Egyszeri' beállítása esetén a lejátszás az aktuális dal után leáll, vagy a dal ismétlődik, ha az 'Ismétlés' engedélyezett. Consume Elhasznál When consume is activated, a song is removed from the play queue after it has been played. Amikor az 'Elhasznál' aktív, a dal a lejátszása után kikerül a lejátszási sorból. Find in Play Queue Keresés a lejátszási sorban Play Stream Hangfolyam (stream) lejátszása Locate In Library Könyvtárban megkeres Play next Edit Track Information (Play Queue) Expand All Mind kibontása Collapse All Mind elrejtése Cancel Kilépés Play Queue Sor lejátszása Library Folders Könyvtárak Playlists Lejátszási listák Internet Devices Eszközök Search Keresés Info Info Show Menubar Menüsáv megjelenítése &Music Zene &Edit Szerkesztés &View Nézet &Queue Sor &Settings Beállítások &Help Segítség Set Rating Besorolás beállítása No Rating Nincs besorolás Failed to locate any songs matching the dynamic playlist rules. A dinamikus lejátszási lista feltételeinek megfelelő fájl nem található. Connecting to %1 Csatlakozás ehhez %1 Refresh MPD Database? Adatbázist frissítsem? About Cantata Cantata névjegye <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> A tartalmi nézet háttere innen származik <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> A tartalmi nézet metaadatai innen származnak <a href="http://www.wikipedia.org">Wikipédia</a> és <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Fontold meg a kedvenc zenéd feltöltését ide <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Egy podcast letöltése folyamatban van. A kilépés megszakítja az adatfolyam letöltését. Abort download and quit Letöltés megszakítása és kilépés Please close other dialogs first. Légy szíves zárd be előbb a másik párbeszédet! Enabled: %1 Engedélyezve: %1 Disabled: %1 Nincs engedélyezve: %1 Server Information Szerver-információk <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Szerver</b></td></tr><tr><td align="right">Protokoll:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Idő:&nbsp;</td><td>%4</td></tr><tr><td align="right">Játszott:&nbsp;</td><td>%5</td></tr><tr><td align="right">Kezelők:&nbsp;</td><td>%6</td></tr><tr><td align="right">Címkék:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Adatbázis</b></td></tr><tr><td align="right">Előadók:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albumok:&nbsp;</td><td>%2</td></tr><tr><td align="right">Dalok:&nbsp;</td><td>%3</td></tr><tr><td align="right">Időtartam:&nbsp;</td><td>%4</td></tr><tr><td align="right">Frissítve:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 Az MPD a következő hibát jelezte: %1 Cantata Cantata Playback stopped Lejátszás leállt Remove all songs from play queue? A lejátszási sorból minden számot töröl? Priority Prioritás Enter priority (0..255): Add meg a prioritást (0-255): Decrease priority for each subsequent track Playlist Name Lejátszási lista neve Enter a name for the playlist: Írj be egy nevet a lejátszási listának: '%1' is used to store favorite streams, please choose another name. '%1' lefoglalva a kedvenc adatfolyamok tárolására, kérlek válassz másik nevet! A playlist named '%1' already exists! Add to that playlist? A(z) '%1' nevű lejátszási lista már létezik Hozzáadjam ahhoz a listához? Existing Playlist Létező lejátszási lista %n Track(s) %n Szám %n Tracks (%1) %n Szám (%1) MenuButton Menu Menü MessageOverlay Cancel Kilépés Mpris (Stream) (Stream) MtpConnection Connecting to device... Csatlakozás az eszközhöz... No devices found Nem találtam eszközt Connected to device Csatlakozva az eszközhöz Disconnected from device Leválasztva az eszközről Updating folders... Könyvtárak frissítése Updating files... Fájlok frissítése... Updating tracks... számok frissítése... MtpDevice Not Connected Nincs csatlakozva %1 free %1 szabad MusicBrainz Failed to open CD device Nem sikerült megnyitni a CD-eszközt Track %1 Szám %1 %1 (Disc %2) %1 (Lemez %2) No matches found in MusicBrainz Nincs találat a MusicBrainz-ben MusicLibraryModel Cue Sheet Kulcslap Playlist Lejátszási lista %n Track(s) %n Szám %n Artist(s) %n Előadó %n Album(s) %n Album %n Tracks (%1) %n Szám (%1) %1 by %2 Album by Artist %1 előadja %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>Megjegyzés:</b> %1</i> NowPlayingWidget (Stream) (Stream) OSXStyle &Window Ablak Close Bezár Minimize Minimalizál Zoom Zoom OnlineDbService Downloading...%1% Parsing music list.... Failed to download Letöltés sikertelen %n Artist(s) %n Előadó OnlineDbWidget Group By Genre Műfaj Artist Előadó Configure Beállítás The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Letöltés Re-download music listing? OnlineSearchService Searching... Keresés... OnlineSearchWidget No tracks found. Számokat nem találtam. %n Tracks (%1) %n Szám (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Az aktív szolgáltatások beállítására használd az lenti kijelölő gombokat. Configure Service Szolgáltatás beállítása OnlineView Song Information Dalinformációk OnlineXmlParser Failed to parse Elemzés sikertelen OpmlBrowsePage Reload Újratöltés Failed to download directory listing A könyvtárlistát nem sikerült letölteni Failed to parse directory listing Könyvtárlista elemzése sikertelen OtherSettings Background Image Háttérkép None Nincs Artist image A művész képe Custom image: Egyéni kép: Blur: Elmosás: 10px 10px Opacity: Áttetszőség: 40% 40% Automatically switch to view after: A nézet automatikus váltása ez után: Do not auto-switch Ne legyen automatikus váltás ms ms Dark background Fekete háttér Darken background, and use white text, regardless of current color palette. Az aktuális színösszeállítástól függetlenül a háttér sötétítése és fehér szöveg alkalmazása. Always collapse into a single pane Mindig csukd össze egyetlen táblába Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Csak az 'Előadó', 'Album', vagy 'Szám' mutatása, még ha elegendő hely van mindháromhoz. Only show basic wikipedia text Csak az alapvető Wikipédia-szöveg mutatása Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). A Cantata a wikipédia oldalainak csökkentett változatát mutatja (képek, linkek stb. nélkül). Ez a vágás nem mindig 100%-ig pontos, mivel a Cantata alapból csak a bevezetőt mutatja. Ha a teljes cikk megjelenítését választod, akkor beolvasási gondok lehetnek. Emellett el kell távolítanod az összes (a 'Cache' oldal használatával) beolvasott cikket. Images (*.png *.jpg) Képek (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Könyvtárválasztás Select File Fájl-választás PlayQueueModel Title Cím Artist Előadó Album Lemez # Track number Length Időtartam Disc Lemez Year Év Original Year Genre Műfaj Priority Prioritás Composer Zeneszerző Performer Szereplő Rating Besorolás Remove Duplicates Másolatok eltávolítása Undo Visszavon Redo Újra végrehajt Shuffle Összekeverés Tracks Számok Albums Albumok Sort By Rendezés ... szerint Album Artist Album előadója Track Title Szám címe Track Number # (Track Number) PlayQueueView Remove Eltávolítás PlaybackSettings Playback Lejátszás Fa&deout on stop: None Nincs ms ms Stop playback on exit Kilépéskor a lejátszás megállítása Inhibit suspend whilst playing Lejátszás közbeni felfüggesztés megakadályozás If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) A Stop gomb lenyomása és tartása hatására menüben válaszható ki, hogy a leállás azonnali, vagy az aktuális szám után legyen. (A stop gomb a Megjelenés/Eszközsáv résznél aktiválható.) Output Kimenet <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Nincs csatlakoztatva!<br/>Az alábbi elemek nem módosíthatóak, mivel a Cantata ben csatlakozik az MPD-hez.</i> &Crossfade between tracks: s s Replay &gain: About replay gain A lejátszási szintről Use the checkboxes below to control the active outputs. Az aktív kimenet beállítására használd az lenti kijelölő gombokat. Track Szám Album Lemez Auto Automatikus <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>csatlakozva ehhez %1<br/>Alábbi elemeket a jelenleg kapcsolt MPD-gyűjteményre érvényesíti.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> A Replay Gain egy 2001-ben abból a célból kiadott ajánlott szabvány, hogy beszabályozza az MP3 és Ogg Vorbis-hez hasonló számítógépes hangformátum várható hangerejét. Szám/album alapon dolgozik, és mára növekvő számú lejátszó támogatja..<br/><br/> A következő Replay Gain (lejátszási szint)beállítások használhatók <ul><li><i>Nincs</i> - értelemszerű.</li><li><i>Szám</i> - A hangerőt a szám ReplayGain jelölője szerint állítja be.</li><li><i>Album</i> - A hangerő beállításához az album ReplayGain jelölőjét használja.</li><li><i>Auto</i> - Amennyiben véletlenszerű lejátszás van, akkor a hangerőt a szám ReplayGain jelölője szerint, más esetben pedig az album jelölője szerint állítja be.</li></ul> PlaylistRule Type: Típus: Include songs that match the following: Beleértve a következőknek megfelelő dalokat: Exclude songs that match the following: Kizárva a következőknek megfelelő dalokat: Artist: Előadó: Artists similar to: Ehhez hasonló előadók: Album Artist: Album előadó: Composer: Zeneszerző: Album: Album Title: Cím: Genre Műfaj From Year: Évből: Any Akármi To Year: Évig Comment: Megjegyzés: Filename / path: Exact match Pontos egyezés Only enter values for the tags you wish to be search on. Csak a keresni szándékozott címkeértékeket írd be. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Műfajokra érvénes, ha csillaggal zárod le, akkor az több műfajt is jelent. Pl. a 'rock*' lehet 'Hard Rock' és 'Rock and Roll' is. PlaylistRuleDialog Dynamic Rule Dinamikaszabály Smart Rule Add Hozzáad <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>HIBA</b>: Az 'Évtől...' kisebb legyen mint az 'Évig...'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>HIBA:</b> A dátumtartomány túl nagy (legfeljebb %1 év lehet)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules A dinamikus szabályok neve Add Hozzáad Edit Szerkesztés Remove Eltávolítás Songs with ratings between: - - Songs with duration between: seconds másodperc Number of songs in play queue: Order songs: About Rules Szabályokról PlaylistRulesDialog Dynamic Rules Dinamikaszabályok None Nincs No Limit About dynamic rules A dinamikus szabályokról Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 Nem sikerült menteni %1 A set of rules named '%1' already exists! Overwrite? A '%1' nevű szabálycsoport mér létezik! Felülírjam? Overwrite Rules Szabályok felülírása Saving %1 Mentés %1 PlaylistsModel New Playlist... Új lejátszási lista... Stored Playlists Standard playlists %n Tracks (%1) %n Szám (%1) Smart Playlist Okos lejátszási lista Plurals %n Track(s) %n Szám %n Tracks (%1) %n Szám (%1) %n Album(s) %n Album %n Artist(s) %n Előadó %n Stream(s) %n Stream %n Entry(s) %n Tétel %n Rule(s) %n Szabály %n Podcast(s) %n Podcast %n Episode(s) %n Rész %n Update(s) available %n frissítés érhető el PodcastPage RSS: RSS: Website: Weblap: Podcast details A podcast részletei Select a podcast to display its details Válaszd ki a podcast-ot, amiek a részleteit megjelenítenéd PodcastSearchDialog Subscribe Feliratkozás Enter URL Írd be a URL-t: Manual podcast URL Podcast kézi URL Search %1 Keresés %1 Search for podcasts on %1 Podcast keresése itt: %1 Add Podcast Subscription Podcast leírásának hozzáadása Browse %1 Böngészés %1 Browse %1 podcasts A(z) %1 podcast-ok böngészése You are already subscribed to this podcast! Már feliratkoztál erre a podcsat-ra! Subscription added Előfizetés hozzáadva PodcastSearchPage Enter search term... Írd be a keresett kifejezést... Search Keresés Failed to fetch podcasts from %1 Nem sikerült podcast-ot letölteni erről %1 There was a problem parsing the response from %1 A(z) %1 válaszának elemzésével probléma adódott. PodcastService Subscribe to RSS feeds %n Podcast(s) %n Podcast %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) %n Rész (Downloading: %1%) (Letölt: %1%) Failed to parse %1 Elemzés sikertelen %1 Cantata only supports audio podcasts! %1 contains only video podcasts. A Cantata csak audio podcast-ok lejátszását támogatja! A(z) %1 csak videó podcast-okat tartalmaz. Failed to download %1 Letöltés sikertelen %1 PodcastSettingsDialog Check for new episodes: Új részek keresése: Download episodes to: Részek letöltése ide: Download automatically: Automatikus letöltés: Podcast Settings Podcast beállítások Manually Kézzel Every 15 minutes Minden 15. percben Every 30 minutes Minden 30. percben Every hour Minden órában Every 2 hours Minden 2. órában Every 6 hours Minden 6. órában Every 12 hours Minden 12. órában Every day Minden nap Every week Minden héten Don't automatically download episodes Ne töltse le automatikusan a részeket. Latest episode A legfrissebb rész Latest %1 episodes A legfrissebb %1 részek All episodes Az összes rész PodcastUrlPage URL URL Enter podcast URL... Írd be a podcast URL-jét... Load Betölt Enter podcast URL below, and press 'Load' Írd be a podcast URL-jét és 'Betölt' gombot nyomd le. Invalid URL! Érvénytelen URL! Failed to fetch podcast! Nem sikerült podcast-ot letölteni Failed to parse podcast. A podcast elemzése sikertelen Cantata only supports audio podcasts! The URL entered contains only video podcasts. A Cantata csak audio podcast-ok lejátszását támogatja! Az URL csak videó podcast-okat tartalmaz. PodcastWidget Add Subscription Előfizetés hozzáadása Remove Subscription Előfizetés eltávolítása Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Leiratkozol a(z) '%1'-ről? Do you wish to download the selected podcast episodes? Biztosan eltávolítanád a kiválasztott %1 podcast epizódot? Cancel podcast episode downloads (both current and any that are queued)? Podcast-rész letöltését megszakítja (mind a folyamatban lévőt, mind a várakozót)? Do you wish to the delete downloaded files of the selected podcast episodes? Letörölnéd a következő kiválasztott podcast részek letöltött fájljait? Do you wish to mark the selected podcast episodes as new? Megjelölöd a kiválasztott podcast-részt újnak? Do you wish to mark the selected podcast episodes as listened? Megjelölöd a kiválasztott podcast-részt meghallgatottnak? Refresh all subscriptions? Refresh Frissítés Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track A Cantata éppen számot játszik le PreferencesDialog Collection Gyűjtemény Collection Settings Gyűjtemény beállításai Playback Lejátszás Playback Settings Lejátszás beállításai Downloaded Files Downloaded Files Settings Interface Felület Interface Settings A felület beállításai Info Info Info View Settings Scrobbling Zenefeljegyzés Scrobbling Settings Zenefeljegyzés beállításai Audio CD Hang CD Audio CD Settings Hang CD beállításai Proxy Proxy Proxy Settings Proxybeállítások Shortcuts Gyorsbillentyűk Keyboard Shortcut Settings Gyorsbillentyűk beállításai Cache Cache Cached Items Cache elemei Custom Actions Cantata Preferences A Cantata beállításai Configure Beállítás ProxySettings Mode: Mód: Type: Típus: HTTP Proxy HTTP Proxy SOCKS Proxy SOCKS Proxy Host: Elhelyező: Port: Port: Username: Felhasználónév: Password: Jelszó: No proxy Nincs proxy Use the system proxy settings Rendszer proxybeállításainak használata Manual proxy configuration Kézi proxybeállítás QObject Track listing Számlista Read more on wikipedia Továbbiakat a Wikipédiából. Open in browser Megnyitás böngészőben <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) szabadalmaztatott veszteséges digitális hang codec.<br>AAC azonos bitrátánál általában jobb hangminőséget ad mint az MP3. Ésszerű választás lehet iPod-okhoz és más hordozható médialejátszókhoz. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>AAC</b> kódoló támogatj a <a href=http://hu.wikipedia.org/wiki/Változó_bitráta>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br>Ezért a csúszkán lévő bitrátaérték csak becslése <a href=http://www.ffmpeg.org/faq.html#SEC21>az átlagos értéknek <b>150kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>120kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>200kb/s</b> fölött túlzó lehet. Expected average bitrate for variable bitrate encoding Változó bitrátás kódolás várható bitrátája Smaller file Kisebb fájl Better sound quality Jobb hangminőség <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://hu.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) veszteséges adattömörítést alkalmazó szabadalmaztatott digitális hang codec, <br>Tökéletlenségei ellenére a felhasználói hang tárolás elterjedt formátuma, és a hordozható zenejátszók széles köre támogatja. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>MP3</b> kódoló támogatja a <a href=http://en.wikipedia.org/wiki/MP3#VBR>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br><b>160kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>120kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>205kb/s</b> fölött túlzó lehet. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://hu.wikipedia.org/wiki/Ogg>Ogg Vorbis</a> nyílt jogtiszta veszteséges hangtömörítő hang codec. <br> Az MP3-nál kisebb fájlokat produkál azonos, vagy jobb minőség mellett. Az Ogg Vorbis általában kiváló választás, különösen azokat hordozható zenelejátszók esetén. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>Ogg Vorbis</b> kódoló támogatja <a href=http://hu.wikipedia.org/wiki/Vorbis>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek;ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint az állandó bitráta a szám egésze során. <br>A Vorbis kódoló -1 és 10 közötti minőségi besorolást használ egy adott elvárt hangminőség meghatározására A bitráta érték a csúszkán csak durva becslése (Vorbis adja meg) a kódolt szám minőségének. A valóságban az újabb Vorbis verziók bitrátája ennél kisebb. <b><b>5</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>3</b> alatt bárki számára alkalmatlan lehet és bármi <b>8</b> fölött túlzó lehet. Quality rating Minőségi besorolás Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://hu.wikipedia.org/wiki/Opus_(hangformátum)>Opus</a> szabadalommentes, veszteséges adattömörítést alkalmazó digitális audio codec. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A Cantata által használt <b>Opus</b> kódoló támogatja a <a href=http://hu.wikipedia.org/wiki/Változó_bitráta>változó bitráta (VBR)</a> beállítását, ami azt jelenti, hogy értéke változik a szám során, annak megfelelően, hogy a hanganyag tartalma mennyire összetett. A komplexebb adatszakaszok magasabb bitrátával lesznek kódolva, mint a kevésbé komplexek; ez a megközelítés egyszerre produkál jobb minőséget és kisebb fájlt, mint a szám egésze során állandó bitráta. <br>Ezért a csúszkán lévő bitrátaérték csak becslése az átkódolt szám átlagos bitrátájának.<br><b>128kb/s</b> jó választás zenék hordozható lejátszókon történő hallgatására. <br/>Bármi <b>100kb/s</b> alatt bárki számára alkalmatlan lehet és bármi <b>256kb/s</b> fölött túlzó lehet. Bitrate Bitráta Apple Lossless Apple veszteségmentes <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) veszteségmentes digitális zenetömörítő audio codec. <br>Csak a FLAC-ot nem támogató Apple zenelejátszók és lejátszók számára javasolt FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://hu.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) egy veszteségmentes, nyílt és jogdíjmentes digitális zenetömörítő codec. <br>Ha a zenédet hangminőség-romlás nélkül akarod tárolni, FLAC kitűnő választás. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. A FLAC <a href=http://flac.sourceforge.net/documentation_tools_flac.html>tömörítési szintjét</a> 0 és 8 közötti egész érték jelzi a <b>FLAC</b> általi a kódolás során a fájl mérete és a tömörítés sebessége közötti kompromisszumot. <br/>A tömörítés szintjét <b>0</b>-ra állítva a tömörítési idő rövid lesz, de viszonylag nagy fájlt eredményez. <br/>Más részről ha a tömörítés szintje <b>8<b>, a tömörítés nagyon lassú lesz, de a legkisebb mérete adja. <br/> Mellesleg, mivel definíciója szerint a FLAC veszteségmentes kódoló, a kimeneti hangfájl minősége a tömörítés értékétől függetlenül azonos lesz.</br>Minden <b>5<b> fölötti érték miközben drámaian növeli a tömörítési időt, a fájlméret csak kicsit lesz kisebb és nem ajánlott. Compression level Tömörítési szint Faster compression Gyorsabb tömörítés Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) Microsoft által kifejlesztett, szabadalmaztatott veszteséges hangtömörítési eljárás. <br>Csak azon hordozható zenejátszók esetén ajánlott, amelyek nem támogatják az Ogg Vorbis-t. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. A bitráta az adatminőség mérésére szolgál a szám egy másodpercére vonatkoztatva.<br> A szabadalmi korlátozások és a jogvédett kódoló visszafejtésének nehézségei következtében a Cantata által használt <b>WMA</b> kódoló <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>állandó bitrátát (CBR)</a> alkalmaz. <br>Ennek következtében a csúszkán a szám kódolásának jelzett bitrátája elég pontos becslés. <br><b>136kb/s<!b> jó választás zenék hordozható lejátszón történő hallgatására. <br>Minden <b>112kb/s</b> alatt nem kielégítő zenét eredményez és bármi <b>182kb/s</b> fölött valószínűleg túllövés. Empty filename. Üres fájlnév Invalid filename. (%1) Érvénytelen fájlnév. (%1) Failed to save %1. Nem sikerült menteni %1. Failed to delete rules file. (%1) Nem sikerült törölni a dinamika szabálylistát. (%1) Invalid command. (%1) Érvénytelen parancs. (%1) Could not remove active rules link. Nem tudtam eltávolítani az aktív szabályok hivatkozást. Active rules is not a link. Az aktív szabályok nem hivatkozás. Could not create active rules link. Nem sikerült létrehozni az aktív szabályok hivatkozást. Rules file, %1, does not exist. A(z) %1 szabályok fájl nem létezik. Incorrect arguments supplied. Érvénytelen értékmegadás. Unknown method called. Ismeretlen eljárás hívása. Unknown error Ismeretlen hiba Artist Előadó SimilarArtists Hasonló előadók AlbumArtist AlbumElőadó Composer Zeneszerző Comment Megjegyzés Album Lemez Title Cím Genre Műfaj Date Dátum File Include Befoglal Exclude Kizár (Exact) (Pontosan) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Aktuális borító CoverArt Archive CoverArt archívum Grouped Albums Csoportosított lemezek Table Táblázat Parse in Library view, and show in Folders view Only show in Folders view Do not list 1 Track Singular 1 Szám %1 Tracks Plural (N!=1) %1 Számok 1 Track (%1) Singular 1 Szám (%1) %1 Tracks (%2) Plural (N!=1) %1 Számok (%2) 1 Album Singular 1 Album %1 Albums Plural (N!=1) %1 Album 1 Artist Singular 1 Előadó %1 Artists Plural (N!=1) %1 Előadók %1 Streams Plural (N!=1) %1 Stream-ek %1 Entries Plural (N!=1) %1 Tételek 1 Rule Singular 1 Szabály %1 Rules Plural (N!=1) %1 Szabályok 1 Podcast Singular 1 Podcast %1 Podcasts Plural (N!=1) %1 Podcast 1 Episode Singular 1 Rész %1 Episodes Plural (N!=1) %1 Részek %1 Updates available Plural (N!=1) %1 számú frissítés érhető el Previous Track Előző szám Next Track Következő szám Play/Pause Lejátszás/Szünet Stop Állj Stop After Current Track A mostani szám után leáll Stop After Track A szám után leáll Increase Volume Hangerő növelése Decrease Volume Hangerő csökkentése Save As Mentés mint... Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Hozzáad prioritással Set Priority Prioritás beállítása Highest Priority (255) Legmagasabb prioritás (255) High Priority (200) Magas prioritás (200) Medium Priority (125) Közepes prioritás (125) Low Priority (50) Alacsony prioritás (50) Default Priority (0) Alap prioritás (0) Custom Priority... Tetszőleges prioritás... Add To Playlist Lejátszási listához ad Organize Files Fájlok rendezése Edit Track Information Száminformációk szerkesztése ReplayGain Lejátszási szint Copy Songs To Device Számok másolása az eszközre Delete Songs Dalok törlése Set Image Kép beállítása Remove Eltávolítás Find Keres Add To Play Queue Lejátszási listához hozzáad Parse error loading cache file, please check your songs tags. Other Továbbiak Default Alapbeállítás "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Egyedüli számok Personal Személyes Unknown Ismeretlen Various Artists Vegyes előadók Album artist Album előadói Performer Szereplő Track number Szám sorszáma Disc number Lemez sorszáma Year Év Orignal Year Length Időtartam <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> a <b>%2</b>-ön <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> <b>%2</b>-vel <b>%3</b>-on Invalid service Érvénytelen szolgáltatás Invalid method Érvénytelen eljárás Authentication failed Az azonosítás sikeretlen Invalid format Érvénytlen formátum Invalid parameters Érvénytelen paraméterek Invalid resource specified Érvénytlen forrásmeghatározás Operation failed Sikertelen művelet Invalid session key Érvénytelen munkamenet-kulcs Invalid API key Érvénytelen API-kulcs Service offline Szolgáltatás offline Last.fm is currently busy, please try again in a few minutes A last.fm pillanatnyilag foglalt, próbáld meg néhány perc múlva. Rate-limit exceeded A díjhatárt átlépted General Általános: Digitally Imported Digitally Imported Local and National Radio (ListenLive) Helyi és nemzeti rádió (ListenLive) &OK &Ok &Cancel Elvet &Yes Igen &No &Nem &Discard Visszavon &Save Ment &Apply &Alkalmaz &Close Bezár &Help Segítség &Overwrite Felülír &Reset &Reset &Continue Folytat &Delete Töröl &Stop Állj &Remove Eltávolít &Previous Előző &Next Következő Close Bezár Error Hiba Information Információ Warning Figyelem Question Kérdés %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Alap fastruktúra (nincs ikon) Simple Tree Egyszerű fa Detailed Tree Részletes fa List Lista Grid Rács (osztás) %n Track(s) %n Szám %n Tracks (%1) %n Szám (%1) %n Album(s) %n Album %n Artist(s) %n Előadó %n Stream(s) %n Stream %n Entry(s) %n Tétel %n Rule(s) %n Szabály %n Podcast(s) %n Podcast %n Episode(s) %n Rész %n Update(s) available %n frissítés érhető el RemoteDevicePropertiesDialog Device Properties Eszközjellemzők Connection Csatlakozás Music Library Zenekönyvtár Add Device Eszköz hozzáadása A remote device named '%1' already exists! Please choose a different name. A '%1' nevű távoli eszköz már létezik! Kérlek válassz másik nevet. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Ezek a beállítások csak akkor szerkeszthetők, amikor az eszköz nem csatlakozik. Type: Típus: Name: Név: Options Opciók Host: Elhelyező: Port: Port: User: Felhasználó: Domain: Kiszolgáló: Password: Jelszó: Share: Megosztás: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Az itt beírt jelszót <b>titkosítatlanul</b> tárolja a Cantata beállító fájljában. Ha a megosztás előtt akarod a jelszót bekéretni, akkor azt állítsd '-' -ra. Service name: Szolgáltatás neve: Folder: Könyvtár: Extra Options: Extra opciók: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. Az sshfs működéséből eredően jelszó beviteléhez egy megfelelő ssh-askpass alkalamzás (ksshaskpass, ssh-askpass-gnome, stb.) szükséges. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Ez a párbeszéd csak távoli (pl. Samba-n keresztül csatlakozott), vagy helyi csatolású eszközök, hozzáadásához és leválasztásához való. A szokáos, USB-n csatlakozott médialejátszókat a Cantata csatlakozáskor automatikusan megjeleníti. Samba Share Szamba-megosztás Samba Share (Auto-discover host and port) Szamba-megosztás (kiszolgáló és port automatikus felderítése) Secure Shell (sshfs) Biztonságos héj (sshfs) Locally Mounted Folder Helyileg csatolt könyvtár RemoteFsDevice Available Elérhető Not Available Nem elérhető Failed to resolve connection details for %1 Nem sikerült megállapítani a csatlakozási részleteket erre %1 Connecting... Csatlakozás... Password prompting does not work when cantata is started from the commandline. A jelszóbekérés nem működik, amikor a Cantata-t parancssorból indítják. No suitable ssh-askpass application installed! This is required for entering passwords. Nincs megfelelő ssh-jelszóbekérő alkalmazás telepítve. A jelszó beviteléhez szükséges. Mount point ("%1") is not empty! Csatolási pont ("%1") nem üres! "sshfs" is not installed! "sshfs" nincs telepítve! Disconnecting... Leválasztás... "fusermount" is not installed! "fusermount" nincs telepítve! Failed to connect to "%1" Nem sikerült csatlakozni ehhez "%1" Failed to disconnect from "%1" Nem sikerült leválasztani erről "%1" Updating tracks... számok frissítése... Not Connected Nincs csatlakozva Capacity Unknown Kapacitás ismeretlen %1 free %1 szabad RgDialog ReplayGain Lejátszási szint Show All Tracks Összes szám mutatása Show Untagged Tracks A címke nélküli számok mutatása Remove From List Listából eltávolít Artist Előadó Album Lemez Title Cím Album Gain Album hangereje Track Gain Szám hangereje Album Peak Album csúcs Track Peak Szám csúcs Scan Átnéz Update ReplayGain tags in tracks? A számokban a lejátszási színt címkéket frissíted? Update Tags Címkék frissítése Abort scanning of tracks? Számok átnézését felfüggeszted? Abort Megszakít Abort reading of existing tags? A beállított címkék beolvasását beszakítod? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Az <b>összes</b> számot átnézi?<br/><br/><i>Minden szám rendelkezik Lejátszási Szint jelölővel.</i> Do you wish to scan all tracks, or only tracks without existing tags? Átnézzem az összes számot, vagy csak azokat, amiknek nincs címkéjük? Untagged Tracks Címke nélküli számok All Tracks Minden szám Scanning tracks... Számok átnézése... Reading existing tags... Létező címkék beolvasása... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Hibás címkék?) Failed to update the tags of the following tracks: A következő számok címkéit nem sikerült frissíteni: Device has been removed! Az eszköz el lett távolítva! Device is not connected. Az eszköz nem csatlakozik. Device is busy? Az eszköz foglalt? %1 dB %1 dB Failed Sikertelen Original: %1 dB Eredeti: %1 dB Original: %1 Eredeti: %1 Remove the selected tracks from the list? A kiválasztott számokat eltávolítsam a listából? Remove Tracks Számok eltávolítása RulesPlaylists - Rating: %1..%2 - Besorolás: %1..%2 Album Artist Album előadója Artist Előadó Album Lemez Composer Zeneszerző Date Dátum Genre Műfaj Rating Besorolás File Age Random Véletlenszerűen %n Rule(s) %n Szabály , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 hiba: %2 ScrobblingLove %1: Loved Current Track %1: aktuális szám tetszett %1: Love Current Track %1: az aktuális szám tetszik ScrobblingSettings Scrobble using: Karcolás használata: Username: Felhasználónév: Password: Jelszó: Status: Státusz: Login Bejelentkezés Scrobble tracks Karcolás-számok Show 'Love' button Mutassa a 'Tetszik' gombot %1 (via MPD) scrobbler name (via MPD) %1 (MPD-n keresztül) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Amennyiben olyan zenefeljegyzést használsz, mint '(MPD-n keresztül)' (mint %1), akkor ennek már megnyitottnak ls futónak kell lennie. A Cantata csak ezen keresztül tudja 'kedvelni' a számokat és nem kapcsolható ki, vagy be a zenefeljegyzés. Authenticating... Azonosítás... Authenticated Azonosítva Not Authenticated Nincs azonosítva ScrobblingStatus %1: Scrobble Tracks %1: számok feljegyzése SearchModel # (Track Number) SearchPage Locate In Library Könyvtárban megkeres Artist: Előadó: Composer: Zeneszerző: Performer: Szereplő: Album: Album Title: Cím: Genre: Műfaj: Comment: Megjegyzés: Date: Dátum: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Módosítva: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Fájl: Any: Bármely: No tracks found. Számokat nem találtam. %n Tracks (%1) %n Szám (%1) SearchWidget Search... Keresés... Close Search Bar Keresősáv bezárása ServerSettings Collection: Gyűjtemény: Name: Név: Host: Elhelyező: Password: Jelszó: Music folder: Zenekönyvtár: Cover filename: Borító fájlneve: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Fájlnév (kiterjesztés nélkül) lementi a borítót aszerint.<br/>Ha üresen marad 'borítót' használja.<br/><br/><i>A jelenlegi számnál az %előadó% album előadóra kerül lecserélésre, és az %album% pedig az album nevére.</i></p> HTTP stream URL: HTTP hangfolyam URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. A 'HTTP adatfolyam URL' csak HTTP kimenetére beállított MPD esetén használatos, és ha szeretnéd a Cantata-t, hogy képes legyen az adatfolyam lejátszására. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. Ha a 'Zenekönyvtár' beállítását megváltoztatod, akkor a zenei adatbázist kézzel kell frissíteni. Ez 'Előadó'-, vagy 'Album'-nézetben a 'Adatbázisfrissítés' gomb lenyomásával érhető el. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? Milyen típusú válogatáshoz szeretnél csatlakozni? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> <i><b>Megjegyzés:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Gyűjteményt hozzáad Standard Szabványos Basic Alap Delete '%1'? Törlöd a(z) '%1'-t? Delete Törlés New Collection %1 Új gyűjtemény %1 Default Alapbeállítás ServiceStatusLabel Logged into %1 Bejelentkezve ide: %1 <b>NOT</b> logged into %1 <b>NEM VAGY</b> bejelentkezve a ide: %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Keresés: Shortcut for Selected Action Billentyűparancs a kiválasztott művelethez Default: Alapbeállítás: None Nincs Custom: Egyéni: SinglePageWidget Refresh Frissítés View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Hozzáad Edit Szerkesztés Remove Eltávolítás Are you sure you wish to remove the selected rules? This cannot be undone. Biztosan eltávolítod a kiválasztott szabályokat? Ezt nem lehet visszavonni. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. A dalfájl nem érhető el! Kérem ellenőrizd a "Zenekönyvtár" beállítását és az MPD "zenekönyvtárának" beállítását. Cannot access song files! Please check that the device is still attached. A dalfájlokat nem lehet elérni!! Kérem ellenőrizd, hogy az eszköz csatlakoztatva van-e még! SongView Lyrics Dalszövegek Information Információ Metadata Metaadat Scroll Lyrics Dalszöveg görgetése Refresh Lyrics Dalszöveg frissítése Edit Lyrics Dalszöveg szerkesztése Delete Lyrics File Dalszöveg-fájl törlése Refresh Track Information Száminformációk frissítése Cancel Kilépés Track Szám Reload lyrics? Reload from disk, or delete disk copy and download? Dalszöveg újratöltése? Lemezről töltsem újra, vagy töröljem a lemezről és töltsem le? Reload Újratöltés Reload From Disk Újratöltés lemezről Download Letöltés Current playing song has changed, still perform search? A dal, amit játszik megváltozott, továbbra is keresel? Song Changed A dal megváltozott Perform Search Keresés Delete lyrics file? Törölöd a dalszöveg fájlt. Delete File Fájl törlése Artist Előadó Album artist Album előadói Composer Zeneszerző Lyricist Dalszövegírók Conductor Rendező Remixer Újrakeverő Album Lemez Subtitle Alcím Track number Szám sorszáma Disc number Lemez sorszáma Genre Műfaj Date Dátum Original date Eredeti dátum Comment Megjegyzés Copyright Copyright Label Címke Catalogue number Katalógusszám Title sort Cím szerint rendez Artist sort Előadó szerint rendez Album artist sort Album előadó szerint rendez Album sort Album szerint rendez Encoded by Kódolta Encoder Kódoló Mood Mood Media Média Bitrate Bitráta Sample rate Mintavételi arány Channels Csatornák Tagging time Időjelzés Performer (%1) Előadó (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bit Performer Szereplő Year Év Filename Fájlnév Fetching lyrics via %1 Dalszöveg leszedése innen %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Számol... Total space used: %1 Teljes felhasznált terület: %1 SqlLibraryModel %n Artist(s) %n Előadó %n Album(s) %n Album %n Tracks (%1) %n Szám (%1) Cue Sheet Kulcslap Playlist Lejátszási lista StoredPlaylistsPage Rename Átnevezés Remove Duplicates Másolatok eltávolítása Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Biztos el akarod távolítani a kiválasztott lejátszási listát? Ez nem vonható vissza. Remove Playlists Lejátszási lista eltávolítása Playlist Name Lejátszási lista neve Enter a name for the playlist: Írj be egy nevet a lejátszási listának: A playlist named '%1' already exists! Overwrite? A(z) '%1' nvű lejátszási lista már létezik! Felülírod? Overwrite Playlist Lejátszási lista felülírása Rename Playlist Lejátszási lista eltávolítása Enter new name for playlist: Adj új nevet a lejátszási listának: Cannot add songs from '%1' to '%2' A dalok innen '%1'nem adhatók ide '%2' StreamDialog Add stream to favourites Name: Név: URL: URL: Add Stream Stream hozzáadása Edit Stream Stream szerkesztése <i><b>ERROR:</b> Invalid protocol</i> <i><b>Hiba:</b> érvénytelen protokoll</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Telepítve Update available Frissítés elérhető Check the providers you wish to install/update. Ellenőrizd a telepítés, frissítés szolgáltatóit. Install/Update Stream Providers Adatfolyam szolgáltatóinak telepítése, frissítése Downloading list... Lista letöltése... Failed to download list of stream providers! Nem tudtam letölteni az adatfolyam szolgáltatók listáját! Installing/updating %1 Telepítés, frissítés %1 Failed to install '%1' A(z) '%1' telepítése sikertelen Failed to download '%1' A(z) '%1' letöltése sikertelen Install/update the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat telepíti, vagy frissíti? Install the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat telepíti? Update the selected stream providers? A kiválasztott adatfolyam-szolgáltatókat frissíti? Install/Update Telepít, frissít Abort installation/update? Telepítés, frissítés megszakítása? Abort Megszakít %n Update(s) available %n frissítés érhető el Downloading %1 Letöltés %1 Update all updateable providers Az összes frissíthető szolgáltató frissítése StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble??? Stream Search Search for radio streams Enter string to search Not Loaded Nincs betöltve Loading... Betöltés... %n Entry(s) %n Tétel StreamSearchPage Added '%1'' to favorites %1 hozzáadva a kedvencekhez StreamsBrowsePage Import Streams Into Favorites Stream importálása kedvencek közé Export Favorite Streams Kedvenc stream-ek exportálása Add New Stream To Favorites Új stream hozzáadása a kedvencekhez Edit Szerkesztés Seatch For Streams Configure Beállítás Digitally Imported Service name Digitally Imported Import Streams Stream-ek importálása XML Streams (*.xml *.xml.gz *.cantata) XML stream-ek (*.xml *.xml.gz *.cantata) Export Streams Stream-ek exportálása XML Streams (*.xml.gz) XML adatfolyamok (*.xml.gz) Failed to create '%1'! A(z) '%1'-t nem sikerült létrehozni! Stream '%1' already exists! A(z) '%1' adatfolyam már létezik! A stream named '%1' already exists! A(z) '%1' nevű adatfolyam már létezik! Bookmark added Könyvjelző hozzáadva Already bookmarked Már van könyvjelzője Already in favorites Már szerepel a kedvencek között Reload '%1' streams? A(z) '%1' adatfolyamot újratölti? Are you sure you wish to remove bookmark to '%1'? Biztosan eltávolítaná a(z) '%1' könyvjelzőjét? Are you sure you wish to remove all '%1' bookmarks? Biztosan eltávolítaná az összes '%1' könyvjelzőt? Are you sure you wish to remove the %1 selected streams? Biztosan eltávolítanád a kiválasztott %1 stream-eket? Are you sure you wish to remove '%1'? Biztosan eltávolítaná a(z) '%1'-t? Added '%1'' to favorites %1 hozzáadva a kedvencekhez StreamsModel Bookmarks Könyvjelzők TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble??? Favorites Kedvencek Bookmark Category Könyvjelző-kategóriák Add Stream To Favorites Hangfolyam hozzáadása a kedvencekhez Configure Digitally Imported Reload Újratöltés Streams Stream-ek Radio stations Not Loaded Nincs betöltve Loading... Betöltés... %n Entry(s) %n Tétel StreamsSettings Use the checkboxes below to configure the list of active providers. Az aktív szolgáltatók beállítására használd az lenti kijelölő gombokat. Built-in categories are shown in italic, and these cannot be removed. Az előre meghatározott kategóriák dőlttel szedettek és nem távolíthatóak el. Configure Streams Hangfolyam beállítása From File... Fájlból... Download... Letöltve... Configure Provider Szolgáltató beállítása Install Telepítés Remove Eltávolítás Install Streams Stream-ek telelpítése Cantata Streams (*.streams) Cantata stream-ek (*.streams) A category named '%1' already exists! Overwrite? A(z) '%1' nevű kategória már létezik! Felülírja? Failed top open package file. A csomagfájl megnyitása sikertelen. Invalid file format! Érvénytelen fájlformátum! Failed to create stream category folder! Nem sikerült létrehozni a steram-kategória könyvtárát!<br/>%1 Failed to save stream list! Nem sikerült menteni a stream-ek litáját! Are you sure you wish to remove '%1'? Biztosan eltávolítaná a(z) '%1'-t? Failed to remove streams folder! Nem sikerült eltávolítani a stream-ek könyvtárát SyncCollectionWidget Search Keresés Check Items Tételek kiválasztása Uncheck Items Tételek kiválasztásának törlése SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Szinkronizálás Device and library are in sync. Az eszköz és a könyvtár szinkronban van. Loading all songs from library, please wait...%1%... Local Music Library Properties Helyi zenekönyvtár tulajdonságok Device has been removed! Az eszköz el lett távolítva! Device has been changed? Az eszköz megváltozott? Device is busy? Az eszköz foglalt? TableView Stretch Columns To Fit Window Oszlop igazítás az ablakhoz illeszkedéshez Left Bal Center Right Jobb Alignment TagEditor Track: Szám: Title: Cím: Artist: Előadó: Album artist: Album előadója: Composer: Zeneszerző: Album: Album Track number: Szám sorszáma: Disc number: Lemez száma: Genre: Műfaj: Year: Év: Rating: Besorolás (minősítés): <i>(Various)</i> <i>(Vegyes)</i> Comment: Megjegyzés: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. A besorolást (minősítést) külső adatbázis tárolja és <b>nem</b> dal fájlja. Tags Címkék Tools Eszközök Apply "Various Artists" Workaround Alkalmazza a "Vegyes előadók" munkakörnyezetet Revert "Various Artists" Workaround Visszaállítja a "Vegyes előadók" munkakörnyezetet Set 'Album Artist' from 'Artist' 'Album Előadó' beállítása 'Előadó'-ból Capitalize Váltás nagybetűre Adjust Track Numbers Számok sorszámának beállítása Read Ratings from File A besorolás olvasása fájlból Write Ratings to File Besorolás kiírása fájlba All tracks Minden szám Apply "Various Artists" workaround to <b>all</b> tracks? "Válogatott előadók" munkakörnyezet alkalmazása az <b>összes</b> számra? Apply "Various Artists" workaround? Alkalmazza a "Vegyes előadók" munkakörnyezetet? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Ez a "Album előadót" és "Előadót" "Vegyes előadók"-ra állítja, és a "Címet" "Előadó Szám - Szám címe"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Visszaállítod a "Vegyes előadók" munkakörnyezetet az <b>összes</b> számra? Revert "Various Artists" workaround Visszaállítja a "Vegyes előadók" munkakörnyezetet <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Ahol az "Album előadó" megegyezik az "Előadó és a Címmel, illetve "Cím" "SzámElőadó -SzámCím" formátumú, az Előadót a "Cím"-ből veszi és a "Cím" maga egyszerűen a cím lesz, pl. <br/><br/>ha a "Cím" "Wibble - Wobble", akkor az "Előadó" egyszerűen "Wibble", a "Cím" pedig "Wobble" lesz.</i> Revert Visszaállít Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 'Album Előadó'-t állítasz az 'Előadó' helyett (ha az 'Album Előadó' üres) <b>az összes</b> számnál? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Beállítod 'Album előadót' az 'Előadó' helyett (ha az 'Album Előadó' üres)? Album Artist from Artist Album Előadó-ról Előadóra Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Nagybetűsítse a (pl. 'Cím', 'Előadó, 'Előadó' stb.) első betűjét az <b>összes</b> számnak? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Nagybetűsítse a szövegmező (pl. 'Cím', 'Előadó, 'Előadó' stb.) első betűjét? Adjust the value of each track number by: Az összes szám sorszámának módosítása ennyivel: Adjust track number by: A szám sorszámának módosítás ennyivel: Read ratings for all tracks from the music files? Az összes szám besorolásának kiolvasása a zenei fájlból? Read rating from music file? A besorolás kiolvasása a zenei fájlból? Ratings Besorolások Read Ratings Besorolások olvasása Read Rating Besorolások olvasása Read, and updated, ratings from the following tracks: A következő fájlok besorolás kiolvasva és frissítve: Not all Song ratings have been read from MPD! Nem minden dal besorolását olvasta ki az MPD-ről! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. A dalok besorolása nem a dalfájlokban, hanem az MPD-hez 'ragasztott' adatbázisban vannak. Hogy ezek az adott fájlban kerüljenek, a Cantata-nak előbb ki kell olvasni azokat.az MPD-ből. Song rating has not been read from MPD! A dalbesorolást az MPD-ből nem olvasta be! Write ratings for all tracks to the music files? Az összes szám besorolását az zenei fájlokba írja? Write rating to music file? A besorolást a zenefájlba írja? Write Ratings A besorolások kiírása Write Rating A besorolások kiírása Failed to write ratings of the following tracks: A következő számok besorolását nem tudtam kiírni? Failed to write rating to music file! Nem tudtam kiírni a besorolást a zenefájlba! All tracks [modified] Minden szám [módosítva] %1 [modified] %1 [módosítva] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Hibás címkék?) Failed to update the tags of the following tracks: A következő számok címkéit nem sikerült frissíteni: Would you also like to rename your song files, so as to match your tags? Átnevezed a dalok fájljait is, hogy egyezzenek a címkéiddel? Rename Files Fájlok átnevezése Rename Átnevezés Device has been removed! Az eszköz el lett távolítva! Device is not connected. Az eszköz nem csatlakozik. Device is busy? Az eszköz foglalt? TagSpinBox (Various) (Vegyes) ThinSplitter Reset Spacing Szüneteket alaphelyzetbe TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Elérhető: Selected: Kiválasztva: TrackOrganiser Filenames Fájlnevek Filename scheme: Fájlnév-séma: VFAT safe VFAT tároló Use only ASCII characters Kizárólag ASCII-es karakterek használata Replace spaces with underscores Szóközök cseréje aláhúzásra Append 'The' to artist names A 'The' hozzáfűzése az előadói nevekhez. Original Name Eredeti név New Name Új név Ratings will be lost if a file is renamed. A besorolások elvesznek, ha a fájlt átnevezed. Organize Files Fájlok rendezése Rename Átnevezés Remove From List Listából eltávolít Abort renaming of files? Fájlátnevezés megszakítása? Abort Megszakít Source file does not exist! A forrás nem létezik! Skip Kihagy Auto Skip Automatikusan kihagy Destination file already exists! A célfájl már létezik! Failed to create destination folder! Nem sikerült létrehozni a célkönyvtárat! Failed to rename '%1' to '%2' '%1'-t nem sikerült átnevezni '%2'-re Remove the selected tracks from the list? A kiválasztott számokat eltávolítsam a listából? Remove Tracks Számok eltávolítása Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. A dalok besorolása nem a dalfájlokban, hanem az MPD-hez 'ragasztott' adatbázisban van tárolva. Ha a fájlt (vagy a könyvtárat, amiben van) átnevezed, akkor a dalra vonatkozó besorolás elveszik. Device has been removed! Az eszköz el lett távolítva! Device is not connected. Az eszköz nem csatlakozik. Device is busy? Az eszköz foglalt? TrayItem Cantata Cantata Now playing Most megy UltimateLyricsProvider (Polish Translations) (Lengyel fordítás) (Portuguese Translations) (Portugál fordítás) UmsDevice Not Scanned Nincs átnézve Not Connected Nincs csatlakozva %1 free %1 szabad ValueSlider (recommended) (ajánlott) View Cancel Kilépés VolumeSlider Mute Némít Unmute Némítás kikapcsolása Volume %1% (Muted) Hangerő %1% (Némítva) Volume %1% Hangerő %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | előadó|együttes|énekes|vokál|zenész album|score|soundtrack Search pattern for an album, separated by | album|kotta|hangsáv WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Válaszd ki a Wikipédiában, az előadó, vagy album információinak keresésekor használni kívánt nyelvet Reload Újratöltés cantata-2.2.0/translations/cantata_it.ts000066400000000000000000014215371316350454000203440ustar00rootroot00000000000000 ActionDialog Calculating size of files to be copied, please wait... Calcolando la dimensione dei file da coipare, attendere... Copy songs from: Copia brani da: Configure Configurazione (Needs configuring) (Necessaria configurazione) Copy songs to: Copia brani su: Destination format: Formato di destinazione: Overwrite songs Sovrascrivi brani To copy: Da copiare: <b>INVALID</b> <b>NON VALIDO</b> <i>(When different)</i> <i>(Quando diverso)</i> Artists:%1, Albums:%2, Songs:%3 Artisti:%1, Album:%2, Brani:%3 %1 free %1 liberi Local Music Library Libreria Musicale Locale Audio CD CD Audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Non c'è abbastanza spazio disponibile sul dispositivo di destinazione. I brani selezionati occupano %1, ma si sono solamente %2 disponibili. I brani dovranno essere trascodificati ad una dimensione di file inferiore per poter venire copiati. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Non c'è abbastanza spazio disponibile sulla destinazione. I brani selezionati occupano %1, ma si sono solamente %2 disponibili. Copy Songs To Library Copia Brani nella Libreria Copy Songs To Device Copia Brani nel Dispositivo Copy Songs Copia Brani Delete Songs Cancella Brani You have not configured the destination device. Continue with the default settings? Il dispositivo di destinazione non è configurato. Continuare con le impostazioni predefinite? Not Configured Non Configurato Use Defaults Usa Predefinite You have not configured the source device. Continue with the default settings? Il dispositivo sorgente non è configurato. Continuare con le impostazioni predefinite? Are you sure you wish to stop? Sicuro di voler interrompere? Stop Interrompi Device has been removed! Il dispositivo è stato rimosso! Device is not connected! Il dispositivo non è connesso! Device is busy? Il dispositivo è occupato? Device has been changed? È stato cambiato il dispositivo? Clearing unused folders Pulizia cartelle inutilizzate Calculate ReplayGain for ripped tracks? Calcolare il ReplayGain per le tracce estratte? ReplayGain ReplayGain Calculate Calcolo The destination filename already exists! I nome del file di destinazione esiste già! Song already exists! Brano già esistente! Song does not exist! Brano inesistente! Failed to create destination folder!<br/>Please check you have sufficient permissions. Impossibile creare la cartella di destinazione!<br/>Prego verificare di avere sufficienti permessi. Source file no longer exists? Il file sorgente non esiste più? Failed to copy. Copia fallita. Failed to delete. Cancellazione fallita. Not connected to device. Disconnesso dal dispositivo. Selected codec is not available. Il codec selesionato non è disponibile. Transcoding failed. Trascodifica fallita. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Impossibile creare il file temporaneo.<br/>(Richiesto per la trascodifica verso i dispositivi MTP.) Failed to read source file. Impossibile leggere il file sorgente. Failed to write to destination file. Impossibile scrivere sul file di destinazione. No space left on device. Spazio esaurito sul dispositivo. Failed to update metadata. Impossibile aggiornare i metadati. Failed to download track. Impossibile scaricare la traccia. Failed to lock device. Impossibile bloccare il dispositivo. Local Music Library Properties Proprieta della Libreria Musicale Locale Error Errore Skip Salta Auto Skip Salta automaticamente Retry Riprova Artist: Artista: Album: Album: Track: Traccia: Source file: File sorgente: Destination file: File di destinazione: File: File: Saving cache Salvo la cache AlbumDetails Album Details Dettagli dell Album Artist: Artista: Composer: Compositore: Title: Titolo: Genre: Genere: Year: Anno: Disc: Disco: Single artist Artista singolo Tracks Tracce Track Traccia Artist Artista Title Titolo AlbumDetailsDialog Audio CD CD Audio Apply "Various Artists" Workaround Applica soluzione per "Artisti Vari" Revert "Various Artists" Workaround Rimuovi soluzione per "Artisti Vari" Capitalize Aggiusta Maiuscole Adjust Track Numbers Aggiusta Numerazione Tracce Tools Strumenti Apply "Various Artists" workaround? Applica soluzione per "Artisti Vari"? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Questo imposterà 'Artista album' e 'Artista' ad "Artisti Vari", e 'Titolo' a "ArtistaTraccia - TitoloTraccia" Revert "Various Artists" workaround? Ripristina dalla soluzione per "Artisti Vari"? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Quando 'Artista album' è uguale a 'Artista' e il 'Titolo' è nel formato "ArtistaTraccia - TitoloTraccia", 'Artista' verrà preso da 'Titolo' e 'Titolo' verrà impostato al solo titolo. es. Se 'Titolo' è "Wibble - Wobble", allora 'Artista' verrà impostato a "Wibble" e 'Titolo' verrà impostato a "Wobble" Revert Ripristina Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Rendi maiuscola le prime lettere di 'Titolo', 'Artista', 'Artista album' e 'Album'? Adjust track number by: Aggiusta numerazione traccia per: AlbumView Refresh Album Information Aggiorna Informazioni Album Album Album Tracks Tracce ArtistView Refresh Artist Information Aggiorna Informazioni Artista Artist Artista Albums Album Web Links Collegamenti in Rete Similar Artists Artisti Simili AudioCdDevice Reading disc Lettura disco %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) AudioCdSettings Album and Track Information Retrieval Recupero Informazioni Album e Traccia Initially look up via: Ricerca iniziale con: CDDB Host: Server CDDB: CDDB Port: Porta CDDB: Lookup information as soon as CD is inserted Ricerca informazioni appena il CD viene inserito Audio Extraction Estrazione Audio Full paranoia mode (best quality) Modalità paranoia totale (miglior qualità) Never skip on read error Non saltare mai in caso di errore CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet File Cue Sheet Playlist Scaletta CacheItem Deleting... Eliminazione... Calculating... Calcolo... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata memorizza in cache diverse informazioni (coperine, testi, ecc). Qua sotto c'è un riassuto dell'attuale utilizzo di cache di Cantata. Covers Copertine Scaled Covers Copertine Ridimensionate Backdrops Sfondi Lyrics Testi Artist Information Informazioni Artista Album Information Informazioni Album Track Information Informazioni Traccia Stream Listings Liste Flussi Podcast Directories Cartelle Podcast Wikipedia Languages Lingue Wikipedia Scrobble Tracks Tracce Scrobble Delete All Cancella Tutto Delete all '%1' items? Cancellare tutti i '%1' oggetti? Delete Cache Items Cancella Oggetti in Cache Delete items from all selected categories? Cancellare gli oggetti dalle categorie selezionate? CacheTree Name Nome Item Count Conteggio Oggetti Space Used Spazio Utilizzato CddbInterface Data Track Traccia Dati Failed to open CD device Impossibile aprire il dispositivo CD Track %1 Traccia %1 Failed to create CDDB connection Impossibile creare la connessione con CDDB Failed to contact CDDB server, please check CDDB and network settings Impossibile contattare il server CDDB, prego verificare le impostazioni di CDDB e della rete No matches found in CDDB Nessuna corrispondenza trovata nel CDDB CDDB error: %1 Errore CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Trovate corrispondenze multiple. Prego scegliere quella rilevante dalla lista: Artist Artista Title Titolo Disc Selection Selezione Disco %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 - Disco %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Sorgenti Testi Wikipedia Languages Lingue Wikipedia Other Altro ContextWidget &Artist &Artista Al&bum Al&bum &Track &Traccia CoverDialog Search Cerca Add a local file Aggiungi un file locale Configure Configura This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. Questo può essere usato per cambiare solamente il file utilizzato per la copertina, non modificherà alcuna copertina incorporata nei file musicali. CoverArt Archive Archivio Copertine An image already exists for this artist, and the file is not writeable. Esiste già un immagine per questo artista, ed il file non è scrivibile. A cover already exists for this album, and the file is not writeable. Esiste già una copertina per questo album, ed il file non è scrivibile. '%1' Artist Image '%1' Immagine Artista '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Copertina Album Failed to set cover! Could not download to temporary file! Impossibile impostare la copertina! Non è stato possibile scaricare su un file temporaneo! Failed to download image! Impossibile scaricare l'immagine! Load Local Cover Carica Copertina in Locale Images (*.png *.jpg) Immagini (*.png *.jpg) File is already in list! Il file è già nella lista! Failed to read image! Impossibile leggere l'immagine! Display Mostra Remove Rimuovi Failed to set cover! Could not make copy! Impossibile impostare la copertina! Non è stato possibile fare la copia! Failed to set cover! Could not backup original! Impossibile impostare la copertina! Non è stato possibile fare un backup dell'originale! Failed to set cover! Could not copy file to '%1'! Impossibile impostare la copertina! Non è stato possibile copiare il file su '%1'! Searching... Ricerca... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> tr><td align="right"><b>Compositore:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Esecutore:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artista:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Anno:</b></td><td>%3</td></tr> CoverPreview Image Immagine Downloading... Scaricamento... Image (%1 x %2 %3%) Image (width x height zoom%) Immagine (%1 x %2 %3%) CustomActionDialog Name: Nome: Command: Comando: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Nella riga di comando soprastante, %f verrà sostituito dalla lista dei file e %d dalla lista delle cartelle. Se nessuno dei due verrà fornito, la lista dei file verrà accodata al comando. Add New Command Aggiungi Nuovo Comando Edit Command Modifica Comando CustomActions Custom Actions Azioni Personalizzate CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Per far chiamare dei comandi esterni a Cantata (es. per modificare i tag con un'altra applicazione), aggiungere una voce per il comando sottostante. Quando viene definito almeno un comando, una voce 'Azioni Personalizzate' verrà aggiunta ai menu contestuali nelle viste Libreria, Cartelle e Scalette. Add Aggiungi Edit Modifica Remove Rimuovi Name Nome Command Comando Remove the selected commands? Rimuovere i comandi selezionati? Device Updating (%1)... Aggiornamento (%1)... Updating (%1%)... Aggiornamento (%1%)... DevicePropertiesDialog Device Properties Proprietà del Dispositivo DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Queste impostazioni sono valide, e modificabili, quando il dispositivo è connesso. Name: Nome: Music folder: Cartella musica: Copy album covers as: Copia copertine album come: Maximum cover size: Dimensione massima copertine: Default volume: Volume predefinito: 'Various Artists' workaround Soluzione per 'Artisti Vari' Automatically scan music when attached Scansiona automaticamente quando collegato Use cache Usa cache Filenames Nomi file Filename scheme: Schema nomi file: VFAT safe Adatto a VFAT Use only ASCII characters Usa solo caratteri ASCII Replace spaces with underscores Sostituisci gli spazi con underscore Append 'The' to artist names Accoda il 'The' ai nomi degli artisti If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Se il nome di un artista inizia con 'The', allora postponilo nel nome della cartella. es. 'The Beatles' diventa 'Beatles, The' Transcoding Transcodifica Only transcode if source file is of a different format Transcodifica solo se il file sorgente è in un formato diverso Only transcode if source is FLAC/WAV Transcodifica solo se il file sorgente è FLAC/WAV Don't copy covers Non copiare le copertine Embed cover within each file Incorpora la copertina in ogni file No maximum size Nessun limite di dimensione 400 pixels 400 pixel 300 pixels 300 pixel 200 pixels 200 pixel 100 pixels 100 pixel <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Quando vengono copiate le tracce su un dispositivo e 'Artista Album' è impostato ad 'Artisti Vari', Cantata imposterà il tag 'Artista' di tutte le tracce ad 'Artisti Vari' ed il tag 'Titolo' a 'ArtistaTraccia - TitoloTraccia'.<hr/> Quando vengono copiate le tracce da un dispositivo, Cantata controlla se 'ArtistaAlbum' e 'Artista' sono entrambi impostati ad 'Artisti Vari'. In caso positivo, cercherà di estrarre il vero nome dell'artista dal tag 'Titolo' e rimuoverà il nome artista dal tag 'Titolo'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Abilitando questo, Cantata creerà una cache della libreria musicale del dispositivo. Questo permetterà di velocizzare le scansioni alla libreria consecutive (dato che verrà usato il file cache invece di dover leggere i tag di ogni file.)<hr/><b>NOTA:</b> Se utilizzerai un'altra applicazione per aggiornare la libreria del dispositivo, la cache non verrà aggiornata. Per correggere questo, basta cliccare sull'icona 'aggiorna' nella lista dei dispositivi. Questo farà sì che la cache venga rimossa ed il contenuto del dispositivo scansionato nuovamente.</p> Do not transcode Non fare transcodifica Encoder Codificatore Transcode to %1 Transcodifica su %1 %1 (%2 free) name (size free) %1 (%2 liberi) DevicesModel Configure Device Configura Dispositivo Refresh Device Aggiorna Dispositivo Connect Device Connetti Dispositivo Disconnect Device Disconnetti Dispositivo Edit CD Details Modifica Dettagli CD Not Connected Non Connesso No Devices Attached Nessun Dispositivo Collegato DevicesPage Copy To Library Copia nella Libreria Synchronise Sincronizza Forget Device Dimentica Dispositivo Add Device Aggiungi Dispositivo Lookup album and track details? Cercare dettagli albim e tracca? Refresh Aggiorna Via CDDB Tramite CDDB Via MusicBrainz Tramite MusicBrainz Which type of refresh do you wish to perform? Che tipo di aggiornamento eseguire? Partial - Only new songs are scanned (quick) Parziale - Verranno scansionati solo i nuovi brani (veloce) Full - All songs are rescanned (slow) Completo - Tutti i brani verranno scansionati di nuovo (lento) Partial Parziale Full Completo Are you sure you wish to delete the selected songs? This cannot be undone. Sicuro di voler cancellare i brani selezionati? Non sarà possibile tornare indietro. Delete Songs Cancella Brani Are you sure you wish to forget '%1'? Sicuro di voler dimenticare '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Sicuro di voler espellere il CD Audio '%1 - %2'? Eject Espelli Are you sure you wish to disconnect '%1'? Sicuro di voler disconnettere '%1'? Disconnect Disconnetti Please close other dialogs first. Prego chiudere le altre finestre di dialogo, prima. DigitallyImported Not logged in Non registrato Logged in Registrato Unknown error Errore sconosciuto No subscriptions Nessuna sottoscrizione You do not have an active subscription Non possieti una sottoscrizione attiva Logged in (expiry:%1) Regostrato (scadenza: %1) Session expired Sessione scaduta DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Senza un account è possibile ascoltare gratuitamente, ma i membri Premium possono accedere a flussi di più elevata qualità senza le pubblicità. Visita <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> per passare ad un account premium. Premium Account Account Premium Username: Utente: Password: Password: Stream type: Tipo di flusso: Status: Stato: Login Accesso Session expiry: Scadenza sessione: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Queste impostazioni si applicano a Digitally Imported, JazzRadio.com, RockRadio.com, e Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Inserendo i dettagli dell'account apparirà una voce di stato 'DI' sotto la lista dei flussi. Questa indicherà se si è connessi o no. Digitally Imported Settings Impostazioni Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated Non Autenticato Authenticating... Autenticazione... Authenticated Autenticato Logout Uscita DockMenu Play Riproduci Pause Pausa DynamicPlaylists Start Dynamic Playlist Avvia Scaletta Dinamica Stop Dynamic Mode Ferma Modalità Dinamica Dynamic Playlists Scalette Dinamiche Dynamically generated playlists Scalette generate dinamicamente You need to install "perl" on your system in order for Cantata's dynamic mode to function. Per l'utilizzo della modalità dinamica, è necessatio installare "perl" sul sistema. Failed to locate rules file - %1 Impossibile trovare il file di regole - %1 Failed to remove previous rules file - %1 Impossibile rimuovere il precedente file di regole - %1 Failed to install rules file - %1 -> %2 Impossibile installare il file di regole - %1 -> %2 Dynamizer has been terminated. Dynamizer è stato terminato. Awaiting response for previous command. (%1) In attesa di risposta dal comando precedente. (%1) Saving rule Salvataggio regola Deleting rule Cancellazione regola Failed to save %1. (%2) Impossibile salvare %1. (%2) Failed to delete rules file. (%1) Impossibile cancellare il file regole. (%1) Failed to control dynamizer state. (%1) Impossibile controllare lo stato del dynamizer. (%1) Failed to set the current dynamic rules. (%1) Impossibile impostare le regole dinamiche selezionate. (%1) DynamicPlaylistsPage Add Aggiungi Edit Modifica Remove Rimuovi Remote dynamizer is not running. Il dynamizer remoto non è in esecuzione. Are you sure you wish to remove the selected rules? This cannot be undone. Sicuro di voler cancellare le regole selezionate? Non sarà possibile tornare indietro. Remove Dynamic Rules Rimuovi Regole Dinamiche FileSettings Save downloaded covers, artist, and composer images, in music folder Salva le immagini scaricate di copertine, artisti e compositori nella cartella musica Save downloaded lyrics in music folder Salva i testi scaricati nella cartella musica Save downloaded backdrops in music folder Salva gli sfondi scaricati nella cartella musica If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Se hai scelto che Cantata memorizzi copertine, testi o sfondi all'interno della cartella musica e non hai permessi di accesso in essa, Cantata salverà i file nella tua cartella cache personale. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata può salvare le immagini di sfondi, artisti e compositore all'interno della gerarchia della cartella musica solo se questa è profonda due livelli. es. 'Artista/Album/Brani'. FilenameSchemeDialog Example: Esempio: About filename schemes Informazioni sugli schemi per i nomi file The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> L'artista dell'album. Nella maggior parte degli album, è uguale a <i>Artista Traccia.</i> Per le raccolte, spesso è <i>Artisti Vari.</i> Album Artist Artista Album The name of the album. Il nome dell'album. Album Title Titolo Album The composer. Il compositore. Composer Compositore The artist of each track. L'artista di ogni traccia. Track Artist Artista Traccia The track title (without <i>Track Artist</i>). Il titolo della traccia (senza <i>Artista Traccia</i>). Track Title Titolo Traccia The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Il titolo della traccia (con <i>Artista Traccia</i>, se diverso da <i>Artista Album</i>). Track Title (+Artist) Titolo Traccia (+Artista) The track number. Il numero di traccia. Track # Traccia # The album number of a multi-album album. Often compilations consist of several albums. Il numero dell'album di un album multiplo. Spesso, le raccolte consistono in diversi album. CD # CD # The year of the album's release. L'anno di pubblicazione dell'album. Year Anno The genre of the album. Il genere dell'album. Genre Genere Filename Scheme Schema nomi file Various Artists Example album artist Artisti Vari Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Le seguenti variabili verranno sostituire con i loro significati corripondenti per ogni nome traccia. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Variabile</em></th><th><em>Pulsante</em></th><th><em>Descrizione</em></th></tr> FolderPage Open In File Manager Apri nel Gestore File Are you sure you wish to delete the selected songs? This cannot be undone. Sicuro di voler cancellare i brani selezionati? Non sarà possibile tornare indietro. Delete Songs Cancella Brani FsDevice Updating... Aggiornamento... Reading cache Lettura cache Saving cache Salvataggio cache %1 %2% Message percent %1 %2% GenreCombo Filter On Genre FIltra sul Genere All Genres Tutti i Generi GroupedViewDelegate Audio CD CD Audio Streams Flussi %n Track(s) %n Traccia %n Tracce InitialSettingsWizard Cantata First Run Primo avvio di Cantata Welcome to Cantata Benvenuto a Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata è un client per Music Player Daemon (MPD) ricco di funzioni e semplice da usare. MPD è un'aplicazione server potente e flessibile per la riproduzione di musica.</p><p>Per maggiori informazioni su MPD, fare riferimento al sito di MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Questa guida ti condurrà attraverso l'impostazione base necessaria a Cantata per funzionare correttamente.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Benvenuto a Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>Cantata è un client per Music Player Daemon (MPD) ricco di funzioni e semplice da usare. MPD è un'aplicazione server potente e flessibile per la riproduzione di musica.MPD può venire eseguito da sistema oppure su base utente. <br/><br/>Prego selezionare come vuoi che Cantata si colleghi inizialmente (o avvi) MPD:</p> Standard multi-user/server setup Impostazione standard multi-utente/server <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>Scegli quest'opzione se la tua collezione musica è condivisa tra più utenti, se l'istanza MPD viene eseguita su un'altra macchina, se hai già un'impostazione personale di MPD, oppure se vuoi abilitare l'acceso da altri client (es. MPDroid). Se selezionerai questa opzione, Cantata non potrà controllare l'avvio e lo spegnimento del server MPD. Dovrai quindi assicurarti che MPD sia già configurato ed in esecuzione.</i> Basic single user setup Impostazione base per utente singolo <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>Scegli quest'opzione se la tua collezione musicale non è condivisa con altri e vuoi che Cantata configuri e controlli l'istanza di MPD. Questa configurazione sarà esclusiva per Cantata e <b>non</b> sarà accessibile da altri client MPD (es. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Se desideri una configurazione di MPD avanzata (es. uscite audio multiple, supporto completo DSD, ecc) allora <b>dovrai</b> scegliere 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Per maggiori informazioni su MPD, fare riferimento al sito di MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>Questa guida ti condurrà attraverso l'impostazione base necessaria a Cantata per funzionare correttamente. Connection details Dettagli connessione The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Le impostazioni seguenti sono le impostazioni base richiesta da Cantata. Prego inserire i dettagli rilevanti e premere il pulsante 'Connect' per provare la connessione. Host: Host: Password: Password: Music folder: Cartella musica: Connect Connetti The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. L'impostazione 'Cartella musica' viene usata per cercare copertine, testi, ecc. Se la tua istanza MPD è su un host remoto, puoi impostare questa voce ad un URL HTTP. Music folder Cartella musica Please choose the folder containing your music collection. Prego selezionare la cartella contenente la collezione musicale. Covers and Lyrics Copertine e Testi <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata scaricherà le copertine ed i testi mancanti da internet.</p><p>Per ognuno di questi, prego confermare se desideri che Cantata memorizzi i file in questione nella cartella musica, oppure nella tua personale cartella cache/configurazione.</p> Save downloaded covers, artist, and composer images, in music folder Salva le immagini di copertine, artisti e compositori scaricate nella cartella musica Save downloaded lyrics in music folder Salva i testi scaricati nella cartella musica Save downloaded backdrops in music folder Salva gli sfondi scaricati nella cartella musica If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Se hai scelto che Cantata memorizzi copertine, testi o sfondi all'interno della cartella musica e non hai permessi di accesso in essa, Cantata salverà i file nella tua cartella cache personale. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantata può salvare le immagini di sfondi, artisti e compositore all'interno della gerarchia della cartella musica solo se questa è profonda due livelli. es. 'Artista/Album/Brani'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. La 'Cartella musica' è impostata su un indirizzo HTTP e Cantata non può al momento caricare file su server HTTP esterni. Pertanto l'impostazione dovrà essere lasciata disattivata. Finished! Finito! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata ora è configurato!<br/><br/>La finestra di configurazione di cantata può esssere usata per personalizzare l'aspetto di cantata, ed anche per aggiungere altri host MPD. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantata raggrupperà le tracce in album usando il tag 'ArtistaAlbum' se impostato, altrimenti ripiegherà sul tag 'Artista'. Se hai album con artisti multipli, <b>dovrai<b> impostare il tag "ArtistaAlbum' per far sì che il raggruppamento funzioni correttamente.In quel scenario, suggeriamo di usare 'Artisti Vari'. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Attenzione:</b> Non sei attualmente un membro del gruppo 'users'. Cantata funziona meglio (salvando copertine di album, testi, ecc. con i permessi corretti) se tu (o il tuo amministratore) ti aggiungi a questo gruppo. Se ti aggiungi dovrai fare logour e rientrare per far sì che la modifica abbia effetto. Not Connected Non Connesso Connection Established Connession Stabilita Connection Failed Connessione Fallita Cantata will now terminate Cantata verrà ora terminato InputDialog Password Password Please enter password: Prego inserire la password: InterfaceSettings Sidebar Barra laterale Views Viste Use the checkboxes below to configure which views will appear in the sidebar. Usa le caselle sottostanti per configurare quali viste appariranno nella barra laterale. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Se 'Coda di Riproduzione' non è selezionato, apparirà a fianco delle altre viste. Se 'info' non è selezionato, verrà aggiunto un pulsante sulla barra strumenti per permetterti di accedere alle informazioni sul brano. Options Opzioni Style: Stile: Position: Posizione: Only show icons, no text Mostra solo le icone, nessun testo Auto-hide Nascondi automaticamente Play Queue Coda di Riproduzione Initially collapse albums Album contratti inizialmente Automatically expand current album Espandi automaticamente l'album corrente Scroll to current track Scorri alla traccia corrente Prompt before clearing Chiedi prima di svuotare Separate action (and shortcut) for play queue search Azione (e scorciatoia) separata per la ricerca nella coda di riproduzione Background Image Immagine di sfondo None Nessuna Current album cover Copertina dell'album corrente Custom image: Immagine personalizzata: Blur: Sfocatura: 10px 10px Opacity: Opacità: 40% 40% Toolbar Barra degli strumenti Show stop button Mostra pulsante di stop Show cover of current track Mostra copertina della traccia corrente Show track rating Mostra valutazione traccia External Esterno Enable MPRIS D-BUS interface Abilita l'interfaccia MPRIS D-BUS Show popup messages when changing tracks Mostra una notifica al cambio traccia Show icon in notification area Mostra icona nell'area di notifica Minimize to notification area when closed Alla chiusura, minimizza nell'area di notifica On Start-up All'avvio Show main window Mostra la finestra principale Hide main window Nascondi la finestra principale Restore previous state Ripristina lo stato precedente Tweaks Modifiche Artist && Album Sorting Ordinamento Artista && Album Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Inserisci una lista di prefissi (separati da virgole) da ignorare quando vengono ordinati artisti ed album. es. se impostato a 'The' allora 'The Beatles' verrà ordinato come 'Beatles' Enter comma separated list of prefixes... Inserisci una lista di prefissi separati da virgole... Composer Support Supporto Compositore By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Come impostazione predefinita, Cantata usa il tag 'Artista Album' (o 'Artista' se un brano non ha 'Artista Album') per raggruppare brani e album. Per certi generi, es 'Classical', può essere preferibile usare il tag 'compositore' (se impostato) per fare il raggruppamento. Prego inserire una lista di generi (separata da virgole) per i quali vuoi che Cantata usi il tag 'Compositore'. Enter comma separated list of genres... Inserisci una lista di generi separati da virgole... Single Tracks Tracce Singole If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Se nella collezione hai molti artisti che contengona una sola traccia, potrebbe essere scomodo che ogniuno di essi abbia la propria voce nella lista degli artisti. È possibile aggirare il problema mettendo queste tracce in una cartella separata, ed inserendo il nome della cartella qua sotto, cosicché Cantata le raggruppi in un album chiamato 'Tracce Singole' con artista album 'Artisti Vari' Folder that contains single track files... Cartella che contiene i file delle tracce singole... CUE Files File CUE A cue file is a metadata file which describes how the tracks of a CD are laid out. Un file CUE è un file di metadati che descrive la disposizione delle tracce di un CD. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. Le modifiche alle impostazioni sopra richiedono un aggiornamento del DB (e possibilmente un riavvio di Cantata) perché abbiano effetto. General Generale Fetch missing covers from Last.fm Scarica le copertine mancanti da Last.fm Show delete action in context menus Mostra l'azione Cancella nel menu contestuale Enforce single-click activation of items Forza l'attivazione delle voci con un solo clic Show song information tooltips Mostra in un suggerimento le informazioni sui brani Changing the style setting will require a re-start of Cantata. Le modifiche all'impostazione di stile richiedono il riavvio di Cantata. Support retina displays Supporto per gli schermi 'Retina' Language: Lingua: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Cambiare 'Forza l'attivazione delle voci con un solo clic' richiederà il riavvio di Cantata. Changing the language setting will require a re-start of Cantata. Cambiare le impostazioni della lingua richiederà il riavvio di Cantata. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. Abilitare il supporto per gli schermi 'retina' produrrà icone più nitide su di essi, ma potrebbe produrne di meno nitide sugli schemi non 'retina'. La modifica di quest'impostazione richiederà il riavvio di Cantata. Library Libreria Folders Cartelle Playlists Scalette Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet - Flussi, Jamendo, Magnatune, SoundCloud e Podcast Devices - UMS, MTP (e.g. Android), and AudioCDs Dispositivi - UMS, MTP (es. Android) e CD Audio Search (via MPD) Cerca (tramite MPD) Info - Current song information (artist, album, and lyrics) Info - Informazioni sul brano corrente (artista, album e testi) Large Grande Small Piccolo Tab-bar Barra a schede Left Sinistra Right Destra Top Sopra Bottom Sotto Images (*.png *.jpg) Immagini (*.png *.jpg) 10px pixels 10px Notifications Notifiche English (en) Inglese (en) System default Predefinita del sistema %1% value% %1% %1 px pixels %1 px ItemView Go Back Indietro Updating... Aggiornamento... JamendoService The world's largest digital service for free music Il più grande servizio digitale al mondo di musica libera JamendoSettingsDialog Jamendo Settings Impostazioni di Jamendo MP3 MP3 Ogg Ogg Streaming format: Formato del flusso: KeySequenceButton The key you just pressed is not supported by Qt. Il tasto che hai premuto non è supportato dalle Qt. Unsupported Key Tasto non supportato KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Clicca sul pulsante, quindi inserisci la scorciatoria che vorresti nel programma. Esempio per Ctrl+a: teni premuto il tasto Ctrl e premi a. Meta Meta key Meta Ctrl Ctrl key Ctrl Alt Alt key Alt Shift Shift key Maiuscole Input What the user inputs now will be taken as the new shortcut Inserimento None No shortcut defined Nessuna Shortcut Conflict Conflitto di Scorciatorie The "%1" shortcut is already in use, and cannot be configured. Please choose another one. La scorciatoria "%1" è già in uso e non può essere configurata. Prego sceglierne un'altra. The "%1" shortcut is ambiguous with the shortcut for the following action: La scorciatoria "%1" si può confondere con la scorciatoria per la seguente azione: Do you want to reassign this shortcut to the selected action? Vuoi riassegnare questa scorciatoia per l'azione scelta? Reassign Riassegna LastFmEngine Read more on last.fm Maggiori informazioni su last.fm LibraryDb Database error - please check Qt SQLite driver is installed Errore del Database - prego controllare che il driver Qt SQLite sia installato LibraryPage Show Artist Images Mostra Immagini Artisti Sort Albums Ordina Album Name Nome Year Anno Album, Artist, Year Album, Artista, Anno Album, Year, Artist Album, Anno, Artista Artist, Album, Year Artista, Album, Anno Artist, Year, Album Artista, Anno, Album Year, Album, Artist Anno, Album, Artista Year, Artist, Album Anno, Artista, Album Modified Date Data Modificata Group By Raggruppa per Genre Genere Artist Artista Album Album Are you sure you wish to delete the selected songs? This cannot be undone. Sicuro di voler cancellare i brani selezionati? Non sarà possibile tornare indietro. Delete Songs Cancella Brani LyricSettings Choose the websites you want to use when searching for lyrics. Scegli il sito che vuoi usare nelle ricerche dei testi. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Se Cantata non è riuscito a trovare il testo, oppure ne ha trovato uno sbagliato, usa questa finestra per inserire ulteriori dettagli per la ricerca. Ad esempio, il brano corrente potrebbe essere una cover - se è così, allora potrebbe aiutare cercare il testo dell'artista originale. Se questa ricerca dovesse trovare nuovi testi, sarebbero ancora associarti al titolo del brano ed all'artista originali come mostrati in Cantata. Title: Titolo: Artist: Artista: Search For Lyrics Cerca i testi MPDConnection Unknown Sconosciuto Connection to %1 failed Connessione a %1 fallita Connection to %1 failed - please check your proxy settings Connessione a %1 fallita - prego verificare le impostazioni del proxy Connection to %1 failed - incorrect password Connessione a %1 fallita - password errata Connecting to %1 Connessione a %1 Failed to send command to %1 - not connected Impossibile inviare il comando a %1 - non connesso Failed to load. Please check user "mpd" has read permission. Impossibile caricare. Prego verificare che l'utente "mpd" abbia i permessi di lettura. Failed to load. MPD can only play local files if connected via a local socket. Impossibile caricare. MPD può riprodure i file locali solo se connesso tramire un socket locale. MPD reported the following error: %1 MPD ha riportato il seguente errore: %1 Failed to send command. Disconnected from %1 Impossibile inviare il comando. Disconnesso da %1 Failed to rename <b>%1</b> to <b>%2</b> Impossibile rinominare <b>%1</b> in <b>%2</b> Failed to save <b>%1</b> Impossibile salvare <b>%1</b> You cannot add parts of a cue sheet to a playlist! Non è possibile aggiungere parti di un cue sheet ad una playlist! You cannot add a playlist to another playlist! Non è possibile aggiungere una playlist ad un'altra playlist! Failed to send '%1' to %2. Please check %2 is registered with MPD. Impossibile inviare '%1' a %2. Prego verificare che %2 sia registrato con MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. Impossibile memorizzare le valutazioni perché il comando MPD 'sticker' non è supportato. MagnatuneService None Nessuna Streaming Flusso MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com Musica online da magnatune.com MagnatuneSettingsDialog Magnatune Settings Impostazioni di Magnatune Username: Utente: Password: Password: Membership: Adesione: Downloads: Scaricamenti: MainWindow [Dynamic] [Dinamico] Exit Full Screen Esci da Schermo Intero Configure Cantata... Configura Cantata... Preferences Preferenze Quit Esci About Cantata... Informazoni su Cantata... Show Window Mostra Finestra Server information... Informazioni sul Server... Refresh Database Aggiorna Database Refresh Aggiorna Connect Connetti Collection Collezione Outputs Uscite Stop After Track Ferma dopo la Traccia Seek forward (%1 seconds) Cerca avanti (%1 secondi) Seek backward (%1 seconds) Cerca indietro (%1 secondi) Add To Stored Playlist Aggiungi alle Scalette Salvate Crop Others Rimuovi gli altri Add Stream URL Aggiungi URL flusso Clear Pulisci Center On Current Track Centra sulla Traccia Corrente Expanded Interface Interfaccia Estesa Show Current Song Information Mostra le Informazioni sul Brano Corrente Full Screen Schermo Intero Random Casuale Repeat Ripeti Single Singola When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Quando 'SIngola' è attivo, la riproduzione viene fermata dopo il brano corrente, oppure se 'Ripeti' è abilitato, il brano verrà ripetuto. Consume Consuma When consume is activated, a song is removed from the play queue after it has been played. Quando 'Consuma' è attivo, il brano viene rimosso dalla coda di riproduzione nonappena è stato riprodotto. Find in Play Queue Trova nella Coda di Riproduzione Play Stream Riproduci Flusso Locate In Library Trova nella Libreria Play next Riproduci il prossimo Edit Track Information (Play Queue) Modifica Informazioni Traccia (Coda di Riproduzione) Expand All Espandi Tutto Collapse All Contrai Tutto Cancel Annulla Play Queue Coda di Riproduzione Library Libreria Folders Cartelle Playlists Scalette Internet Internet Devices Dispositivi Search Cerca Info Informazioni Show Menubar Mostra la barra dei menu &Music &Musica &Edit &Modifica &View &Vista &Queue &Coda &Settings &Impostazioni &Help &Aiuto Set Rating Imposta Valutazione No Rating Nessuna Failed to locate any songs matching the dynamic playlist rules. Impossibile trovare brani che corrispondano alle regole dinamiche della scaletta. Connecting to %1 Connessione a %1 Refresh MPD Database? Aggiornare il Database di MPD? About Cantata Informazoni su Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>Client per MPD.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Rilasciato sotto licenza <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Basato su <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 Gli autori di QtMPC<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Sfondi della vista contestuale per cortesia di <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Metadati della vista contestuale per cortesia di <a href="http://www.wikipedia.org">Wikipedia</a> e <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Siete invitati a caricare la fostra fan-art musicale su <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. È in corso lo scaricamento di un podcast Uscire ora interromperà lo scaricamento. Abort download and quit Interrompi scaricamento ed esci Please close other dialogs first. Si prega di chiudere prima le altre finestre di dialogo. Enabled: %1 Abilitato: %1 Disabled: %1 Disabilitato: %1 Server Information Informazioni sul Server <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocollo:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Riproduzione:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tag:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artisti:&nbsp;</td><td>%1</td></tr><tr><td align="right">Album:&nbsp;</td><td>%2</td></tr><tr><td align="right">Brani:&nbsp;</td><td>%3</td></tr><tr><td align="right">Durata:&nbsp;</td><td>%4</td></tr><tr><td align="right">Aggiornato il:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD ha riportato il seguente errore: %1 Cantata Cantata Playback stopped Riproduzione fermata Remove all songs from play queue? Rimuovere tutti i brani dalla coda di riproduzione? Priority Priorità Enter priority (0..255): Inserire priorità (0..255): Decrease priority for each subsequent track Riduci la priorità di tutte le tracce successive Playlist Name Nome Scaletta Enter a name for the playlist: Inserire un nome per la scaletta: '%1' is used to store favorite streams, please choose another name. '%1' è già usato per salvare i flussi preferiti, prego scegliere un altro nome. A playlist named '%1' already exists! Add to that playlist? Una scaletta chiamata '%1' esiste già! Aggiungere i brani a quella scaletta? Existing Playlist Scalette Esistenti %n Track(s) %n Traccia %n Tracce %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) MenuButton Menu Menu MessageOverlay Cancel Annulla Mpris (Stream) (Flusso) MtpConnection Connecting to device... Connessione al dispositivo... No devices found Nessun dispositivo trovato Connected to device Connesso al dispositivo Disconnected from device Disconnessione dal dispositivo Updating folders... Aggiornamento cartelle... Updating tracks... Aggiornamento tracce... MtpDevice Not Connected Non Connesso %1 free %1 liberi MusicBrainz Failed to open CD device Impossibile aprire il dispositivo CD Track %1 Traccia %1 %1 (Disc %2) %1 (Disco %2) No matches found in MusicBrainz Nessuna corrispondenza trovata su MusicBrainz MusicLibraryModel Cue Sheet Cue Sheet Playlist Scaletta %n Track(s) %n Traccia %n Tracce %n Artist(s) %n Artista %n Artisti %n Album(s) %n Album %n Album %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) %1 by %2 Album by Artist %1 di %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) (Flusso) OSXStyle &Window &Finestra Close Chiudi Minimize Minimizza Zoom Ingrandisci OnlineDbService Downloading...%1% Scaricamento... %1% Parsing music list.... Lettura lista musica... Failed to download Scaricamento fallito %n Artist(s) %n Artista %n Artisti OnlineDbWidget Group By Raggruppa per Genre Genere Artist Artista Configure Configura The music listing needs to be downloaded, this can consume over %1Mb of disk space È necessario scaricare la lista musicale, può arrivare ad occupare più di %1Mb di spazio su disco Dowload music listing? Scaricare la lista musicale? Download Scarica Re-download music listing? Scaricare nuovamente la lista musicale? OnlineSearchService Searching... Ricerca... OnlineSearchWidget No tracks found. Nessuna traccia trovata. %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Usa le caselle sottostanti per configurare la lista dei servizi attivi. Configure Service Configura Servizio OnlineView Song Information Informazioni sul Brano OnlineXmlParser Failed to parse Lettura fallita OpmlBrowsePage Reload Ricarica Failed to download directory listing Scaricamento della lista cartelle fallita Failed to parse directory listing Lettura della lista cartelle fallita OtherSettings Background Image Immagine di sfondo None Nessuna Artist image Immagine Artista Custom image: Immagine personalizzata: Blur: Sfocatura: 10px 10px Opacity: Opacità: 40% 40% Automatically switch to view after: Passa automaticamente alla vista dopo: Do not auto-switch Non passare automaticamente ms ms Dark background Sfondo scuro Darken background, and use white text, regardless of current color palette. Scurisci lo sfondo ed usa il testo bianco, indipendentemente dall'attuale schema di colori. Always collapse into a single pane Contrai sempre in un unica schermata Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Mostra solo 'Artista', 'Album', o 'Traccia' anche se vi è sufficiente spazio per mostrarli tutti assieme. Only show basic wikipedia text Mostra solo un testo base di wikipedia Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantata mostrerà solamente una versione ridotta delle pagine di wikipedia (nessuna immagine, collegamento, ecc). Questa riduzione non è sempre accurata al 100%, per questo motivo Cantata mostrerà solo l'introduzione. Se sceglierai di mostrate l'intero articolo, potrebbero esserci degli errori di lettura. Dovrai inoltre rimuovere tutti gli articoli attualmente in cache (dalla pagina 'Cache'). Images (*.png *.jpg) Immagini (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Seleziona Cartella Select File Selezione File PlayQueueModel Title Titolo Artist Artista Album Album # Track number # Length Durata Disc Disco Year Anno Original Year Anno Originale Genre Genere Priority Priorità Composer Compositore Performer Esecutore Rating Valutazione Remove Duplicates Rimuovi Duplicati Undo Annulla Redo Ripeti Shuffle Mescola Tracks Tracce Albums Album Sort By Ordina per Album Artist Artista Album Track Title Titolo Traccia Track Number Numero Traccia # (Track Number) # (Numero Traccia) PlayQueueView Remove Rimuovi PlaybackSettings Playback Riproduzione Fa&deout on stop: Fa&deout allo stop: None Nessuno ms ms Stop playback on exit Ferma la riproduzione all'uscita Inhibit suspend whilst playing Inibisci la sospensione quando in riproduzione If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Se prmi e teini premuto il pulsante di stop, apparirà un menu da cui potrai scegliere se fermare la riproduzione ora oppure dopo la traccia attuale. (È possibile abilitare il pulsante di stop nella sezione Interfaccia/Barra degli strumenti Output Uscita <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Non Connesso!<br/>Le voci sottostanti non possono venire modificare perché Cantata non è connesso a MPD.</i> &Crossfade between tracks: &Crossfade tra le tracce: s s Replay &gain: Replay &gain: About replay gain Informazioni su replay gain Use the checkboxes below to control the active outputs. Usa le caselle sottostanti per controllare le uscire attive. Track Traccia Album Album Auto Auto <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Connesso a %1<br/>Le voci sottostanti si applicano alla collezione di MPD attualmente connessa.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gain è uno standard proposto pubblicato nel 2001 per normalizzare il volume percepito dei formati audio come MP3 e Ogg Vorbis. Funziona in base alla traccia o all'album ed è attualmete supportato da un sempre maggior numero di lettori.<br/><br/>Possono venire usare le seguenti impostazioni di Replay Gain:<ul><li><i>Nessuno</i> - Non viene applicato Replay Gain</li><li><i>Traccia</i> - Il volume verrà regolato usando il tag Replay Gain della traccia.</li><li><i>Album</i> - Il volume verrà regolato usando il tag Replay Gain dell'album.</li><li><i>Auto</i> - Se è attiva la riproduzione casuale, il volume verrà regolato usando il tag Replay Gain della traccia, altrimenti verrà usato quello dell'album.</li></ul> PlaylistRule Type: Tipo: Include songs that match the following: Includi i brani che corrispondono ai seguenti: Exclude songs that match the following: Escludi i brani che corrispondono ai seguenti: Artist: Artista: Artists similar to: Artista simile a: Album Artist: Artista Album: Composer: Compositore: Album: Album: Title: Titolo: Genre Genere From Year: Dall'anno: Any Qualsiasi To Year: All'anno: Comment: Commento: Filename / path: Nome file / percorso: Exact match Corrispondenza esatta Only enter values for the tags you wish to be search on. Inserire solamente i valori dei tag per i quali fare la ricerca. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Per il genere, terminare la stringa con un asterisco per trovare corrispondenze con diversi generi. es. 'rock*' corrisponde a 'Hard Rock' e 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Regola Dinamica Smart Rule Regola Intelligente Add Aggiungi <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ERRORE</b>: 'Dall'anno' dovrebbe essere inferiore ad 'All'anno'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ERRORE:</b> L'intervallo di date è troppo largo (può essere al massimo di %1 anni)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>ERRORE:</b> Puoi cercare corrispondenze di nome file/ percorso se 'Corrispondenza esatta' <b>non</b> è selezionato</i> PlaylistRules Name of Dynamic Rules Nome delle Regole Dinamiche Add Aggiungi Edit Modifica Remove Rimuovi Songs with ratings between: Brani con valutazione tra: - - Songs with duration between: Brani con durata tra: seconds secondi Number of songs in play queue: Brani in coda di riproduzione: Order songs: Ordine brani: About Rules Informazioni su Regole PlaylistRulesDialog Dynamic Rules Regole Dinamiche Smart Rules Regole Intelligenti No Limit Nessun Limite Ascending Ascendente Descending Discendente Name of Smart Rules Nome delle Regole Intelligenti Number of songs Numero di brani About dynamic rules Informazioni su regole dinamiche <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata interrogherà la libreria utilizzando tutte le regole elencate. La lista delle regole di <i>inclusione</i> verrà usata per costruire un insieme di brani che possonno essere usati. La lista di regole di <i>esclusione</i> verrà usata per costruire un insieme di brani che non possono essere usati. Se non ci sono regole di <i>inclusione</i>, Catata assumerà che tutti i brani (eccetto quelli <i>esclusi</i> possono essere usati.</p><p>es. per far sì che Cantata cerchi 'Canzoni rock di Wibble O canzoni di Artisti Vari', ti servirà quetso:<ul><li>Includi ArtistaAlbum=Wibble Genere=Rock</li><li>Includi ArtistaAlbum=Artisti Vari</li></ul>Perché Cantata cerchi 'Canzoni di Wibble ma non dall'album Abc', ti servirà questo:<ul><li>Includi ArtistaAlbum=Wibble</li><li>Escludi ArtistaAlbum=Wibble Album=Abc</li></ul>Dopo che l'insieme di brani utilizzabili è stato creato, Cantata selezionerà casualmente i brani da tenere nella coda di riproduzione (l'impostazione predefinita è 10). Se è stato specificato un intervallo di valutazioni, allora saranno usati solo i brani all'interno di quell'intervallo. Similmente, se è stato impostato un intervallo di durata.</p> About smart rules Informazioni sulle regole intelligenti <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata interrogherà la libreria utilizzando tutte le regole elencate. La lista delle regole di <i>inclusione</i> verrà usata per costruire un insieme di brani che possonno essere usati. La lista di regole di <i>esclusione</i> verrà usata per costruire un insieme di brani che non possono essere usati. Se non ci sono regole di <i>inclusione</i>, Catata assumerà che tutti i brani (eccetto quelli <i>esclusi</i> possono essere usati.</p><p>es. per far sì che Cantata cerchi 'Canzoni rock di Wibble O canzoni di Artisti Vari', ti servirà quetso:<ul><li>Includi ArtistaAlbum=Wibble Genere=Rock</li><li>Includi ArtistaAlbum=Artisti Vari</li></ul>Perché Cantata cerchi 'Canzoni di Wibble ma non dall'album Abc', ti servirà questo:<ul><li>Includi ArtistaAlbum=Wibble</li><li>Escludi ArtistaAlbum=Wibble Album=Abc</li></ul>Dopo che l'insieme di brani utilizzabili è stato creato, Cantata selezionerà il numero di brani desiderato alla coda di riproduzione. Se è stato specificato un intervallo di valutazioni, allora saranno usati solo i brani all'interno di quell'intervallo. Similmente, se è stato impostato un intervallo di durata.</p> <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with 10 entries. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Cantata interrogherà la libreria utilizzando tutte le regole elencate. La lista delle regole di <i>inclusione</i> verrà usata per costruire un insieme di brani che possonno essere usati. La lista di regole di <i>esclusione</i> verrà usata per costruire un insieme di brani che non possono essere usati. Se non ci sono regole di <i>inclusione</i>, Catata assumerà che tutti i brani (eccetto quelli <i>esclusi</i> possono essere usati.</p><p>es. per far sì che Cantata cerchi 'Canzoni rock di Wibble O canzoni di Artisti Vari', ti servirà quetso:<ul><li>Includi ArtistaAlbum=Wibble Genere=Rock</li><li>Includi ArtistaAlbum=Artisti Vari</li></ul>Perché Cantata cerchi 'Canzoni di Wibble ma non dall'album Abc', ti servirà questo:<ul><li>Includi ArtistaAlbum=Wibble</li><li>Escludi ArtistaAlbum=Wibble Album=Abc</li></ul>Dopo che l'insieme di brani utilizzabili è stato creato, Cantata selezionerà casualmente i 10 brani da tenere nella coda di riproduzione. Se è stato specificato un intervallo di valutazioni, allora saranno usati solo i brani all'interno di quell'intervallo. Similmente, se è stato impostato un intervallo di durata.</p> Failed to save %1 Impossibile salvare %1 A set of rules named '%1' already exists! Overwrite? Esiste già un insieme di regole chiamato '%1'! Sovrascrivere? Overwrite Rules Sovrascrivi Regole Saving %1 Salvataggio %1 PlaylistsModel New Playlist... Nuova Scaletta... Stored Playlists Scalette Salvate Standard playlists Scalette standard %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) Smart Playlist Playlist Intelligente PodcastPage RSS: RSS: Website: Sito web: Podcast details Dettagli del podcast Select a podcast to display its details Seleziona un podcast per mostrarne i dettagli PodcastSearchDialog Subscribe Sottoscrivi Enter URL Inserisci URL Manual podcast URL URL maniale del podcast Search %1 Cerca %1 Search for podcasts on %1 Cerca podcast su %1 Add Podcast Subscription Aggiungi Sottoscrizione al Podcast Browse %1 Naviga %1 Browse %1 podcasts Naviga i podcast di %1 You are already subscribed to this podcast! Hai già sottoscritto questo podcast! Subscription added Sottoscrizione aggiunta PodcastSearchPage Enter search term... Inserire il termine di ricerca... Search Cerca Failed to fetch podcasts from %1 Impossibile scaricare podcast da %1 There was a problem parsing the response from %1 C'è stato un problema nell'interpretare la risposta da %1 PodcastService Subscribe to RSS feeds Sottoscrivi ad un abbonamento RSS %n Podcast(s) %n Podcast %n Podcast %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) %n Episodio %n Episodi (Downloading: %1%) (Scaricamento: %1%) Failed to parse %1 Impossibile leggere %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata supporta solamente i podcast audio! %1 contiene solo podcast video. Failed to download %1 Impossibile scaricare %1 PodcastSettingsDialog Check for new episodes: Controlla i nuovi episodi: Download episodes to: Scarica gli episodi fino a: Download automatically: Scarica automaticamente: Podcast Settings Impostazioni Podcast Manually Manualmente Every 15 minutes Ogni 15 minuti Every 30 minutes Ogni 30 minuti Every hour Ogni ora Every 2 hours Ogni 2 ore Every 6 hours Ogni 6 ore Every 12 hours Ogni 12 ore Every day Ogni giorno Every week Ogni settimana Don't automatically download episodes Non scaricare automaticamente gli episodi Latest episode Ultimo episodio Latest %1 episodes Ultimi %1 episodi All episodes Tutti gli episodi PodcastUrlPage URL URL Enter podcast URL... Inserire l'URL del podcast... Load Carica Enter podcast URL below, and press 'Load' Inserisci sotto l'URL del podcast e premi 'Carica' Invalid URL! URL non valido! Failed to fetch podcast! Impossibile scaricare il podcast! Failed to parse podcast. Impossibile leggere il podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata supporta solamente i podcast audio! L'URL inserito contiene solo podcast video. PodcastWidget Add Subscription Aggiungi Sottoscrizione Remove Subscription Rimuovi Sottoscrizione Download Episodes Scarica Episodi Delete Downloaded Episodes Candella gli Episodi Scaricati Cancel Download Annulla Scaricamento Mark Episodes As New Segna gli Episodi come Nuovi Mark Episodes As Listened Segna gli Episodi come Ascoltati Show Unplayed Only Mostra solo quelli non riprodotti Unsubscribe from '%1'? Rimuovere sottoscrizione da '%1'? Do you wish to download the selected podcast episodes? Vuoi scaricare gli episodi del podcast selezionati? Cancel podcast episode downloads (both current and any that are queued)? Annulla gli scaricamenti degli episodi del podcast (sia l'attuale che gli altri in coda)? Do you wish to the delete downloaded files of the selected podcast episodes? Vuoi cancellare i file degli episodi di podcast scaricati selezionati? Do you wish to mark the selected podcast episodes as new? Vuoi segnare come nuovi i file degli episodi di podcast selezionati? Do you wish to mark the selected podcast episodes as listened? Vuoi segnare come ascoltati i file degli episodi di podcast selezionati? Refresh all subscriptions? Aggiornare tutte le sottoscrizioni? Refresh Aggiorna Refresh All Aggiorna Tutto Refresh all subscriptions, or only those selected? Aggiornare tutte le sottoscrizioni o solo quelli selezionati? Refresh Selected Aggiorna Selezionati PowerManagement Cantata is playing a track Cantata sta riproducendo una traccia PreferencesDialog Collection Collezione Collection Settings Impostazioni della Collezione Playback Riproduzione Playback Settings Impostazioni di Riproduzione Downloaded Files File Scaricati Downloaded Files Settings Impostazioni dei File Scaricati Interface Interfaccia Interface Settings Impostazioni dell'Interfaccia Info Informazioni Info View Settings Impostazioni della Vista Informazioni Scrobbling Scrobbling Scrobbling Settings Impostazioni di Scrobbling Audio CD CD Audio Audio CD Settings Impostazioni dei CD Audio Proxy Proxy Proxy Settings Impostazioni del Proxy Shortcuts Scorciatoie Keyboard Shortcut Settings Impostazioni delle Scorciatoie da Tastiera Cache Cache Cached Items Oggetti in Cache Custom Actions Azioni Personalizzate Cantata Preferences Preferenze di Cantata Configure Configura ProxySettings Mode: Modo: Type: Tipo: HTTP Proxy Proxy HTTP SOCKS Proxy Proxy SOCKS Host: Host: Port: Porta: Username: Nome utente: Password: Password: No proxy Nessun proxy Use the system proxy settings Usa le impostazioni di proxy del sistema Manual proxy configuration Configurazione del proxy manuale QObject Track listing Lista tracce Read more on wikipedia Leggi di più su wikipedia Open in browser Apri nel browser <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) è un codec lossy per l'audio digitale brevettato.<br>AAC generalmente ottiene un suono di qualità migliore a parità di bitrate rispetto ad MP3. È una scelta ragionevole per l'uso su iPod ed altri lettori musicali portatili. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Il bitrate è la misura della quantità di dati utilizzati per rappresentare un secondo di suono in una traccia audio.<br>L'encoder <b>AAC</b> usato da Cantata supporta l'impostazione <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>bitrate variabile (VBR)</a>, ciò significa che il vaolre del bitrate fluttua all'interno della traccia in base alla complessità del contenuto audio. Gli intervalli con dati più complessi vengono codificati ad un bitrate maggiore di quelli meno complessi; questo approccio mira ad una qualità globale maggiore ed una dimensione inferiore rispetto a ciò che si ottiene con un bitrate costante.<br>Per questo motivo la misura del bitrate su questo cursore è solo una stima del <a href=http://www.ffmpeg.org/faq.html#SEC21>bitrate medio</a> della traccia codificata.<br><b>150kb/s</b> è una buona scelta per l'ascolto di musica su un lettore portatile.<br/>Valori sotto i <b>120kb/s</b> potrebbero essere insoddisfacenti per la musica e qualsiasi valore sopra i <b>200kb/s</b> sarebbe probabilmente esagerato. Expected average bitrate for variable bitrate encoding Bitrate medio previsto per la codifica a bitrate bariabile Smaller file File più piccolo Better sound quality Miglior qualità audio <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Livello 3</a> (MP3) è un tipo di codec lossy per l'audio digitale brevettato.<br>Nonostante i suoi limiti, è un formato comune per la memorizzazione audio ed è ampiamente supportato nei lettori musicali portatili. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Il bitrate è la misura della quantità di dati utilizzati per rappresentare un secondo di suono in una traccia audio.<br>L'encoder <b>MP3</b> usato da Cantata supporta l'impostazione <a href=http://en.wikipedia.org/wiki/MP3#VBR>bitrate variabile (VBR)</a>, ciò significa che il vaolre del bitrate fluttua all'interno della traccia in base alla complessità del contenuto audio. Gli intervalli con dati più complessi vengono codificati ad un bitrate maggiore di quelli meno complessi; questo approccio mira ad una qualità globale maggiore ed una dimensione inferiore rispetto a ciò che si ottiene con un bitrate costante.<br>Per questo motivo la misura del bitrate su questo cursore è solo una stima del bitrate medio della traccia codificata.<br><b>160kb/s</b> è una buona scelta per l'ascolto di musica su un lettore portatile.<br/>Valori sotto i <b>120kb/s</b> potrebbero essere insoddisfacenti per la musica e qualsiasi valore sopra i <b>205kb/s</b> sarebbe probabilmente esagerato. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> è un codec per la compressione audio lossy aperto e libero da royalty.<br>Produce file di dimensioni inferiori ad MP3 con qualità equivalente o maggiore. Ogg Vorbis è una scelta eccellente, specialmente per i lettori musicali portatili che lo supportano. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Il bitrate è la misura della quantità di dati utilizzati per rappresentare un secondo di suono in una traccia audio.<br>L'encoder <b>Vorbis</b> usato da Cantata supporta l'impostazione <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>bitrate variabile (VBR)</a>, ciò significa che il vaolre del bitrate fluttua all'interno della traccia in base alla complessità del contenuto audio. Gli intervalli con dati più complessi vengono codificati ad un bitrate maggiore di quelli meno complessi; questo approccio mira ad una qualità globale maggiore ed una dimensione inferiore rispetto a ciò che si ottiene con un bitrate costante.<br>L'encoder Vorbis utilizza un valore di qualità compreso tra -1 and 10 per definire il livello di qualità previsto. La misura del bitrate su questo cursore è solo una stima grezza (fornita da Vorbis) del bitrate medio della traccia codificata in base al valore di qualità. In realtà, con le nuove e più efficienti versioni di Vorbis il bitrate reale è anche più basso.<br><b>5</b> è una buona scelta per ascoltare musica su un lettore portatile.<br/>Valori sotto il <b>3</b> potrebbero essere insoddisfacenti per la musica e qualsiasi valore sopra ad <b>8</b> sarebbe probabilmente esagerato. Quality rating Valore di qualità Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> è un codec per la compressione audio lossy aperto e libero da brevetti. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Il bitrate è la misura della quantità di dati utilizzati per rappresentare un secondo di suono in una traccia audio.<br>L'encoder <b>Opus</b> usato da Cantata supporta l'impostazione <a href=http://en.wikipedia.org/wiki/Variable_bitrate>bitrate variabile (VBR)</a>, ciò significa che il vaolre del bitrate fluttua all'interno della traccia in base alla complessità del contenuto audio. Gli intervalli con dati più complessi vengono codificati ad un bitrate maggiore di quelli meno complessi; questo approccio mira ad una qualità globale maggiore ed una dimensione inferiore rispetto a ciò che si ottiene con un bitrate costante.<br>Per questo motivo la misura del bitrate su questo cursore è solo una stima del bitrate medio della traccia codificata.<br><b>128kb/s</b> è una buona scelta per l'ascolto di musica su un lettore portatile.<br/>Valori sotto i <b>100kb/s</b> potrebbero essere insoddisfacenti per la musica e qualsiasi valore sopra i <b>256kb/s</b> sarebbe probabilmente esagerato. Bitrate Bitrate Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) è un codec lossless per la compressione di audio digitale.<br>Raccomandato solo per i lettori musicali Apple e per i lettori che non supportano il formato FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) è un codec lossless per la compressione di audio digitale aperto e libero da royalty.<br>Se vuoi salvare la tua musica senza comprometterne la qualità, FLAC è una scelta eccellente. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Il <a href=http://flac.sourceforge.net/documentation_tools_flac.html>livello di compressione</a> è un valore intero compreso tra 0 e 8 che rappresenta il compromesso tra dimensione del file e velocità di compressione con<b>FLAC</b>.<br/> Impostando il livello di compressione a <b>0</b> si ottiene il tempo di compressione più breve, ma anche un file comparabilmente più grande.<br/>D'altra parte, un livello di compressione di <b>8</b> rende al compressione abbastanza lenta, ma produce file più piccoli.<br/>Notare che, essendo FLAC per definizione un codec lossless, la qualità audio risultante è esattamente la stessa indipendentemente dal livello di compressione.<br/>Inoltre, livelli sopra al <b>5</b> aumentano drammaticamente i tempi di compressione, ma creano dei file solo leggermente più piccoli, e non sono consigliati. Compression level Livello di compressione Faster compression Compressione più veloce Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) è un codec proprietario di Microsoft per la compressione audio lossy.<br>Raccomandato solo per i lettori musicali portatili che non supportano Ogg Vorbis.. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Il bitrate è la misura della quantità di dati utilizzati per rappresentare un secondo di suono in una traccia audio.<br>A causa delle limitazione del formato proprietario <b>WMA</b> ed alla difficoltà di fare reverse engineering su un encoder proprietario, il codec WMA usato da Cantata imposta il <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>bitrate costante (CBR)</a>.<br>Per questa ragione, la misura del bitrate sul cursore è una stima abbastanza accurata del bitrate della traccia codificata.<br><b>136kb/s</b> è una buona scelta per ascoltare musica su un lettore portatile.<br/>Valori sotto <b>112kb/s</b> potrebbero essere insoddisfacenti per la musica e qualsiasi valore sopra i <b>182kb/s</b> sarebbe probabilmente esagerato. Empty filename. Nome file vuoto. Invalid filename. (%1) Nome file non valido. (%1) Failed to save %1. Impossibile salvare %1. Failed to delete rules file. (%1) Impossibile cancellare il file regole. (%1) Invalid command. (%1) Comando non valido. (%1) Could not remove active rules link. Impossibile rimuovere il collegamento alle regole attive. Active rules is not a link. Le regole attive non sono un collegamento. Could not create active rules link. Impossibile creare il collegamento alle regole attive. Rules file, %1, does not exist. Il file di regole %1 non esiste. Incorrect arguments supplied. Gli argomenti forniti non sono corretti. Unknown method called. Chiamata a metodo sconosciuta. Unknown error Errore sconosciuto Artist Artista SimilarArtists ArtistiSimili AlbumArtist ArtistaAlbum Composer Compositore Comment Commento Album Album Title Titolo Genre Genere Date Data File File Include Includi Exclude Escludi (Exact) (Esatto) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Copertina Corrente CoverArt Archive Archivio Copertine Grouped Albums Album Raggruppati Table Tabella Parse in Library view, and show in Folders view Elabora nella vista Libreria e mostra nella vista Cartelle Only show in Folders view Mostra solo nella vista Cartelle Do not list Non elencare Previous Track Traccia Precedente Next Track Traccia Successiva Play/Pause Riproduci/Pausa Stop Ferma Stop After Current Track Ferma dopo la Traccia Corrente Stop After Track Ferma dopo la Traccia Increase Volume Aumenta Volume Decrease Volume Diminuisci Volume Save As Salva come Append Accoda Append To Play Queue Accoda alla Coda di Riproduzione Append And Play Accoda e Riproduci Add And Play Aggiungi e Riproduci Append To Play Queue And Play Accoda alla Coda di Riproduzione e Riproduci Insert After Current Inserisci dopo l'Attuale Append Random Album Accoda un Album a Caso Play Now (And Replace Play Queue) Riproduci Ora (E Sostituisci la Coda di Riproduzione) Add With Priority Aggiungi con Priorità Set Priority Imposta Priorità Highest Priority (255) Piorità Massima (255) High Priority (200) Priorità Alta (200) Medium Priority (125) Piorità Media (125) Low Priority (50) Priorità Baassa (50) Default Priority (0) Priorità Predefinita (0) Custom Priority... Priorità Personalizzata... Add To Playlist Aggiungi alla Scaletta Organize Files Organizza i File Edit Track Information Modifica Informazioni Traccia ReplayGain ReplayGain Copy Songs To Device Copia i Brani sul Dispositivo Delete Songs Cancella Brani Set Image Imposta Immagine Remove Rimuovi Find Trova Add To Play Queue Aggiungi alla Coda di Riproduzione Parse error loading cache file, please check your songs tags. Errore di lettura caricandi il file di cache, prego verifica i tag dei tuoi brani. Other Altro Default Predefinito "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Tracce Singole Personal Personale Unknown Sconosciuto Various Artists Artisti Vari Album artist Artista album Performer Esecutore Track number Numero traccia Disc number Numero Disco Year Anno Orignal Year Anno Originale Length Durata <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> su <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> di <b>%2</b> su <b>%3</b> Invalid service Servizio non valido Invalid method Metodo non valido Authentication failed Autenticazione fallita Invalid format Formato non valido Invalid parameters Parametri non validi Invalid resource specified Risorsa specificata non valida Operation failed Operazione fallita Invalid session key Chiave di sessione non valida Invalid API key Chiave API non valida Service offline Servizio offline Last.fm is currently busy, please try again in a few minutes Last.fm è attualmente occupato, prego riprovare tra qualche minuto Rate-limit exceeded Limite-dati superato General Generale Digitally Imported Digitally Imported Local and National Radio (ListenLive) Radio Locale e Nazionale (ListenLive) &OK &OK &Cancel &Annulla &Yes &Sì &No &No &Discard &Abbandona &Save &Salva &Apply &Applica &Close &Chiudi &Help &Aiuto &Overwrite &Sovrascrivi &Reset &Reimposta &Continue &Continua &Delete &Cancella &Stop &Ferma &Remove &Rimuovi &Previous &Precedente &Next &Successivo Close Chiudi Error Errore Information Informazioni Warning Avviso Question Domanda %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Albero Base (Senza Icone) Simple Tree Albero Semplice Detailed Tree Albero Dettagliato List Lista Grid Griglia RemoteDevicePropertiesDialog Device Properties Proprietà del Dispositivo Connection Connessione Music Library Libreria Musicale Add Device Aggiungi Dispositivo A remote device named '%1' already exists! Please choose a different name. Esiste già un dispositivo remoto chiamato '%1'! Prego scegliere un nome diverso. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Queste impostazioni sono modificabili solo quando il dispositivo non è connesso. Type: Tipo: Name: Nome: Options Opzioni Host: Host: Port: Porta: User: Utente: Domain: Dominio: Password: Password: Share: Condivisione: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Se inserisci qua la password, verrà salvata <b>non criptata</b> nel file di configurazione di Cantata. Per far sì che Cantata chieda la password prima di accedere alla condivisione, imposta la password a '-' Service name: Nome del servizio: Folder: Cartella: Extra Options: Opzioni extra: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. Per via del modo in cui sshfs funziona, è richiesta un'applicazione ssh-askpass (ksshaskpass, ssh-askpass-gnome, ecc.) per poter inserire la password. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Questa finestra viene usata solamente per aggiungere dispositivi remoti (es. tramite Samba), o per accedere a cartelle montate localmente. Per i lettori musicali normali collegati via USB, Cantata mostrerà automaticamente quando il dispositivo è collegato. Samba Share Condivisione Samba Samba Share (Auto-discover host and port) Samba Share (Trova automaticamente host e porta) Secure Shell (sshfs) Shell Sicura (sshfs) Locally Mounted Folder Cartella Montata Localmente RemoteFsDevice Available Disponibile Not Available Non Disponibile Failed to resolve connection details for %1 Impossibile risolbere i dettagli per la connessione di %1 Connecting... Connessione... Password prompting does not work when cantata is started from the commandline. La richiesta password non funziona quando Cantata viene lanciato dalla riga di comando. No suitable ssh-askpass application installed! This is required for entering passwords. Applicazione ssh-askpass non installata! È richiesta per l'inserimento delle password. Mount point ("%1") is not empty! Il punto di mount ("%1") non è vuoto! "sshfs" is not installed! "sshfs" non è installato! Disconnecting... Disconnessione... "fusermount" is not installed! "fusermount" non è installato! Failed to connect to "%1" Impossibile connettersi a "%1" Failed to disconnect from "%1" Impossibile disconnettersi da "%1" Updating tracks... Aggiornamento tracce... Not Connected Non Connesso Capacity Unknown Capacità sconosciuta %1 free %1 liberi RgDialog ReplayGain ReplayGain Show All Tracks Mostra Tutte le Tracce Show Untagged Tracks Mostra le Tracce senza Tag Remove From List Rimuovi dalla Lista Artist Artista Album Album Title Titolo Album Gain Guadagno Album Track Gain Guadagno Traccia Album Peak Picco Album Track Peak Picco Traccia Scan Scansione Update ReplayGain tags in tracks? Aggiornare il tag ReplayGain nelle tracce? Update Tags Aggiorna Tracce Abort scanning of tracks? Interrompere la scansione delle tracce? Abort Interrompi Abort reading of existing tags? Interrompere la lettura dei tag esistenti? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Scansionare <b>tutte</b> le tracce?<br/><br/><i>Tutte le tracce hanno già dei tag ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Vuoi fare la scansione di tutte le tracce o solo di quelle senza i tag? Untagged Tracks Tracce senza Tag All Tracks Tutte le Tracce Scanning tracks... Scansione tracce... Reading existing tags... Lettura tag esistenti... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Tag corrotti?) Failed to update the tags of the following tracks: Impossibile aggiornare i tag delle seguenti tracce: Device has been removed! Il dispositivo è stato rimosso! Device is not connected. Il dispositivo non è connesso. Device is busy? Il dispositivo è occupato? %1 dB %1 dB Failed Fallito Original: %1 dB Originale: %1 dB Original: %1 Originale: %1 Remove the selected tracks from the list? Rimuovere le tracce selezionate dalla lista? Remove Tracks Rimuovi Tracce RulesPlaylists - Rating: %1..%2 - Valutazione: %1..%2 Album Artist Artista Album Artist Artista Album Album Composer Compositore Date Data Genre Genere Rating Valutazione File Age Random Casuale %n Rule(s) %n Regola %n Regole , Rating: %1..%2 Valutazione: %1..%2 Ascending Ascendente Descending Discendente Scrobbler %1 error: %2 %1 errore: %2 ScrobblingLove %1: Loved Current Track %1 hanno Amato la Traccia Corrente %1: Love Current Track %1 Amano la Traccia Corrente ScrobblingSettings Scrobble using: Scrobble usando: Username: Nome utente: Password: Password: Status: Stato: Login Accesso Scrobble tracks Tracce Scrobble Show 'Love' button Mostra pulsante 'Amore' %1 (via MPD) scrobbler name (via MPD) %1 (tramite MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Se stai usando uno scrobbler che è marcato come '(tramite MPD)' (come %1), allora questo dev'essere già avviato. Cantata può indicare 'Amore' sulle tracce tramite questo e non può abilitare/disabilitare lo scrobbing. Authenticating... Autenticazione... Authenticated Autenticato Not Authenticated Non Autenticato ScrobblingStatus %1: Scrobble Tracks %1: Tracce Scrobble SearchModel # (Track Number) # (Numero Traccia) SearchPage Locate In Library Trova nella Libreria Artist: Artista: Composer: Compositore: Performer: Esecutore: Album: Album: Title: Titolo: Genre: Genere: Comment: Commento: Date: Data: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Trova brani usando il tag 'Data'.<br/><br/>Solitamente è sufficiente inserire l'anno. Original Date: Data Originale: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Trova brani usando il tag 'Data Originale'.<br/><br/>Solitamente è sufficiente inserire l'anno. Modified: Modificato: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Inserisci una data (AAAA/MM/GG - es, 2015/01/31) per cercare file modificati a partire da quella data.<br/><br>Oppure inserisci un numero di giorni per trovare i file che sono stati modificati nei giorni precedenti. File: File: Any: Qualsiasi: No tracks found. Nessuna traccia trovata. %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) SearchWidget Search... Cerca... Close Search Bar Chiudi Barra di Ricerca ServerSettings Collection: Collezione: Name: Nome: Host: Host: Password: Password: Music folder: Cartella musica: Cover filename: Nome Coperina: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Nome file (senza estensione) con cui salvare le copertine scaricate.<br/>Se lasciato vuoto verrà usato 'cover'.<br/><br/><i>%artist% verrà sostituito dall'artista dell'album del brano corrente, ed %album% verrà sostituito col nome dell'album.</i></p> HTTP stream URL: URL del flusso HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. L'impostazione della 'Cartella Musica' verrà usata per cercare la copertina. Può venire impostata ad un URL HTTP se il tuo MPD è su un'altra macchina e le copertine sono accessibili tramite HTTP. Se non è impostata su un URL HTTP ed hai i permessi di scrittua in quella cartella (e nelle sue sotto-cartelle), Cantata salverà qualsiasi copertina scaricata nella rispettiva cartella dell'album. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> Se non viene specificato nulla per 'Nome Copertina', Cantata usera l'impostazione predefinita <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. 'URL del flusso HTTP' è utile solamente se MPD è configurato per uscire su un flusso HTTP e vuoi che Cantata sia in grado di riprodurre quel flusso. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. Se modifichi l'impostazione "Cartella Musica', dovrai aggiornare manualmente il database musicale. Ciò può essere fatto premendo il pulsante 'Aggiorna Database' nelle viste 'Artisti' o 'Album'. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. Questa cartella verrà usata anche per trovare i file musicali per la modifica di tag, replay gain e per trasferirli verso (e da) i dispositivi. This folder will also be used to locate music files for tag-editing, replay gain, etc. Questa cartella verrà usata anche per trovare i file musicali per la modifica di tag, ecc. Which type of collection do you wish to connect to? A che tipo di collezione ti vuoi connettere? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Standard - la collezione musicale può venire condivisa, è su un'altra macchina, è già configurata, oppure vuoi permettere l'accesso da altri client (es. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Base - la collezione musicale non è condivisa con altri e Cantata configurerà e controllerà l'istanza di MPD. Quest'impostazione sarà esclusiva per Cantata e <b>non</b> sarà accessibile ad altri client MPD. <i><b>NOTE:</b> %1</i> <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Se desideri una configurazione di MPD avanzata (es. uscite audio multiple, supporto completo DSD, ecc) allora <b>dovrai</b> scegliere 'Standard' Add Collection Aggiungi Collezione Standard Standard Basic Base Delete '%1'? Cancellare '%1'? Delete Cancella New Collection %1 Nuova Collezione %1 Default Predefinito ServiceStatusLabel Logged into %1 Registrato su %1 <b>NOT</b> logged into %1 <b>NON</b> registrato su %1 ShortcutsModel Action Azione Shortcut Scorciatoia ShortcutsSettingsWidget Search: Cerca: Shortcut for Selected Action Scorciatoia per l'Azione Selezionata Default: Predefinita: None Nessuna Custom: Personalizzata: SinglePageWidget Refresh Aggiorna View Vista SmartPlaylists Smart Playlists Scalette Inelligenti Rules based playlists Scalette basate su regole SmartPlaylistsPage Add Aggiungi Edit Modifica Remove Rimuovi Are you sure you wish to remove the selected rules? This cannot be undone. Sicuro di voler cancellare le regole selezionate? Non sarà possibile tornare indietro. Remove Smart Rules Rimuovi Regole Intelligenti Failed to locate any matching songs Non è stato possibile trovare brani corrispondenti SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Impossibile accedere ai file dei brani! Prego controllare l'impostazione "Cartella Musica" di cantata e "music_directory" di MPD. Cannot access song files! Please check that the device is still attached. Impossibile accedere ai file dei brani! Prego controllare che il dispositivo sia ancora collegato. SongView Lyrics Testi Information Informazioni Metadata Metadati Scroll Lyrics Scorri Testi Refresh Lyrics Aggiorna Testi Edit Lyrics Modifica Testi Delete Lyrics File Cancella il file dei Testi Refresh Track Information Aggiorna Informazioni Traccia Cancel Annulla Track Traccia Reload lyrics? Reload from disk, or delete disk copy and download? Ricaricare testi? Ricaricare da disco, oppure cancellare la copia su disco e scaricare? Reload Ricarica Reload From Disk Ricarica da Disco Download Scarica Current playing song has changed, still perform search? Il brano in riproduzione è cambiato, proseguire ancora la ricerca? Song Changed Brano Cambiato Perform Search Esegui Ricerca Delete lyrics file? Cancellare il file dei Testi? Delete File Cancella File Artist Artista Album artist Artista album Composer Compositore Lyricist Paroliere Conductor Direttore Remixer Remixer Album Album Subtitle Sottotitolo Track number Numero traccia Disc number Numero Disco Genre Genere Date Data Original date Data Originale Comment Commento Copyright Copyright Label Etichetta Catalogue number Numero di catalogo Title sort Ordinamento titolo Artist sort Ordinamento artista Album artist sort Ordinamento artista album Album sort Ordinamento album Encoded by Codificato da Encoder Codificatore Mood Atmosfera Media Media Bitrate Bitrate Sample rate Campionamento Channels Canali Tagging time Data di tag Performer (%1) Esecutore (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bit Performer Esecutore Year Anno Filename Nome file Fetching lyrics via %1 Scaricamento testi da %1 SoundCloudService Search for tracks from soundcloud.com Cerca tracce da soundcloud.com SpaceLabel Calculating... Calcolo... Total space used: %1 Spazio utilizzato totale: %1 SqlLibraryModel %n Artist(s) %n Artista %n Artisti %n Album(s) %n Album %n Album %n Tracks (%1) %n Traccia (%1) %n Tracce (%1) Cue Sheet Cue Sheet Playlist Scaletta StoredPlaylistsPage Rename Rinomina Remove Duplicates Rimuovi Duplicati Initially Collapse Albums Album contratti inizialmente Are you sure you wish to remove the selected playlists? This cannot be undone. Sicuro di voler cancellare le scalette selezionate? Non sarà possibile tornare indietro. Remove Playlists Rimuovi Scalette Playlist Name Nome Scaletta Enter a name for the playlist: Inserire un nome per la scaletta: A playlist named '%1' already exists! Overwrite? Una scaletta chiamata '%1' esiste già! Sovrascrivere? Overwrite Playlist Sovrascrivi Scaletta Rename Playlist Rinomina Scaletta Enter new name for playlist: Inserire un nuovo nome per la scaletta: Cannot add songs from '%1' to '%2' Impossibile aggiungere i brani da '%1' a '%2' StreamDialog Add stream to favourites Aggiungi il flusso ai preferiti Name: Nome: URL: URL: Add Stream Aggiungi Flusso Edit Stream Modifica Flusso <i><b>ERROR:</b> Invalid protocol</i> <i><b>ERRORE:</b> Protocollo non valido</i> StreamFetcher Loading %1 Caricamento %1 StreamProviderListDialog Installed Installato Update available Aggiornamenti disponibile Check the providers you wish to install/update. Contolla il fornitore che vuoi installare/aggiornare. Install/Update Stream Providers Installa/Aggiorna Fornitori di Flussi Downloading list... Scaricamento lista... Failed to download list of stream providers! Impossibile scaricare la lista dei fornitori di flussi! Installing/updating %1 Installazione/aggiornamento %1 Failed to install '%1' Impossibile installare '%1' Failed to download '%1' Impossibile scaricare '%1' Install/update the selected stream providers? Installare/aggiornare i fornitori di flussi selezionati? Install the selected stream providers? Installare i fornitori di flussi selezionati? Update the selected stream providers? Aggiornare i fornitori di flussi selezionati? Install/Update Installa/Aggiorna Abort installation/update? Interrompere installazione/aggiornamento? Abort Interrompi %n Update(s) available Disponibile %n aggiornamento Disponibili %n aggiornamenti Downloading %1 Scaricamento %1 Update all updateable providers Aggiorna tutti i fornitori aggiornabili StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Cerca Flusso Search for radio streams Cerca flussi radio Enter string to search Inserisci la stringa da ricercare Not Loaded Non Caricato Loading... Caricamento... %n Entry(s) %n Elemento %n Elementi StreamSearchPage Added '%1'' to favorites '%1' aggiunto ai preferiti StreamsBrowsePage Import Streams Into Favorites Importa Flussi nei Preferiti Export Favorite Streams Esporta Flussi Preferiti Add New Stream To Favorites Aggiungi Nuovo Flusso ai Preferiti Edit Modifica Seatch For Streams Cerca Flussi Configure Configurazione Digitally Imported Service name Digitally Imported Import Streams Importa Flussi XML Streams (*.xml *.xml.gz *.cantata) Flussi XML (*.xml *.xml.gz *.cantata) Export Streams Esporta Flussi XML Streams (*.xml.gz) Flussi XML (*.xml.gz) Failed to create '%1'! Impossibile creare '%1'! Stream '%1' already exists! Il flusso '%1' esiste già! A stream named '%1' already exists! Un flusso chiamato '%1' esiste già! Bookmark added Aggiunto un segnalibro Already bookmarked Già nei segnalibri Already in favorites Già nei preferiti Reload '%1' streams? Ricaricare '%1' flussi? Are you sure you wish to remove bookmark to '%1'? Sicuro di voler cancellare il segnalibro di '%1'? Are you sure you wish to remove all '%1' bookmarks? Sicuro di voler cancellare il tutti i '%1' segnalibri? Are you sure you wish to remove the %1 selected streams? Sicuro di voler cancellare i %1 flussi selezionati? Are you sure you wish to remove '%1'? Sicuro di voler cancellare '%1'? Added '%1'' to favorites '%1' aggiunto ai preferiti StreamsModel Bookmarks Segnalibri TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Preferiti Bookmark Category Categoria Segnalibri Add Stream To Favorites Aggiungi il Flusso ai Preferiti Configure Digitally Imported Configura Digitally Imported Reload Ricarica Streams Flussi Radio stations Stazioni Radio Not Loaded Non Caricato Loading... Caricamento... %n Entry(s) %n Elemento %n Elementi StreamsSettings Use the checkboxes below to configure the list of active providers. Usa le caselle sottostanti per configurare la lista dei fornitori attivi. Built-in categories are shown in italic, and these cannot be removed. Le categorie preimpostate sono mostrate in corsivo e non possono essere rimosse. Configure Streams Configura Flussi From File... Da File... Download... Scaricamento... Configure Provider Configura Fornitore Install Installa Remove Rimuovi Install Streams Installa Flussi Cantata Streams (*.streams) Flussi di Cantata (*.streams) A category named '%1' already exists! Overwrite? Una categoria chiamata '%1' esiste già! Sovrascrivere? Failed top open package file. Impossibile caricare il file pacchetto. Invalid file format! Formato file non valido! Failed to create stream category folder! Impossibile creare la cartella delle categorie dei flussi! Failed to save stream list! Impossibile salvare la lista dei flussi! Are you sure you wish to remove '%1'? Sicuro di voler cancellare '%1'? Failed to remove streams folder! Impossibile rimuovere la cartella dei flussi! SyncCollectionWidget Search Cerca Check Items Seleziona Elementi Uncheck Items Deseleziona Elementi SyncDialog Library: Libreria: Device: Dispositivo: Loading all songs from library, please wait... Caricamento dei brani dalla libreria, attendere... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Libreria</code> elenca solo i brani presenti nella libreria ma non nel dipositivo. Similmente <code>Dispositivo</code> elenca i brani che sono solo sul dispositivo.<br/>Scegli i brani da <code>Libreria</code> che vorresti copiare sul <code>Dispositivo</code>, e scegli i brani dal <code>Dispositivo</code> che vorresti copiare nella <code>Libreria</code>. Infine premi il pulsate <code>Sincronizza</code>. Synchronize Sincronizza Device and library are in sync. Il dispositivo e la libreria sono sincronizzati. Loading all songs from library, please wait...%1%... Caricamento dei brani dal dispositivo, attendere...%1%... Local Music Library Properties Proprieta della Libreria Musicale Locale Device has been removed! Il dispositivo è stato rimosso! Device has been changed? È stato cambiato il dispositivo? Device is busy? Il dispositivo è occupato? TableView Stretch Columns To Fit Window Adatta le Colonne all Finestra Left Sinistra Center Centro Right Destra Alignment Allineamento TagEditor Track: Traccia: Title: Titolo: Artist: Artista: Album artist: Artista album: Composer: Compositore: Album: Album: Track number: Numero traccia: Disc number: Numero disco: Genre: Genere: Year: Anno: Rating: Valutazione: <i>(Various)</i> <i>(Vari)</i> Comment: Commento: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Generi multipli vanno separati con delle virgole (es. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Le valutazioni sono salvate in un database esterno, <b>non</b> nel file del brano. Tags Tag Tools Strumenti Apply "Various Artists" Workaround Applica soluzione per "Artisti Vari" Revert "Various Artists" Workaround Rimuovi soluzione per "Artisti Vari" Set 'Album Artist' from 'Artist' Imposta 'Artista Album' da 'Artista' Capitalize Aggiusta Maiuscole Adjust Track Numbers Aggiusta Numerazione Tracce Read Ratings from File Leggi le Valutazioni da File Write Ratings to File Scrivi le Valutazioni su File All tracks Tutte le Tracce Apply "Various Artists" workaround to <b>all</b> tracks? Applicare soluzione per "Artisti Vari"? a <b>tutte</b> le tracce? Apply "Various Artists" workaround? Applicare soluzione per "Artisti Vari"? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Questo imposterà 'Artista album' e 'Artista' a "Artisti Vari", e 'Titolo' a "ArtistaTraccia - TitoloTraccia"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Ripristinare dalla soluzione per "Artisti Vari" su <b>tutte</b> le tracce? Revert "Various Artists" workaround Ripristina dalla soluzione per "Artisti Vari" <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Quando 'Artista album' è uguale a 'Artista' e il 'Titolo' è nel formato "ArtistaTraccia - TitoloTraccia", 'Artista' verrà preso da 'Titolo' e 'Titolo' verrà impostato al solo titolo. es. <br/><br/>Se 'Titolo' è "Wibble - Wobble", allora 'Artista' verrà impostato a "Wibble" e 'Titolo' verrà impostato a "Wobble"</i> Revert Ripristina Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Impostare 'Artista Album' da 'Artista' (se 'Artista Album' è vuoto) per <b>tutte</b> le tracce? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Impostare 'Artista Album' da 'Artista' (se 'Artista Album' è vuoto)? Album Artist from Artist Artista Album da Artista Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Rendere maiuscole le prime delle dei campi di testo (es. 'Titolo', 'Artista', ecc) per <b>tutte</b> le tracce? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Rendere maiuscole le prime delle dei campi di testo (es. 'Titolo', 'Artista', ecc)? Adjust the value of each track number by: Aggiustare il valore del numero di ogni traccia per: Adjust track number by: Aggiusta numerazione traccia per: Read ratings for all tracks from the music files? Leggere le valutazione di tutte le tracce dai file musicali? Read rating from music file? Leggere le valutazioni dal file musicale? Ratings Valutazioni Read Ratings Leggi Valutazioni Read Rating Leggi Valutazione Read, and updated, ratings from the following tracks: Lette ed aggiornate le valutazioni dalle seguenti tracce: Not all Song ratings have been read from MPD! Non tutte le valutazioni dei brani sono state lette da MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Le valutazioni dei brani non sono salvate nei file dei brani ma nel database 'sticker' di MPD. Per poterle salvare nel file, Cantata deve poterle leggere da MPD. Song rating has not been read from MPD! La valutazione del brano non è stata letta da MPD! Write ratings for all tracks to the music files? Scrivere le valutazioni di tutte le tracce nei file musicali? Write rating to music file? Scrivere le valutazioni sui file musicali? Write Ratings Scrivi Valutazioni Write Rating Scrivi Valutazione Failed to write ratings of the following tracks: Impossibile scrivere le valutazione delle seguenti tracce: Failed to write rating to music file! Impossibile scrivere la valutazione sul file musicale! All tracks [modified] Tutte le tracce [modificate] %1 [modified] %1 [modificata] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Tag corrotti?) Failed to update the tags of the following tracks: Impossibile aggiornare i tag delle seguenti tracce: Would you also like to rename your song files, so as to match your tags? Vuoi anche rinominare i tuoi file musicali, in modo che corrispondano ai tuoi tag? Rename Files Rinomina File Rename Rinomina Device has been removed! Il dispositivo è stato rimosso! Device is not connected. Il dispositivo non è connesso. Device is busy? Il dispositivo è occupato? TagSpinBox (Various) (Vari) ThinSplitter Reset Spacing Reimposta Spaziatura TitleWidget Click to go back Clicca per tornare indietro Add All To Play Queue Aggiungi Tutto alla Coda di Riproduzione Add All And Replace Play Queue Aggiungi Tutto e Sostituisci la Coda di Riproduzione ToggleList Available: Disponibile: Selected: Selezionato: TrackOrganiser Filenames Nomi file Filename scheme: Schema nomi file: VFAT safe Adatto a VFAT Use only ASCII characters Usa solo caratteri ASCII Replace spaces with underscores Sostituisci gli spazi con underscore Append 'The' to artist names Accoda il 'The' ai nomi degli artisti Original Name Nome Originale New Name Nuovo nome Ratings will be lost if a file is renamed. Le valutazioni verranno perdute se il file sarà rinominato. Organize Files Organizza i File Rename Rinomina Remove From List Rimuovi dalla Lista Abort renaming of files? Interrompere la ridenominazione dei file? Abort Interrompi Source file does not exist! Il file sorgente non esiste! Skip Salta Auto Skip Salta automaticamente Destination file already exists! I file di destinazione esiste già! Failed to create destination folder! Impossibile creare la cartella di destinazione! Failed to rename '%1' to '%2' Impossibile rinominare '%1' in '%2' Remove the selected tracks from the list? Rimuovere le tracce selezionate dalla lista? Remove Tracks Rimuovi Tracce Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Le valutazioni dei brani non sono salvate nei file dei brani ma nel database 'sticker' di MPD. Se rinomini un file (o la cartella che lo contiene), la valutazione associata al brano verrà persa. Device has been removed! Il dispositivo è stato rimosso! Device is not connected. Il dispositivo non è connesso. Device is busy? Il dispositivo è occupato? TrayItem Cantata Cantata Now playing In riproduzione UltimateLyricsProvider (Polish Translations) (Traduzioni Polacche) (Portuguese Translations) (Traduzioni Portoghesi) UmsDevice Not Scanned Non Scansionato Not Connected Non Connesso %1 free %1 liberi ValueSlider (recommended) (raccomandato) View Cancel Annulla VolumeSlider Mute Muto Unmute Non Muto Volume %1% (Muted) Volume %1% (Muto) Volume %1% Volume %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artista|gruppo|cantante|vocalista|musicista album|score|soundtrack Search pattern for an album, separated by | album|partitura|colonna-sonora WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Scegli le lingue che vuoi utilizzare su wikipedia quando vengono cercate le informazioni su album e artista. Reload Ricarica cantata-2.2.0/translations/cantata_ja.ts000066400000000000000000027070511316350454000203210ustar00rootroot00000000000000 Refresh Album Information アルバム情報の更新 Album アルバム Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) トラック Refresh Artist Information アーティスト情報の更新 Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) アーティスト Albums アルバム Web Links Webリンク Similar Artists 同様のアーティスト Lyrics Providers 歌詞プロバイダ Wikipedia Languages Wikipedia 言語 Other その他 &Artist アーティスト(&A) Al&bum アルバム(&b) &Track トラック(&T) Read more on last.fm lat.fmよりさらに読込 If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Cantata が歌詞検索に失敗もしくは誤った楽曲を発見した際、このダイアログで新たな詳細検索を行ってください。例えば、現在の楽曲は実際にはカバーバージョンである場合、オリジナルのアーティストによる歌詞の検索が役立つかもしれません。 もし検索で新たな歌詞が見つからない場合、Cantata で表示されているオリジナルの楽曲タイトルとアーティストの関連付けが継続状態です。 Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) タイトル: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) アーテイスト: Search For Lyrics 歌詞の検索 Choose the websites you want to use when searching for lyrics. 歌詞の検索に使用したいサイトを選択してください。 Song Information 楽曲情報 Images (*.png *.jpg) 画像ファイル (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics 歌詞 Information 情報 Metadata メタデータ Scroll Lyrics 歌詞をスクロール Refresh Lyrics 歌詞を更新 Edit Lyrics 歌詞を編集 Delete Lyrics File 歌詞ファイルを削除 Refresh Track Information トラック情報を更新 Cancel キャンセル Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) トラック Reload lyrics? Reload from disk, or delete disk copy and download? 瑕疵を再読込しますか? ディスクから再読込もしくはディスクから削除しダウンロードし直しますか? Reload 再読込 Reload From Disk ディスクから再読込 Download ダウンロード Current playing song has changed, still perform search? 現在の再生中楽曲は変更しました。検索を継続しますか? Song Changed 楽曲が変更されました Perform Search 検索を実行する Delete lyrics file? 歌詞ファイルを削除しますか? Delete File ファイルの削除 Album artist アルバム アーティスト Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) 作曲家 Lyricist 作詞家 Conductor 指揮者 Remixer レミキサー Subtitle サブタイトル Track number トラック番号 Disc number ディスク番号 Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) ジャンル Date 日付 Original date オリジナル日付 Comment コメント Copyright 版権 Label ラベル Catalogue number カタログ番号 Title sort タイトル ソート Artist sort アーティスト ソート Album artist sort アルバムアーティスト ソート Album sort アルバム ソート Encoded by エンコード Encoder エンコーダ Mood ムード Media メディア Bitrate ビットレート Sample rate サンプルレート Channels チャンネル Tagging time タグ付け時間 Performer (%1) 演奏者 (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits ビット Performer 演奏者 Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Filename ファイル名 Fetching lyrics via %1 %1 より歌詞を取得中 (Polish Translations) (ポーランド語 翻訳) (Portuguese Translations) (ポルトガル語 翻訳) Track listing トラックリスト Read more on wikipedia wikipedia で更に確認 Open in browser ブラウザで開く artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | アーティスト|バンド|歌手|ボーカリスト|ミュージシャン album|score|soundtrack Search pattern for an album, separated by | アルバム|楽譜|サウンドトラック Choose the wikipedia languages you want to use when searching for artist and album information. アーティストやアルバム情報の検索の際に使用するwikipediaでの言語を選択してください。 Cantata is playing a track Cantata はトラックを再生中 <b>INVALID</b> <b>無効</b> <i>(When different)</i> <i>(差異がある場合)</i> Artists:%1, Albums:%2, Songs:%3 アーティスト:%1, アルバム:%2, 楽曲:%3 %1 free %1 空き Local Music Library ローカルミュージックライブラリ Audio CD オーディオCD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. 出力先のデバイスに十分な空き容量がありません。 選択した曲は%1 を消費しますが、 %2 しか残っていません。 曲は、正常にコピーされるために、より小さなファイルサイズに変換される必要があります。 There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. 出力先に十分な空き容量がありません。 選択した曲は%1 を消費しますが、%2 しか残っていません。 Copy Songs To Library 楽曲をライブラリに複写 Copy Songs To Device 楽曲をデバイスに複写 Copy Songs 楽曲を複写 Delete Songs 楽曲を削除 You have not configured the destination device. Continue with the default settings? 出力先のデバイスを設定していません。 規定の設定で継続しますか? Not Configured 未設定 Use Defaults 規定値を使用 You have not configured the source device. Continue with the default settings? 元デバイスを設定しておりません。 規定の設定で継続しますか? Are you sure you wish to stop? 本当に停止しますか? Stop 停止 Device has been removed! デバイスは取り外しされました! Device is not connected! デバイスは未接続です! Device is busy? デバイスが使用中? Device has been changed? デバイスが変更された? Clearing unused folders 未使用フォルダを削除 Calculate ReplayGain for ripped tracks? リッピング済みトラックでリプレイゲインを計算しますか? ReplayGain リプレイゲイン Calculate 計算 The destination filename already exists! 出力ファイル名は既に使用済みです! Song already exists! 楽曲が既に存在します! Song does not exist! 楽曲が存在しません! Failed to create destination folder!<br/>Please check you have sufficient permissions. 出力先フォルダの作成に失敗しました<br/>十分な権限があることを確認してください。 Source file no longer exists? 元ファイルが既に存在しない? Failed to copy. 複写に失敗しました。 Failed to delete. 削除に失敗しました。 Not connected to device. デバイスに未接続です。 Selected codec is not available. 選択コーデックは存在しません。 Transcoding failed. トランスコーディングに失敗しました。 Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) 一時ファイルの作成に失敗しました<br/> Failed to read source file. 元ファイルの読み込みに失敗。 Failed to write to destination file. 出力先ファイルの書込に失敗しました。 No space left on device. デバイスの空き容量がありません。 Failed to update metadata. メタデータの更新に失敗しました。 Failed to download track. トラックのダウンロードに失敗しました。 Failed to lock device. デバイスのロックに失敗しました。 Local Music Library Properties ローカルミュージックライブラリのプロパティ Error エラー Skip スキップ Auto Skip オートスキップ Retry 再試行 Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) アルバム: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) トラック: Source file: 入力元ファイル: Destination file: 出力先ファイル: File: ファイル: Calculating... 計算中... %1 (Estimated) time (Estimated) %1 (推定) Time remaining: 残時間: Saving cache キャッシュを保存中 Apply "Various Artists" Workaround "Various Artists" による回避策を適用 Revert "Various Artists" Workaround "Various Artists" による回避策を元に戻す Capitalize 大文字に変更 Adjust Track Numbers トラック番号を調整 Tools ツール Apply "Various Artists" workaround? "Various Artists" による回避策を適用しますか? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" ’アルバムアーティスト' や 'アーティスト' を "Various Artists" に設定し 'タイトル' を "トラックアーティスト - トラックタイトル" に設定する Revert "Various Artists" workaround? "Various Artists" による回避策を元に戻しますか? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" 'アルバムアーティスト' と 'アーティスト'が同一で、'タイトル'の形式が'トラックアーティスト - トラックタイトル' の場合、’アーティスト’ は ’タイトル’ のトアックアーティストから取得され、’タイトル’ 自体はタイトルのみ(トラックタイトル)設定されます 。 例えば ’タイトル’が”ウォブル - ウォブル” の場合、’アーティスト’は ”ウォブル” に設定され、’タイトル’は’ウォブル’に設定されます Revert 元に戻す Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? 'タイトル', 'アーティスト', 'アルバムアーティスト', 'アルバム' の最初の1文字を大文字に変更しますか? Adjust track number by: トラック番号を調整: Reading disc ディスクの読込中 CDDB CDDB MusicBrainz MusicBrainz Data Track データトラック Failed to open CD device CD デバイスのオープンに失敗 Track %1 トラック %1 Failed to create CDDB connection CDDB 接続の作成に失敗 Failed to contact CDDB server, please check CDDB and network settings CDDB サーバへの接続に失敗しました、CDDBとネットワーク設定を確認してください No matches found in CDDB CDDB内に一致項目がありません CDDB error: %1 CDDB エラー: %1 Multiple matches were found. Please choose the relevant one from below: 複数の一致候補がありました。以下から該当するものを選択してください: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) タイトル Disc Selection ディスクの選択 %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 ディスク %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... 更新中 (%1)... Updating (%1%)... 更新中 (%1%)... Device Properties デバイスプロパティ Don't copy covers カバーを複写しない Embed cover within each file 各ファイルにカバーを埋め込む No maximum size 最大サイズなし 400 pixels 400 ピクセル 300 pixels 300 ピクセル 200 pixels 200 ピクセル 100 pixels 100 ピクセル <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>デバイスにトラックを複写し、 'アルバムアーティスト'が 'Various Artists'に設定されている場合、Cantataはすべてのトラックの 'アーティスト'タグを 'Various Artists'に、トラック 'タイトル'タグを 'トラックアーティスト - トラックタイトル 'に設定します。<hr /> Cantataは、デバイスからコピーする際に、' アルバムアーティスト 'と' アーティスト 'の両方が' Various Artists 'に設定されているかどうかを確認します。 そうであれば、 'タイトル'タグから実際のアーティストを抽出し、 'タイトル'タグからアーティスト名を削除します。</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>本機能を有効にした際、Cantata は本デバイスのミュージックライブラリのキャッシュを生成します。これは、後続のライブラリースキャンを高速化するのに役立ちます(各ファイルのタグを読み取る代わりに、キャッシュファイルが使用されるため)。<hr/><b>注意:</b>別のアプリケーションを使用してデバイスのライブラリを更新すると、このキャッシュは古くなってしまいます。 これを修正するには、デバイスリストの「更新」アイコンをクリックします。 これにより、キャッシュファイルが削除され、デバイスの内容が再スキャンされます。</p> Do not transcode トランスコードしない Transcode to %1 %1 へトランスコード %1 (%2 free) name (size free) %1 (%2 空き) Copy To Library ライブラリへ複写 Synchronise 同期 Forget Device デバイスを消去 Add Device デバイスを追加 Lookup album and track details? アルバムとトラックの詳細を検索しますか? Refresh 更新 Via CDDB CDDB経由 Via MusicBrainz MusicBrainz経由 Which type of refresh do you wish to perform? どのタイプの更新を行いますか? Partial - Only new songs are scanned (quick) 部分的 - 追加楽曲をスキャン (高速) Full - All songs are rescanned (slow) 完全 - 全楽曲を再スキャン (低速) Partial 部分的 Full 完全 Are you sure you wish to delete the selected songs? This cannot be undone. 選択楽曲を削除しますか? 本操作はやり直しできません。 Are you sure you wish to forget '%1'? ’%1’ を消去しますか? Are you sure you wish to eject Audio CD '%1 - %2'? オーディオ CD %1 をイジェクトしますか - %2 ? Eject イジェクト Are you sure you wish to disconnect '%1'? %1 を切断しますか? Disconnect 切断 Please close other dialogs first. 他のダイアログを先に閉じてください。 <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Advanced_Audio_Coding">Advanced Audio Coding</a> (AAC) はディジタルオーディオ向けの特許取得済み非可逆コーデックです。<br>AACは、一般に、類似のビットレートのMP3よりも優れた音質を実現します。 これは、iPodやその他のポータブルミュージックプレーヤーにとっては合理的な選択です。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataで使用される<b> AAC </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR">可変ビットレート(VBR)</a>の設定をサポートしており、これを使用した場合ビットレート値はオーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレートの数値は、エンコードされたトラックの<a href="http://www.ffmpeg.org/faq.html#SEC21">平均ビットレート</a>の見積もりにすぎません。<br>ポータブルプレーヤーで音楽を聴くには、<b>150kb/s</b>が適しています。<br/><b>120kb/s</b>未満は音楽には不満足で、<b>200kb/s</b>以上はおそらく過剰です。 Expected average bitrate for variable bitrate encoding 可変ビットレート符号化の予想平均ビットレート Smaller file より小さなファイル Better sound quality より良いサウンド品質 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/MP3">MPEG Audio Layer 3</a> (MP3) は、不可逆データ圧縮形式を使用した特許取得済みのデジタルオーディオコーデックです。<br>その欠点にもかかわらず、コンシューマオーディオストレージの一般的なフォーマットであり、携帯音楽プレーヤーで広くサポートされています。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataが使用する<b>MP3</b>エンコーダは、<a href="http://en.wikipedia.org/wiki/MP3#VBR">可変ビットレート(VBR)</a>の設定をサポートしており、ビットレート値は、オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレート測定値は、符号化されたトラックの平均ビットレートの推定値にすぎません。<br><b> 160kb/s</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<b>120kb/s</b>未満は音楽に不満足かもしれませんし、<b>205kb/s</b>以上はおそらく過剰です。 Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Vorbis">Ogg Vorbis</a>は、非可逆オーディオ圧縮のためのオープンでロイヤリティフリーのオーディオコーデックです。<br>同等以上の品質でMP3よりも小さいファイルを作成します。Ogg Vorbisは、特にそれをサポートするポータブルミュージックプレイヤーにとって、万能の優れた選択肢です。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataで使用される<b> Vorbis </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder">可変ビットレート(VBR)</a>の設定をサポートしており、ビットレート値は、オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化される。このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>Vorbisエンコーダは、-1〜10の間の品質評価を使用して、特定の期待されるオーディオ品質レベルを定義します。このスライダのビットレート測定値は、品質値が与えられた符号化トラックの平均ビットレートの概算値(Vorbis提供)です。実際、より新しい、より効率的なVorbisバージョンでは、実際のビットレートはさらに低くなります。<br><b>5</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<br/><b>3</b>以下は音楽には不満足で、<b>8</b>以上ははおそらく過剰です。 Quality rating 品質評価 Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Opus_(audio_format)"> Opus </a>は、非可逆データ圧縮形式を使用した特許のないデジタルオーディオコーデックです。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataが使用する<b> Opus </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Variable_bitrate">可変ビットレート(VBR)</a>設定をサポートしており、これは、ビットレート値 オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます。; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレート測定値は、符号化されたトラックの平均ビットレートの推定値にすぎません。<br><b>128kb/s</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<br/><b>100kb/s</b>未満は音楽に不満があり、<b>256kb/s</b>以上はおそらく過剰です。 Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Apple_Lossless"> Apple Lossless </a>(ALAC)は、デジタル音楽のロスレス圧縮のためのオーディオコーデックです。<br>Apple Music PlayerおよびFLACをサポートしないプレーヤにおすすめです。 FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec">フリーロスレスオーディオコーデック</a>(FLAC)は、デジタル音楽の可逆圧縮のためのオープンでロイヤリティフリーのコーデックです。<br> オーディオ品質を損なうことなく音楽を保存するには、FLACは優れた選択肢です。 The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href="http://flac.sourceforge.net/documentation_tools_flac.html">圧縮レベル</a>は、<b> FLACでエンコードする際のファイルサイズと圧縮速度のトレードオフを表す0〜8の整数値です </b>。<br/>圧縮レベルを<b>0</b>に設定すると、圧縮時間は最も短くなりますが、ファイルサイズは比較的大きくなります。<br/>一方、圧縮レベルが<b>8</b>の場合、圧縮はかなり遅くなりますが、ファイルが最も小さくなります。<br/>FLACはロスレスコーデックであるため、圧縮レベルに関係なく出力のオーディオ品質はまったく同じです。<br/>また、<b>5</b>以上のレベルでは圧縮時間が大幅に増加しますが、ファイルサイズはわずかに小さくなるだけなので、推奨しません。 Compression level 圧縮レベル Faster compression より早い圧縮 Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href="http://en.wikipedia.org/wiki/Windows_Media_Audio"> Windows Media Audio </a>(WMA)は、損失の多いオーディオ圧縮のためにMicrosoftによって開発された独自のコーデックです。<br>Ogg Vorbisをサポートしていない携帯音楽プレーヤーにのみおすすめです。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br><b>WMA</b>形式独自のの制限と独自エンコーダのリバースエンジニアリングの難しさのため、Cantataが使用するWMAエンコーダは<a href="http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio">固定ビットレート (CBR)</a> 設定のみ使用します。<br>この理由から、このスライダーのビットレートは、コード化されたトラックのビットレートのかなり正確な見積もりです。<b>136kb/s</b>は、ポータブルプレーヤーでの音楽聴取に適しています。 <br/><b>112kb/s</b>未満は音楽には不満足で、<b>182kb/s</b>以上はおそらく過剰です。 Filename Scheme ファイル名体系 Various Artists Example album artist Various Artists Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. 以下の変数は、各トラック名に対応する意味に置き換えられます。 <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>変数</em></th><th><em>ボタン</em></th><th><em>詳細</em></th></tr> Updating... 更新中... Reading cache キャッシュ読込中 %1 %2% Message percent %1 %2% Connecting to device... デバイスへの接続中... No devices found デバイスが見つかりません Connected to device デバイスに接続済 Disconnected from device デバイスに切断済 Updating folders... フォルダの更新中... Updating files... ファイルの更新中... Updating tracks... トラックの更新中... Not Connected 未接続 %1 (Disc %2) %1 (ディスク %2) No matches found in MusicBrainz MusicBrainz 内に一致項目がありません Connection 接続 Music Library ミュージックライブラリ A remote device named '%1' already exists! Please choose a different name. '%1' という名前のリモートデバイスは既に存在します! 他の名前を選択してください。 Samba Share Samba 共有 Samba Share (Auto-discover host and port) Samba 共有(ホストとポートを自動検索) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder ローカルにマウントされたフォルダ Available 利用可能 Not Available 利用不可 Failed to resolve connection details for %1 %1への接続詳細を解決できませんでした Connecting... 接続中... Password prompting does not work when cantata is started from the commandline. Cantata をコマンドラインから起動した際はパスワードプロンプトが正常に動作しません。 No suitable ssh-askpass application installed! This is required for entering passwords. 適切なSSH-askpass アプリケーションがインストールされていません!パスワード入力のために必要です。 Mount point ("%1") is not empty! マウントポイント ("%1") は空ではありません! "sshfs" is not installed! "sshfs" がインストールされていません! Disconnecting... 切断中... "fusermount" is not installed! "fusemount" がインストールされていません! Failed to connect to "%1" "%1" への接続に失敗しました Failed to disconnect from "%1" ”%1” からの切断に失敗しました Capacity Unknown 容量不明 Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) 検索 Check Items アイテムにチェックを入れる Uncheck Items アイテムのチェックを外す Library: ライブラリ: Device: デバイス: Loading all songs from library, please wait... ライブラリから全楽曲を読込中、お待ちください... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>ライブラリ</code>には、ライブラリにはあるが、デバイスにはない曲だけが表示されます。 同様に、<code>デバイス</code>にはデバイスにのみにある曲がリストされます。<br/><code>デバイス</code>にコピーしたい楽曲を<code>ライブラリ</code>から選択し、<code>デバイス</code>から<code>ライブラリ </code>にコピーしたい楽曲を選択してください。その後<code>同期</code>ボタンを押します。 Synchronize 同期 Device and library are in sync. デバイスとライブラリは同期中です。 Loading all songs from library, please wait...%1%... ライブラリから全楽曲を読込中です、お待ちください...%1%... Not Scanned 未スキャン (recommended) (推奨) Empty filename. ファイル名なし。 Invalid filename. (%1) 無効なファイル名。 (%1) Failed to save %1. %1 への保存に失敗しました。 Failed to delete rules file. (%1) ルールファイルの削除に失敗しました。 (%1) Invalid command. (%1) 無効なコマンドです。 (%1) Could not remove active rules link. アクティブなルールリンクの削除が出来ません。 Active rules is not a link. アクティブなルールはリンクではありません。 Could not create active rules link. アクティブルールリンクの生成に失敗しました。 Rules file, %1, does not exist. ルールファイル %1 は存在しません。 Incorrect arguments supplied. 不適切な引数が指定されました。 Unknown method called. 不明なメソッドが呼ばれました。 Unknown error 不明なエラー Start Dynamic Playlist ダイナミックプレイリストの開始 Stop Dynamic Mode ダイナミックプレイリストの停止 Dynamic Playlists ダイナミックプレイリスト Dynamically generated playlists 動的に生成されたプレイリスト - Rating: %1..%2 - レーティング: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Cantata のダイナミックモードを動作させるにはこのシステムに "perl" をインストールする必要があります。 Failed to locate rules file - %1 ルールファイルの検索に失敗しました - %1 Failed to remove previous rules file - %1 以前のルールファイルの証書に失敗しました - %1 Failed to install rules file - %1 -> %2 ルールファイルのインストールに失敗しました - %1 -> %2 Dynamizer has been terminated. ダイナマイザは停止しました。 Saving rule ルールを保存中 Deleting rule ルールを削除中 Awaiting response for previous command. (%1) 以前のコマンドに対する応答を待っています。 (%1) Failed to save %1. (%2) %1 の保存に失敗しました。 (%2) Failed to control dynamizer state. (%1) ダイナマイザの状態制御に失敗しました。 (%1) Failed to set the current dynamic rules. (%1) 現在のダイナミックルールの設定に失敗しました。 (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) 追加 Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) 編集 Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) 削除 Remote dynamizer is not running. リモートダイナマイザーは動作していません。 Are you sure you wish to remove the selected rules? This cannot be undone. 選択済みのルールを削除しますか? この操作はやり直しできません。 Remove Dynamic Rules ダイナミックルールを削除 Dynamic Rule ダイナミックルール <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>エラー</b>: '開始年' は '終了年' より小さい必要があります</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>エラー:</b> 日付範囲が大きすぎます (最大でも %1 年間以内である必要あり)</i> SimilarArtists 関連アーティスト AlbumArtist アルバムアーティスト (Exact) (一致) Dynamic Rules ダイナミックルール None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) なし About dynamic rules ダイナミックルールについて Failed to save %1 %1 の保存に失敗 A set of rules named '%1' already exists! Overwrite? '%1' という名前のルールセットは既に存在します! 上書きしますか? Overwrite Rules ルールの上書 Saving %1 %1 の保存中 Deleting... 削除中... Name 名前 Item Count アイテム数 Space Used 使用中容量 Total space used: %1 全使用容量: %1 Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata は、さまざまな情報(カバー, 歌詞, その他)をキャッシュします。 以下はCantataの現在のキャッシュ使用方法の概要です。 Covers カバー Scaled Covers スケーリングされたカバー Backdrops バックドロップ Artist Information アーティスト情報 Album Information アルバム情報 Track Information トラック情報 Stream Listings ストリームリスト Podcast Directories Podcast ディレクトリ Scrobble Tracks Scrobble トラック Delete All 全て削除 Delete all '%1' items? 全 '%1' アイテムを削除しますか? Delete Cache Items 全キャッシュアイテムを削除 Delete items from all selected categories? 全選択カテゴリからアイテムを削除しますか? Current Cover 現在のカバー CoverArt Archive カバーアート アーカイブ Image イメージ Downloading... ダウンロード中... Image (%1 x %2 %3%) Image (width x height zoom%) イメージ (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. このアーティスト向けのイメージは既に存在し、ファイルは上書不可能です。 A cover already exists for this album, and the file is not writeable. このアルバム向けのカバーは既に存在し、ファイルは上書不可能です。 '%1' Artist Image '%1' アーティストイメージ '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' アルバムカバー Failed to set cover! Could not download to temporary file! カバーの設定に失敗しました 一時ファイルへのダウンロードが出来ません! Failed to download image! イメージのダウンロードに失敗しました! Load Local Cover ローカルカバーの読込 File is already in list! ファイルは既にリストにあります! Failed to read image! イメージの読込に失敗しました! Display 表示 Failed to set cover! Could not make copy! カバーの設定に失敗しました! 複製を作成出来ません! Failed to set cover! Could not backup original! カバーの設定に失敗しました! オリジナルのバックアップが出来ません! Failed to set cover! Could not copy file to '%1'! カバーの設定に失敗しました! '%1' への複写が出来ません! Searching... 検索中... Custom Actions カスタムアクション Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) 名前: Command: コマンド: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. 上のコマンドラインでは、%f はファイルリストに置き換えられ、%d はフォルダリストに置き換えられます。 どちらも指定されていない場合、ファイルのリストがコマンドに追加されます。 c-format Add New Command 新規コマンド追加 Edit Command コマンド編集 To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Cantataに外部コマンドを呼び出させるには(たとえば、別のアプリケーションでタグを編集する)、以下のコマンドのエントリを追加します。 少なくとも1つのコマンドコマンドが定義されている場合、ライブラリ、フォルダ、プレイリストビューのコンテキストメニューに'カスタムアクション'エントリが追加されます。 Command コマンド Remove the selected commands? 選択済みコマンドを削除しますか? Open In File Manager ファイルマネージャで開く Connection Established 接続が確立しました Connection Failed 接続に失敗しました Grouped Albums グループ化されたアルバム Table テーブル Parse in Library view, and show in Folders view ライブラリビュー内で解析され、フォルダビュー内で表示 Only show in Folders view フォルダービュー内のみで表示 Do not list 表記しない Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) プレイキュー Library ライブラリ Folders フォルダ Playlists プレイリスト Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts インターネット - Streams, Jamendo, Maganatune, SoundCloud, Podcast Devices - UMS, MTP (e.g. Android), and AudioCDs デバイス - UMS, MTP(例えば Android), オーディオCD Search (via MPD) 検索(MPD経由) Info - Current song information (artist, album, and lyrics) 情報 - 現在の楽曲情報(アーティスト, アルバム, 歌詞) Large Small Tab-bar タブバー Left Right Top Bottom Notifications 通知 System default システム規定値 Show Artist Images アーティストイメージの表示 Sort Albums アルバムのソート Modified Date 変更日時 Group By グループ化 Configure Cantata... Cantata の設定... Preferences 設定 Quit 終了 About Cantata... Qt-only Cantata について... Show Window ウィンドウを表示 Server information... サーバー情報... Refresh Database データベースの更新 Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) 接続 Collection コネクション Outputs 出力先 Stop After Track トラック再生後停止 Add To Stored Playlist 格納済プレイリストに追加 Crop Others その他の切取 Add Stream URL ストリーム URLの追加 Clear クリア Expanded Interface 拡張インターフェース Show Current Song Information 現再生中の楽曲情報を表示 Full Screen フルスクリーン Random ランダム Repeat リピート Single シングル When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. ’シングル' 有効時、プレイバックは現在の楽曲再生後停止するか、'リピート'設定が有効な場合は現楽曲が繰り返し再生されます。 Consume コンシューム When consume is activated, a song is removed from the play queue after it has been played. コンシュームが有効な際、現在の楽曲再生が完了した際に楽曲がプレイキューから削除されます。 Find in Play Queue プレイキュー内で捜索 Play Stream 再生ストリーム Locate In Library ライブラリ内での捜索 Expand All すべて拡張する Collapse All すべて折りたたむ Internet インターネット Devices デバイス Info 情報 Show Menubar メニューバーの表示 &Music ミュージック(&M) &Edit 編集(&E) &View ビュー(&V) &Queue キュー(&Q) &Settings 設定(&S) &Help ヘルプ(&H) Set Rating レーティングの設定 No Rating レーティングなし Failed to locate any songs matching the dynamic playlist rules. ダイナミックプレイリストルールに一致する楽曲が捜索出来ませんでした。 Connecting to %1 %1 に接続中 Refresh MPD Database? MPD データベースを更新しますか? About Cantata Qt-only Cantata について <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Qt-only <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only コンテキストビューの背景 <a href="http://fanart.tv">FanArt.tv</a> の協賛 Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only コンテキストビューのメータデータは<a href="http://www.wikipedia.org">Wikipedia</a> と <a href="http://www.last.fm">Last.fm</a> の協賛 Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> あなたのミュージックファンアートを<a href="http://fanart.tv">FanArt.tv</a>にアップロードしてください A Podcast is currently being downloaded Quiting now will abort the download. Podcastは現在ダウンロード中です 終了すると、ダウンロードが中止されます。 Abort download and quit ダウンロードを中止し終了する Enabled: %1 有効化済: %1 Disabled: %1 無効化済: %1 Server Information サーバー情報 <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>サーバー</b></td></tr><tr><td align="right">プロトコル:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">稼働時間:&nbsp;</td><td>%4</td></tr><tr><td align="right">再生中:&nbsp;</td><td>%5</td></tr><tr><td align="right">ハンドラ:&nbsp;</td><td>%6</td></tr><tr><td align="right">タグ:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>データベース</b></td></tr><tr><td align="right">アーティスト:&nbsp;</td><td>%1</td></tr><tr><td align="right">アルバム:&nbsp;</td><td>%2</td></tr><tr><td align="right">楽曲:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPDが以下のエラーを返しました: %1 Cantata Canata Playback stopped 再生が停止しました Remove all songs from play queue? プレイキューからすべての楽曲を削除しますか? Priority プライオリティ Enter priority (0..255): プライオリティを入力 (0..255): Playlist Name プレイリスト名 Enter a name for the playlist: プレイリスト名を入力: '%1' is used to store favorite streams, please choose another name. '%1' はお気に入りのストリームを保存するために使用されています、他の名前を選択してください。 A playlist named '%1' already exists! Add to that playlist? '%1' という名前のプレイリストは既に存在します プレイリストに追加しますか? Existing Playlist 存在するプレイリスト Auto 自動 <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>%1 に接続済<br/>以下のエントリは、現在接続中のMPDコレクションに適用されます。</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>未接続!<br/>以下のエントリはCantataがMPDに接続されていないため、変更できません。</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gainは、MP3やOgg Vorbisなどのコンピュータオーディオフォーマットで知覚される音量を標準化するために2001年に発表され提案された標準規格です。これはトラック/アルバムベースで動作し、現在、ますます多くのプレーヤーでサポートされています。<br/><br/>以下の通り、リプレイゲイン設定が使用されます:<ul><li><i>なし</i> - リプレイゲインが適用されません。</li><li><i>トラック</i> - 各楽曲のReplayGainタグ情報でボリウムが調整されます。</li><li><i>アルバム</i> - アルバムのReplayGainタグ情報でボリウムが調整されます。</li><li><i>自動</i> - ランダムプレイが有効な場合は楽曲のReplayGainタグを使用してボリウムが調整され、それ以外の場合はアルバムのタグが使用されます。</li></ul> Rename 名前を変更 Remove Duplicates 複製を削除 Initially Collapse Albums 初期動作時はアルバムをたたむ Are you sure you wish to remove the selected playlists? This cannot be undone. 選択したプレイリストを削除しますか? この操作はやり直しできません。 Remove Playlists プレイリストの削除 A playlist named '%1' already exists! Overwrite? '%1' というプレイリストは既に存在します 上書しますか? Overwrite Playlist プレイリストの上書 Rename Playlist プレイリストの名前変更 Enter new name for playlist: プレイリストの新しい名前を入力: Cannot add songs from '%1' to '%2' '%1' から '%2' へ楽曲の追加が出来ません 1 Track %1 トラック %1 Tracks 1 Track (%2) %1 トラック (%2) %1 Tracks (%2) 1 Album %1 アルバム %1 Albums 1 Artist %1 アーティスト %1 Artists 1 Stream %1 ストリーム %1 Streams 1 Entry %1 エントリ %1 Entries 1 Rule %1 ルール %1 Rules 1 Podcast %1 Podcast %1 Podcasts 1 Episode %1 エピソード %1 Episodes 1 Update available %1 アップデートあり %1 Updates available Collection Settings コレクションの設定 Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) 再生 Playback Settings 再生設定 Downloaded Files ダウンロード済ファイル Downloaded Files Settings ダウンロード済ファイルの設定 Interface インタフェース Interface Settings インタフェースの設定 Info View Settings 情報ビューの設定 Scrobbling Scrobbing Scrobbling Settings Scrobbing 設定 Audio CD Settings Audio CD 設定 Proxy プロキシ Proxy Settings Qt-only プロキシ設定 Shortcuts Qt-only ショートカット Keyboard Shortcut Settings Qt-only キーボード ショートカット 設定 Cache キャッシュ Cached Items キャッシュ済アイテム Cantata Preferences Cantata の設定 Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) 設定 Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) 作曲家: Performer: 演奏者: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) ジャンル: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) コメント: Date: 日付: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. ’Date’ タグで楽曲を検索します<br/><br/>通常、年を入力するだけで十分です。 Modified: 変更済: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. ファイルが変更された日付(YYYY/MM/DD - 例 2015/01/31)。<br/><br>または、ファイルの修正されてからの経過日数を確認するためは日数指定して下さい。 Any: 全検索: No tracks found. トラックが見つかりません。 Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) ホスト: Which type of collection do you wish to connect to? どのタイプのコレクションに接続しますか? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) 標準 - ミュージックコレクションが共有されている、他のマシーンにあり既にセットアップ済、もしくは他のクライアントからアクセスを許可したい場合(例 MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. 基本 - ミュージックコレクションが他の端末と共有されておらず、Cantata はMPDインスタンスを設定し制御します。本セットアップは Cantata でのみ適用され、他のMPDクライアントはアクセス<b>出来ません</b>。 If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' i18n: file: gui/initialsettingswizard.ui:236 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel_2) 高度なMPD設定(複数のオーディオ出力、完全なDSDサポートなど)を希望する場合、'標準'を選択する<b>必要があります</b> <i><b>NOTE:</b> %1</i> <i><b>追記:</b> %1</i> Add Collection コレクションの追加 Standard 標準 Basic 基本 Delete '%1'? ’%1’ を削除しますか? Delete 削除 New Collection %1 新規コレクション %1 Default 規定 Previous Track 前のトラック Next Track 次のトラック Play/Pause 再生/一時停止 Stop After Current Track 現在トラック再生後停止 Increase Volume 音量Up Decrease Volume 音量Down Save As 別名で保存 Append 追加 Append To Play Queue プレイキューの末尾に追加 Append And Play 末尾に追加し再生 Add And Play 追加し再生 Append To Play Queue And Play プレイキューの末尾に追加し再生 Insert After Current 現楽曲の直後に挿入 Append Random Album ランダムアルバムの末尾に追加 Play Now (And Replace Play Queue) 直ちに再生(し、プレイキューを更新) Add With Priority プライオリティ付きで追加 Set Priority プライオリティの設定 Highest Priority (255) 最高位のプライオリティ(255) High Priority (200) 高プライオリティ(200) Medium Priority (125) 中プライオリティ(125) Low Priority (50) 低プライオリティ(50) Default Priority (0) 規定のプライオリティ(0) Custom Priority... カスタム プライオリティ... Add To Playlist プレイリストに追加 Organize Files ファイルの整理 Edit Track Information トラック情報の編集 Set Image イメージの設定 Find 検索 Add To Play Queue プレイキューへの追加 Now playing 現再生中 Play 再生 Pause 一時停止 Cue Sheet キューシート Playlist プレイリスト Configure Device デバイスの設定 Refresh Device デバイスの更新 Connect Device デバイスに接続 Disconnect Device デバイスからの切断 Edit CD Details CDの詳細を編集 No Devices Attached 接続済デバイスがありません Not logged in ログインなし Logged in ログイン済 No subscriptions サブスクリプションなし You do not have an active subscription アクティブなサブスクリプションがありません Logged in (expiry:%1) ログイン済 (有効期限:%1) Session expired セッションが期限切れです Parse error loading cache file, please check your songs tags. キャッシュファイル読込中の解析エラーです。楽曲タグを確認してください。 %1 by %2 Album by Artist %1 by %2 New Playlist... 新規プレイリスト... Stored Playlists 保存済プレイリスト Standard playlists 標準プレイリスト Smart Playlist スマートプレイリスト Length 時間 Disc ディスク Rating レーティング Undo 元に戻す Redo やり直し Shuffle シャッフル Sort By ソートする Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) アルバムアーティスト Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) トラック名 # (Track Number) # (トラック番号) TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search ストリーム検索 Search for radio streams ラジオストリームの検索 Enter string to search 検索文字列を入力してください Not Loaded 未読込 Loading... 読込中... Bookmarks ブックマーク IceCast IceCast Favorites お気に入り Bookmark Category ブックマーク カテゴリ Add Stream To Favorites ストリームをお気に入りに追加 Configure Digitally Imported デジタルでのインポートを設定 Streams ストリーム Radio stations ラジオステーション Unknown 不明 Connection to %1 failed %1 への接続に失敗しました Connection to %1 failed - please check your proxy settings %1 への接続に失敗しました - プロキシ設定を確認ください Connection to %1 failed - incorrect password %1 への接続に失敗しました - 無効なパスワードです Failed to send command to %1 - not connected %1 へのコマンド送信に失敗しました - 未接続です Failed to load. Please check user "mpd" has read permission. 読込に失敗しました。ユーザ "mpd" に読込許可があるか確認してください。 Failed to load. MPD can only play local files if connected via a local socket. 読込に失敗しました。ローカルソケット経由で接続の際、 MPD はローカルファイルのみ再生可能です。 Failed to send command. Disconnected from %1 コマンド送信に失敗しました。%1 から切断されました Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b> から <b>%2</b> への名前変更に失敗 Failed to save <b>%1</b> <b>%1</b> への保存に失敗しました You cannot add parts of a cue sheet to a playlist! キューシートのパーツをプレイリストへ追加が出来ません! You cannot add a playlist to another playlist! 他のプレイリストへのプレイリストの追加が出来ません! Failed to send '%1' to %2. Please check %2 is registered with MPD. '%1'から’%2’への送信に失敗しました。%2がMPDに登録されているか確認してください。 Cannot store ratings, as the 'sticker' MPD command is not supported. 'sticker' MPD コマンドが未サポートなため、レーティングが格納できません。 Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) シングルトラック Personal パーソナル Various Artists Various Artists <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> on <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> by <b>%2</b> on <b>%3</b> No proxy プロキシなし Use the system proxy settings システムプロキシ設定を使用 Manual proxy configuration 手動プロキシ設定 The world's largest digital service for free music 世界最大のフリーミュージック向けディジタルサービス Jamendo Settings Jamendo 設定 MP3 MP3 Ogg Ogg Streaming format: ストリーム形式: Streaming ストリーミング MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Online music from magnatune.com magnatne.com 経由のオンラインミュージック Magnatune Settings Magnatune 設定 Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) ユーザ名: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) パスワード: Membership: メンバーシップ: Downloads: ダウンロード: Failed to parse 解析に失敗 Downloading...%1% ダウンロード中...%1 % Parsing music list.... 楽曲リストの解析中... Failed to download ダウンロード失敗 The music listing needs to be downloaded, this can consume over %1Mb of disk space 音楽リストをダウンロードする必要があります。これは%1Mb以上のディスク容量を消費する可能性があります Dowload music listing? 音楽リストをダウンロードしますか? Re-download music listing? ミュージックリストを再ダウンロードしますか? RSS: RSS: Website: Webサイト: Podcast details Podcast 詳細 Select a podcast to display its details 詳細を表示する podcast を選択してください Enter search term... 検索文字列を入力... Failed to fetch podcasts from %1 %1 からの podcast の取得に失敗 There was a problem parsing the response from %1 %1 からのレスポンスの解析に問題が発生しました Failed to download directory listing ディレクトリリストのダウンロードに失敗しました Failed to parse directory listing ディレクトリリストの解析に失敗しました URL URL Enter podcast URL... podcast URL を入力... Load 読込 Enter podcast URL below, and press 'Load' podcast URL を以下で入力し ’読込’ を押してください Invalid URL! 無効なURL! Failed to fetch podcast! podcast の取得に失敗しました! Failed to parse podcast. podcast の解析に失敗しました。 Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata は音声 podcast のみサポートします! 入力された URL はビデオ potcast のみです。 Subscribe 購読する Enter URL URL を入力 Manual podcast URL マニュアル podcast URL Search %1 %1 を検索 Search for podcasts on %1 %1 の podcast を検索 Add Podcast Subscription Podcast の購読を追加する Browse %1 %1 をブラウズ Browse %1 podcasts %1 podcast をブラウズ You are already subscribed to this podcast! 既にこの podcast を購読済みです! Subscription added 購読を追加 Subscribe to RSS feeds RSS フィードを購読 (Downloading: %1%) (ダウンロード中: %1%) Failed to parse %1 %1 の解析に失敗 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata は音声 podcast のみサポートします! %1 はビデオ potcast のみです。 Failed to download %1 %1 のダウンロードに失敗しました Check for new episodes: 新規エピソードをチェック: Download episodes to: エピソードのダウンロード先: Download automatically: 自動的にダウンロード: Podcast Settings Podcast 設定 Manually 手動 Every 15 minutes 15分毎 Every 30 minutes 30分毎 Every hour 1時間毎 Every 2 hours 2時間毎 Every 6 hours 6時間毎 Every 12 hours 12時間毎 Every day 毎日 Every week 毎週 Don't automatically download episodes エピソードの自動ダウンロどーを行わない Latest episode 最新のエピソード Latest %1 episodes 最新の %1 エピソード All episodes 全エピソード Add Subscription 購読を追加 Remove Subscription 購読の削除 Download Episodes エピソードのダウンロード Delete Downloaded Episodes ダウンロード済エピソードの削除 Cancel Download ダウンロードのキャンセル Mark Episodes As New エピソードを新規としてマーキング Mark Episodes As Listened エピソードを視聴済みとしてマーキング Unsubscribe from '%1'? '%1' から購読削除しますか? Do you wish to download the selected podcast episodes? 選択した podcast エピソードをダウンロードしますか? Cancel podcast episode downloads (both current and any that are queued)? ポッドキャストのエピソードのダウンロードをキャンセルしますか(現在のものとキュー内の双方)? Do you wish to the delete downloaded files of the selected podcast episodes? 選択済 podcast エピソードのダウンロード済みファイルを削除しますか? Do you wish to mark the selected podcast episodes as new? 選択済 podcast エピソードを新規としてマーキングしますか? Do you wish to mark the selected podcast episodes as listened? 選択済 podcast エピソードを視聴済みとしてマーキングしますか? Refresh all subscriptions? 全ての購読を更新しますか? Refresh All 全て更新 Refresh all subscriptions, or only those selected? 全ての購読もしくは選択済みを更新しますか? Refresh Selected 選択済みを更新 Search for tracks from soundcloud.com soundcloud.com からのトラックを検索 Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) 背景イメージ Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) アーティスト イメージ Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) カスタム イメージ: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) ブラー: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) 透明度: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) 次の後に自動的に切替する: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) 自動的にスイッチしない ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) 暗い背景 Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) 暗い背景で、現在のカラーパレットに関わらず文字列を白色にします。 Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) 常にシングルペインに変更する Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) 3つすべてを表示するのに十分な幅がある場合でも、'アーティスト','アルバム','トラック'のいずれかを表示する。 Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) 基本的な wikipedia テキストのみ表示する Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) Cantataは、ウィキペディアのページの縮小されたバージョンのみを表示します(イメージ、リンクなどはありません)。このトリミングは必ずしも 100% 正確であるとは限りません。そのため、Cantataはデフォルトでイントロダクションのみ表示します。全ての記事を表示すると、解析エラーが発生する可能性があります。また、( 'キャッシュ'ページを使用して)現在キャッシュされている記事を削除する必要があります。 no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) 利用可能: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) 選択済: Calculating size of files to be copied, please wait... i18n: file: devices/actiondialog.ui:86 i18n: ectx: property (text), widget (QLabel, fileS) 複写するファイルのサイズを計算中です、お待ちください... Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) 楽曲の複写元: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (設定が必要) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) 楽曲の複写先: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) 相手先の形式: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) 楽曲の上書 To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) 複写先: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) アルバムの詳細 Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) 年: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) ディスク: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) シングル アーティスト Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) アルバムとトラック情報を検索 Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) 最初に以下を参照: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) CDDB ホスト: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) CDDB ポート: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) CDが挿入された際直ちに情報を検査する Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) オーディオ抽出 Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) フル パラノイアモード (最高の品質) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) 読取エラーをスキップしない These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) これらの設定は、デバイスが接続されている場合にのみ有効で編集可能です。 Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) 音楽フォルダ: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) アルバムカバーを複写: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) 最大カバーサイズ: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) 規定のボリウム: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) 'Various Artists'の回避策 Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) 接続された際に自動的に音楽をスキャンする Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) キャッシュを使用する Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) ファイル名 Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) ファイル名体系: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) VFAT セーフ Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) ASCII文字のみを使用する Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) スペースをアンダーバーに置換する Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) アーティスト名に「The」を付ける If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' i18n: file: devices/devicepropertieswidget.ui:195 i18n: ectx: property (toolTip), widget (QCheckBox, ignoreThe) アーティスト名が 'The'で始まる場合は、フォルダ名の先頭に追加します。 例えば 「The Beatles」は「Beatles, The」になります Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) コード変換中 Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) ソースファイルが異なる書式の場合のみコード変換する Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) 例: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) ファイル名体系について The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) アルバムのアーティストのこと。大半のアルバムではこれは<i>トラック アーティスト</i>と同じであり、コンピレーションアルバムではしばしば<i>Various Artists</i>になります。 The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) アルバム名を指す。 Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) アルバム名 The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) 作曲家を指す。 The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) 各トラックのアーティストを指す。 Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) トラックアーティスト The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) トラック名 (<i>トラック アーティスト</i>を含まない). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) トラック名 (<i>アルバムアーティスト</i>と異なる場合のみ<i>トラックアーティストを含む</i>)。 Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) トラック名 (+アーティスト) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) トラック番号を指す。 Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Track # The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) 複数のディスクを持つアルバムのアルバム番号を指す。多くの場合、コンピレーションアルバムは複数のディスクで構成されています。 CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) アルバムのリリース年を指す。 The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) アルバムのジャンルを指す。 These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) これらの設定はデバイスが未接続時のみ編集可能です。 Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) タイプ: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) オプション Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) ポート: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) ユーザー: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) ドメイン: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) シェア: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) パスワードをここに入力した場合、Cantata のコンフィグファイル内に<b>未暗号化状態</b>で保存されます。共有にアクセスする前にカンタータのパスワードプロンプトを表示するには、パスワードを ' - 'に設定します Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) サービス名: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) フォルダ: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) 追加オプション: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) sshfsの仕組みのため、適切なssh-askpassアプリケーション(ksshaskpass、ssh-askpass-gnomeなど)がパスワードを入力する必要があります。 This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) このダイアログは、リモートデバイスを(Samba経由などで)追加する場合や、ローカルにマウントされたフォルダにアクセスする場合にのみ使用します。 USBを介して接続された通常のメディアプレーヤーの場合、Cantataはデバイスが接続されると自動的にデバイスを表示します。 Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) ダイナミックルールの名称 - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) ルールについて Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) 次のものと一致する曲を含む: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) 次のものと一致しない曲を含む: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) 同様のアーティスト: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) アルバムアーティスト: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) 開始年: To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) 終了年: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) 完全に一致 Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) 検索したいタグの値のみを入力してください。 For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) ジャンルについては、さまざまなジャンルに一致するようにアスタリスクで終了文字列を指定します。 例えば 'rock *'は 'Hard Rock'と 'Rock and Roll'に一致します。 Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) ローカルファイルを追加 Save downloaded covers, artist, and composer images, in music folder i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) ダウンロード済のカバー, アーティスト, 作曲家のイメージを音楽フォルダに保存する Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) ダウンロード済みの歌詞を音楽フォルダに保存する Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) ダウンロード済みのバックドロップを音楽フォルダに保存する If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) 音楽フォルダ内にCantataのカバー、歌詞、または背景を保存することを選択した場合、このフォルダへの書き込みアクセス権がない場合、Cantataはファイルを自分のキャッシュフォルダに保存します。 Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) Cantataは、2レベルの深さまでならば、音楽フォルダ階層内の背景、アーティスト、および作曲家の画像のみを保存できます。 すなわち「Artist / Album / Tracks」と表示されます。 Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Cantataの最初の実行 Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) ようこそ Cantata へ <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>Cantataは、Music Player Daemon (MPD)の機能豊富でユーザーフレンドリーなクライアントです。MPDは、音楽を演奏するための柔軟で強力なサーバーサイドのアプリケーションです。</p><p>MPD自体の詳細については、MPDのWebサイト<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>を参照ください。</p><p>この「ウィザード」は、Cantataが正しく機能するために必要な基本設定をガイドします。</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Cantata へようこそ</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> i18n: file: gui/initialsettingswizard.ui:134 i18n: ectx: property (text), widget (QLabel, label_8) <p>Cantataは、Music Player Daemon (MPD)の機能豊富でユーザーフレンドリーなクライアントです。MPDは、音楽を演奏するための柔軟で強力なサーバーサイドのアプリケーションです。MPD はシステムワイド、ユーザ単位で起動することが可能です。<br/><br/>Cantataに最初にMPDに接続する(または起動する)方法を選択してください:</p> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) 標準マルチユーザ/サーバー セットアップ <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> i18n: file: gui/initialsettingswizard.ui:172 i18n: ectx: property (text), widget (BuddyLabel, label_10) <i>音楽コレクションがユーザー間で共有されている場合、MPDインスタンスが別のマシンで実行中の場合、個人MPD設定が既にある場合、または他のクライアント(MPDroidなど)からのアクセスを有効にする場合はこのオプションを選択します。このオプションを選択すると、Cantata自体がMPDサーバーの起動と停止を制御できなくなります。MPDが既に設定され、実行されていることを確認する必要があります。</i> Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) 基本シングルユーザセットアップ <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> i18n: file: gui/initialsettingswizard.ui:217 i18n: ectx: property (text), widget (BuddyLabel, label_9) <i>あなたの音楽コレクションが他の人と共有されておらず、CantataがMPDインスタンスを設定して制御したい場合は、このオプションを選択します。この設定はCantata専用で、他のMPDクライアント(MPDroidなど)からアクセス<b>出来ない</b>ようになります</i> For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. i18n: file: gui/initialsettingswizard.ui:259 i18n: ectx: property (text), widget (QLabel, label_11) MPD自体の詳細については、MPDのWebサイト<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>を参照ください。<br/><br/>この「ウィザード」は、Cantataが正しく機能するために必要な基本設定をガイドします。 Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) 接続の詳細 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) 以下の設定はCantataが必要な基本設定です。関連する詳細を入力し、[接続]ボタンを使用して接続をテストしてください。 The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) 「音楽フォルダ」設定は、カバーアート、歌詞などを検索するために使用されます。MPDインスタンスがリモートホスト上にある場合、これをHTTP URLに設定することができます。 Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) 音楽フォルダ Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) あなたの音楽コレクションを含むフォルダを選択してください。 Covers and Lyrics i18n: file: gui/initialsettingswizard.ui:620 i18n: ectx: property (text), widget (QLabel, label_6f) カバーと歌詞 <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata はインターネットから不明なカバーや歌詞をダウンロードします。</p><p>これらそれぞれについて、Cantataに関連するファイルをミュージックフォルダ内に保存するか、個人のキャッシュ/設定フォルダ内に保存するかを確認してください。</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) 'Music folder'はHTTPアドレスに設定されており、Cantataは現在、外部HTTPサーバーにファイルをアップロードできません。したがって、上記の設定は未チェックのままにしておく必要があります。 Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) 完了! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata が設定されました!<br/><br/>Cantataの設定ダイアログは、Cantataの外観をカスタマイズするために使用されるほか、追加のMPDホストなどを追加するために使用されます。 Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) Cantataは 'AlbumArtist'タグが設定されている場合はトラックをアルバムにグループ化し、そうでない場合は 'Artist'タグにフォールバックします。複数のアーティストを含むアルバムをお持ちの場合は、グループ化が正しく機能するように 'AlbumArtist'タグを<b>設定</b>する必要があります。このシナリオでは、「Various Artists」を使用することをお勧めします。 <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>注意:</b> お使いのユーザアカウントは 'users' グループのメンバーではありません. Cantata はあなた(もしくは管理者が)ユーザアカウントをこのグループに追加することにより より良く動作します (アルバムカバー, 歌詞, その他の保存が正しい許可で行われる)。グループに使用中のアカウントを追加した場合は一旦ログアウト ログインを行ってアプリケーションを再起動することでこの機能が動作します。 Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) サイドバー Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) ビュー Use the checkboxes below to configure which views will appear in the sidebar. i18n: file: gui/interfacesettings.ui:48 i18n: ectx: property (text), widget (QLabel, label_2) 下のチェックボックスを使用して、サイドバーに表示するビューを設定します。 If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. i18n: file: gui/interfacesettings.ui:61 i18n: ectx: property (text), widget (NoteLabel, sbPlayQueueLabel) 上記のチェックボックスをオンにしないと、他のビューの横に表示されます。上記の[情報]がチェックされていない場合、曲情報にアクセスするためのボタンがツールバーに追加されます。 Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) スタイル: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) ポジション: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) アイコンのみ表示しテキストは非表示にする Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) 自動的に隠す Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) 初期動作時はアルバムをたたむ Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) 現在のアルバムを自動的に拡大する Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) 現在のトラックまでスクロールする Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) クリア作業実施前に警告する Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) プレイキュー検索のためのセパレートアクション (とショートカット) Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) 現在のアルバムカバー Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) ツールバー Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) 停止ボタンを表示 Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) 現トラックのカバーを表示 Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) トラックのレーティングを表示 External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) 外部 Enable MPRIS D-BUS interface i18n: file: gui/interfacesettings.ui:404 i18n: ectx: property (text), widget (QCheckBox, enableMpris) MPRIS D-BUS インタフェースを有効化 Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) トラック変更時にポップアップメッセージを表示 Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) 通知エリアにアイコンを表示 Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) 閉じた際に通知エリアを最小化する On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) 起動時 Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) メインウィンドウを表示 Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) メインウィンドウを隠す Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) 以前の状態を復元する Tweaks i18n: file: gui/interfacesettings.ui:482 i18n: ectx: attribute (title), widget (QWidget, tab_4z) 微調整 Artist && Album Sorting i18n: file: gui/interfacesettings.ui:488 i18n: ectx: property (title), widget (QGroupBox, groupBox_2p) アーティスト && アルバムソート Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' i18n: file: gui/interfacesettings.ui:494 i18n: ectx: property (text), widget (QLabel, labelp) アーティストとアルバムを並べ替えるときに無視するプレフィックスのリスト(カンマ区切り)を入力します。例えば 'The'に設定した場合、 'The Beatles'は 'Beatles'によってソートされます Enter comma separated list of prefixes... i18n: file: gui/interfacesettings.ui:504 i18n: ectx: property (placeholderText), widget (LineEdit, ignorePrefixes) コンマ区切りのプレフィックスのリストを入力してください... Composer Support i18n: file: gui/interfacesettings.ui:514 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) 作曲家のサポート By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. i18n: file: gui/interfacesettings.ui:520 i18n: ectx: property (text), widget (QLabel, label) 既定では、Cantataは曲とアルバムをグループ化するために 'Album Artist'タグ('Album Artist'がない場合は'Artist'タグ)を使用します。'Classical'などの特定のジャンルについては、このグループ化を実行するために'Composer'タグ(設定されている場合)を使用することが好ましい場合があります。Cantataに 'Composer'タグを使用させたいジャンルの(カンマ区切りの)リストを入力してください。 Enter comma separated list of genres... i18n: file: gui/interfacesettings.ui:530 i18n: ectx: property (placeholderText), widget (LineEdit, composerGenres) コンマ区切りのジャンルのリストを入力してください... If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' i18n: file: gui/interfacesettings.ui:546 i18n: ectx: property (text), widget (QLabel, label_3) コレクションに単一のトラックしか含まれていないアーティストがたくさんいる場合は、それぞれのアーティストがアーティストのリストに独自のエントリを持つのは面倒です。これを回避するには、これらのトラックを別のフォルダに配置し、このフォルダ名を下に入力すると、Cantataはアルバムアーティストの「Various Artists」で「Single Tracks」という名前のアルバムをグループ化します Folder that contains single track files... i18n: file: gui/interfacesettings.ui:556 i18n: ectx: property (placeholderText), widget (LineEdit, singleTracksFolders) シングルトラックファイルを含むフォルダ名を入力してください... CUE Files i18n: file: gui/interfacesettings.ui:566 i18n: ectx: property (title), widget (QGroupBox, groupBox_3xx) CUE ファイル A cue file is a metadata file which describes how the tracks of a CD are laid out. i18n: file: gui/interfacesettings.ui:572 i18n: ectx: property (text), widget (QLabel, label_3x) キューファイルは、CDのトラックがどのようにレイアウトされているかを記述するメタデータファイルです。 Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. i18n: file: gui/interfacesettings.ui:588 i18n: ectx: property (text), widget (NoteLabel, tweaksLabel) 上記のいずれかを変更するには、影響を受けるためにDBリフレッシュが必要です(場合によってはCantataを再起動する必要があります)。 General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) 一般設定 Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) 未発見のカバーを Last.fm から取得する Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) コンテキストメニュー内に削除を表示 Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) シングルクリックでのアイテムのアクティブ化を強制する <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>これにより、Cantataのインターフェースが以下の通り変更されます: <ul><li>再生、コントロールボタンが33%広くなります</li><li>ビューは 'flickable'になります</li><li>項目をドラッグするには、左上隅を 'タッチ'する必要があります。</li><li>スクロールバーの幅は数ピクセルになります。</li><li>アクション(「プレイキューに追加」など)は常に表示されます(アイテムがマウスの下にあるときだけでなく)。</li><li>スピンボタンがテキストフィールドの横に+ボタンと - ボタンと追加されます。</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) タッチ操作に適したインターフェースに変更 Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) ツールチップに楽曲情報を表示 Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) 高解像度ディスプレへの対応 Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) 言語: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) 「シングルクリックでのアイテムのアクティブ化を強制する」設定を変更するには、Cantataを再起動する必要があります。 Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) 言語設定の変更時はCantataを再起動する必要があります。 Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) 「タッチフレンドリー」の設定を変更するには、Cantata の再起動が必要です。 Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) 高解像度ディスプレイのサポートを有効にすると、高解像度ディスプレイ上でより鮮明なアイコンが生成されるが、通常解像度のディスプレイ上ではあまり鮮明でないアイコンを生成することがあります。この設定を変更するには、Cantataを再起動する必要があります。 [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [ダイナミック] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) フルスクリーンから戻る Fa&deout on stop: i18n: file: gui/playbacksettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, label_6b) 停止時にフェードアウトさせる(&D): Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) 終了時に再生を停止する Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) 再生時にサスペンドを禁止する If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) 停止ボタンを押し続けると、現在再生中のトラックを停止するか、現在のトラックの後に停止するかを選択できるメニューが表示されます。(停止ボタンは、インターフェース / ツールバー セクションで有効にすることができます) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) 出力 &Crossfade between tracks: i18n: file: gui/playbacksettings.ui:112 i18n: ectx: property (text), widget (BuddyLabel, crossfadingLabel) トラック間のクロスフェード(&C): s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s Replay &gain: i18n: file: gui/playbacksettings.ui:135 i18n: ectx: property (text), widget (BuddyLabel, replayGainLabel) リプレイゲイン(&G): About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) replay gain について Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) 下のチェックボックスを使用して、アクティブ出力を制御します。 Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) コレクション: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) カバーファイル名: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>ダウンロードしたカバーを保存するためのファイル名(拡張子なし)。<br/>空白のままにしておくと、「cover」が使用されます。<br/><br/><i>%artist% は現在の曲のアルバムアーティストに置き換えられ、%album% はアルバム名に置き換えられます。</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) HTTP ストリーム URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. i18n: file: gui/serversettings.ui:171 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) 「音楽フォルダ」の設定は、カバーアートを検索するために使用されます。MPDが別のマシンにあり、HTTPを介してカバーにアクセスできる場合は、HTTP URLに設定されます。それがHTTP URLに設定されておらず、このフォルダ(およびそのサブフォルダ)への書き込み権限もある場合、Cantataはダウンロードしたすべてのカバーをそれぞれのアルバムフォルダに保存します。 If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) 'カバー ファイル名'を特定しない場合、Cantata は規定値の<code>cover</code> を使用する 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) 「HTTP Stream URL」は、HTTPストリームに出力するようにMPDが設定されていて、Cantataがそのストリームを再生できるようにしたい場合にのみ使用します。 If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) 「ミュージックフォルダ」の設定を変更した場合、音楽データベースを手動で更新する必要があります。これは、 'アーティスト' または 'アルバム' ビューの 'データベースの更新' ボタンを押すことで実行できます。 Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) モード: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP プロキシ SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS プロキシ Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) 以下のチェックボックスを使用し、アクティブなサービスのリストを設定します。 Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) サービスの設定 Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Scrobble 使用: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) ステータス: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) ログイン Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Scrobble トラック Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) 'Love' ボタンを表示する You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) アカウントなしで無料で聴くことができますが、プレミアムメンバーは広告なしで高品質のストリームを聴くことができます。プレミアムアカウントにアップグレードするには、<a href="http://www.di.fm/premium/"> http://www.di.fm/premium/ </a>をご覧ください。 Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) プレミアムアカウント Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) ストリームタイプ: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) セッションの有効期限: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) これらの設定はDigitally Imported, JazzRadio.com, RockRadio.com, Sky.fm で有効です If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) アカウントの詳細を入力すると、ストリームのリストの下に「DI」ステータスアイテムが表示されます。 これはあなたがログインしているかどうかを示します。 Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) アクティブなプロバイダのリストを設定するには、以下のチェックボックスを使用します。 Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) 組み込みのカテゴリはイタリック体で表示され、削除することはできません。 Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) 検索: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) 選択アクションへのショートカット Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) 既定: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) カスタム: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) アルバムアーティスト: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) トラック番号: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) ディスク番号: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) レーティング: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(Various)</i> Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') i18n: file: tags/tageditor.ui:210 i18n: ectx: property (text), widget (PlainNoteLabel, label_7x) 複数のジャンルはコンマで区切る必要があります(例: 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) 評価は外部データベースに保存され、曲のファイルには格納され<b>ません</b>。 Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) オリジナル名称 New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) 新規名称 Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) ファイル名が変更された際にレーティングは失われます。 Your names NAME OF TRANSLATORS Sunatomo Masuda Your emails EMAIL OF TRANSLATORS sunatomo@gmail.com Show All Tracks 全トラックの表示 Show Untagged Tracks タグ付けされていないトラックの表示 Remove From List リストから削除 Album Gain アルバムゲイン Track Gain トラックゲイン Album Peak アルバムピーク Track Peak トラックピーク Scan スキャン Update ReplayGain tags in tracks? トラック内のReplayGainタグを更新しますか? Update Tags タグの更新 Abort scanning of tracks? トラックのスキャンを中止しますか? Abort 中止 Abort reading of existing tags? 存在するタグの読込を中止しますか? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> <b>全</b>トラックをスキャンしますか?<br/><br/><i>すべてのタグにはReplayGainタグがあります。</i> Do you wish to scan all tracks, or only tracks without existing tags? すべてのトラックをスキャンしますか、それともタグなしトラックをスキャンしますか? Untagged Tracks 未タグ付けトラック All Tracks すべてのトラック Scanning tracks... トラックのスキャン中... Reading existing tags... 存在するタグを読み込み中... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (破損タグ?) Failed to update the tags of the following tracks: 以下のトラックのタグ更新に失敗しました: Device is not connected. デバイスが未接続です。 %1 dB %1 dB Failed 失敗 Original: %1 dB オリジナル: %1 dB Original: %1 オリジナル: %1 Remove the selected tracks from the list? リストから選択済トラックを削除しますか? Remove Tracks トラックの削除 Invalid service 無効なサービス Invalid method 無効なメソッド Authentication failed 認証に失敗 Invalid format 無効な書式 Invalid parameters 無効なパラメータ Invalid resource specified 無効なリソースが指定されました Operation failed 操作に失敗しました Invalid session key 無効なセッションキー Invalid API key 無効なAPIキー Service offline サービスがオフライン Last.fm is currently busy, please try again in a few minutes Last.fm は現在ビジー状態です。数分後に再試行してください Rate-limit exceeded レート制限を超えました %1 error: %2 %1 エラー: %2 %1: Loved Current Track %1: 現トラックをラブ設定しました %1: Love Current Track %1: 現トラックをラブ設定する %1 (via MPD) scrobbler name (via MPD) %1 (MPD 経由) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. '(MPD経由)'とマークされたscrobblerを使用している場合(%1など)は、これを既に起動して実行しておく必要があります。 Cantataはこれを介してトラックのみを'ラブ' 設定することができ、scrobblingを有効または無効にすることはできません。 Authenticating... 認証中... Authenticated 認証済み Not Authenticated 未認証状態 %1: Scrobble Tracks %1: トラックをScrobble Digitally Imported Settings デジタルでのインポート設定 MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout ログアウト URL: URL: Add Stream ストリームに追加 Edit Stream ストリームの編集 <i><b>ERROR:</b> Invalid protocol</i> <i><b>エラー:</b> 無効なプロトコル</i> Loading %1 %1 読込中 Installed インストール済 Update available 利用可能な更新 Check the providers you wish to install/update. インストール/アップデートするプロバイダを確認します。 Install/Update Stream Providers ストリームプロバイダのインストール/更新 Downloading list... リストのダウンロード中... Digitally Imported デジタルでのインポート Local and National Radio (ListenLive) ローカルと全国ラジオ (ListenLive) Failed to download list of stream providers! ストリームプロバイダリストのダウンロードに失敗しました! Installing/updating %1 %1 のインストール/更新中 Failed to install '%1' '%1' のインストールに失敗 Failed to download '%1' '%1' のダウンロードに失敗 Install/update the selected stream providers? 選択したストリームプロバイダをインストール/更新しますか? Install the selected stream providers? 選択したストリームプロバイダをインストールしますか? Update the selected stream providers? 選択したストリームプロバイダを更新しますか? Install/Update インストール/更新 Abort installation/update? インストール/更新を中止しますか? Downloading %1 %1 ダウンロード中 Update all updateable providers 全ての更新可能プロバイダを更新 Import Streams Into Favorites ストリームからお気に入りにインポート Export Favorite Streams お気に入りをストリームにエクスポート Add New Stream To Favorites 新規ストリームをお気に入りに追加 Seatch For Streams ストリームを検索 Digitally Imported Service name ディジタルでのインポート Import Streams ストリームのインポート XML Streams (*.xml *.xml.gz *.cantata) XML ストリーム (*.xml *.xml.gz *.cantata) Export Streams ストリームのエクスポート XML Streams (*.xml.gz) XML ストリーム (*.xml.gz) Failed to create '%1'! '%1' の作成に失敗しました! Stream '%1' already exists! ストリーム '%1' は既に存在します! A stream named '%1' already exists! '%1' と言う名前のストリームは既に存在します! Bookmark added ブックマークが追加されました Already bookmarked 既にブックマークが存在する Already in favorites 既にお気に入りに存在する Reload '%1' streams? '%1' ストリームを再読み込みしますか? Are you sure you wish to remove bookmark to '%1'? 本当に '%1' へのブックマークを削除しますか? Are you sure you wish to remove all '%1' bookmarks? 本当に全ての '%1' ブックマークを削除しますか? Are you sure you wish to remove the %1 selected streams? 本当に '%1' 選択済みストリームを削除しますか? Are you sure you wish to remove '%1'? 本当に '%1' を削除しますか? Added '%1'' to favorites '%1' をお気に入りに追加しました Configure Streams ストリームの設定 Download... ダウンロード... Configure Provider プロバイダの設定 Install インストール Install Streams ストリームのインストール Cantata Streams (*.streams) Cantata ストリーム (*.streams) A category named '%1' already exists! Overwrite? '%1' と言う名前のカテゴリは既に存在します! 上書しますか? Failed top open package file. パッケージファイルを開くことができません。 Invalid file format! 無効なファイルフォーマットです! Failed to create stream category folder! ストリームカテゴリフォルダの作成に失敗しました! Failed to save stream list! ストリームリストの保存に失敗しました! Failed to remove streams folder! ストリームフォルダの削除に失敗しました! &OK &OK &Cancel &Cancel &Yes &Yes &No &No &Discard 破棄(&D) &Save 保存(&S) &Apply 適用(&A) &Close 閉じる(&C) &Overwrite 上書(&O) &Reset リセット(&R) &Continue 継続(&C) &Delete 削除(&D) &Stop 停止(&S) &Remove 削除(&R) &Previous 前(&P) &Next 後(&N) Configure... 設定... Password パスワード Please enter password: パスワードを入力してください: Close 閉じる Warning 警告 Question 質問 &Window ウィンドウ(&W) Minimize 最小化 Zoom ズーム Select Folder フォルダの選択 Select File ファイルの選択 %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags タグ Set 'Album Artist' from 'Artist' 'アーティスト' から 'アルバムアーティスト' を設定 Read Ratings from File ファイルからレーティングを読込 Write Ratings to File ファイルにレーティングを書込 All tracks 全トラック Apply "Various Artists" workaround to <b>all</b> tracks? <b>全</b> トラックに "Various Artists" 回避策を適用しますか? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i> 'アルバムアーティスト' と 'アーティスト' を "Various Artists" に設定し、'タイトル' を "トラックアーティスト - トラックタイトル" に設定します</i> Revert "Various Artists" workaround on <b>all</b> tracks? <b>全</b> トラックへの "Various Artists" 回避策を元に戻しますか? Revert "Various Artists" workaround "Various Artists" 回避策を元に戻す <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>'アルバムアーティスト' が 'アーティスト' と同じで、 'タイトル' の書式が "トラックアーティスト - トラックタイトル" の場合、 'アーティスト' は 'タイトル' から取得し 'タイトル' はトラックタイトルとなります。例えば <br/><br/> 'タイトル' が "Wibble - Wobble" の場合、 'アーティスト' には "Wibble" が 'タイトル' には "Wobble" が設定されます</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? ('アルバムアーティスト' が空の際) <b>全ての</b> トラックに 'アルバムアーティスト' に 'アーティスト' を設定しますか? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? ('アルバムアーティスト' が空の際) 'アルバムアーティスト' に 'アーティスト' を設定しますか? Album Artist from Artist アーティスト から アルバムアーティスト設定 Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? <b>全ての</b> トラックのテキストフィールド1文字目を大文字にしますか(例えば 'Title', 'Artist'など)? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? テキストフィールド1文字目を大文字にしますか(例えば 'Title', 'Artist'など)? Adjust the value of each track number by: 各トラック番号を調整する: Read ratings for all tracks from the music files? ミュージックファイルからの全トラックのレーティングを読込しますか? Read rating from music file? 音楽ファイルからレーティングを読込しますか? Ratings レーティング Read Ratings レーティングの読込 Read Rating レーティングの読込 Read, and updated, ratings from the following tracks: 以下のトラックからレーティングを読込,更新する: Not all Song ratings have been read from MPD! MPDからどの楽曲のレーティングも読み込みできません! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. 楽曲のレーティングは楽曲ファイルに保存されていませんが、MPDの’スティッカー' データベースに保存されています。これらの情報を実際のファイルに保存するには、CantataはまずMPDからこの情報を読込する必要があります。 Song rating has not been read from MPD! 楽曲のレーティングがMPDから読込されていません! Write ratings for all tracks to the music files? 音楽ファイルに全トラックのレーティングを書き込みしますか? Write rating to music file? 音楽ファイルにレーティングを書き込みしますか? Write Ratings レーティングの書込 Write Rating レーティングの書込 Failed to write ratings of the following tracks: 以下のトラックのレーティング書込に失敗: Failed to write rating to music file! 音楽ファイルへのレーティング書込に失敗! All tracks [modified] 全トラック [修正済] %1 [modified] %1 [修正済] Would you also like to rename your song files, so as to match your tags? 楽曲ファイル名をタグと一致するように変更しますか? Rename Files ファイル名を変更 Abort renaming of files? ファイル名の変更を中止しますか? Source file does not exist! 入力元ファイルが存在しません! Destination file already exists! 出力先ファイルは既に存在します! Failed to create destination folder! 出力先フォルダの作成に失敗しました! Failed to rename '%1' to '%2' '%1' から '%2' への名前変更に失敗しました Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. 楽曲のレーティングは楽曲ファイル内には保存されていませんが、MPDの’sticker’データベースにはあります。 ファイルの名前を変更する(もしくは楽曲のあるフォルダ名を変更する)と、その楽曲へのレーティングは失われます。 <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>作曲家:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>演奏家:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>アーティスト:</b></td><td>%1</td></tr><tr><td align="right"><b>アルバム:</b></td><td>%2</td></tr><tr><td align="right"><b>年:</b></td><td>%3</td></tr> Go Back 戻る Menu メニュー (Stream) (ストリーム) Search... 検索... Close Search Bar 検索バーを閉じる Logged into %1 %1 へログイン <b>NOT</b> logged into %1 %1 にログイン<b>しない</b> Basic Tree (No Icons) ベーシックツリー(アイコンなし) Simple Tree シンプルツリー Detailed Tree 詳細ツリー List リスト Grid グリッド View ビュー Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. 楽曲ファイルへアクセスできません! Cantata の”ミュージックフォルダ” とMPD の "music_drectory" 設定を確認してください。 Cannot access song files! Please check that the device is still attached. 楽曲ファイルへアクセスできません! デバイスが未だに接続されているかを確認してください。 Stretch Columns To Fit Window 行がウィンドウサイズに合うように拡大する Center 中心 Alignment 位置合わせ (Various) (Various) Click to go back クリックし戻る Add All To Play Queue プレイキューにすべて追加 Add All And Replace Play Queue すべて追加しプレイキューを書換 Mute ミュート Unmute ミュート解除 Volume %1% (Muted) ボリューム %1% (ミュート中) Volume %1% Volume %1% 1 Track Singular 1 トラック %1 Tracks Plural (N!=1) %1 トラック 1 Track (%1) Singular 1 トラック (%1) %1 Tracks (%2) Plural (N!=1) %1 トラック (%2) 1 Album Singular 1 アルバム %1 Albums Plural (N!=1) %1 アルバム 1 Artist Singular 1 アーティスト %1 Artists Plural (N!=1) %1 アーティスト 1 Stream Singular 1 ストリーム %1 Streams Plural (N!=1) %1 ストリーム 1 Entry Singular 1 エントリ %1 Entries Plural (N!=1) %1 エントリ 1 Rule Singular 1 ルール %1 Rules Plural (N!=1) %1 ルール 1 Podcast Singular 1 Podcast %1 Podcasts Plural (N!=1) %1 Podcasts 1 Episode Singular 1 エピソード %1 Episodes Plural (N!=1) %1 エピソード 1 Update available Singular 1 更新あり %1 Updates available Plural (N!=1) %1 更新あり ActionDialog Calculating size of files to be copied, please wait... 複写するファイルのサイズを計算中です、お待ちください... Copy songs from: 楽曲の複写元: Configure 設定 (Needs configuring) (設定が必要) Copy songs to: 楽曲の複写先: Destination format: 相手先の形式: Overwrite songs 楽曲の上書 To copy: 複写先: <b>INVALID</b> <b>無効</b> <i>(When different)</i> <i>(差異がある場合)</i> Artists:%1, Albums:%2, Songs:%3 アーティスト:%1, アルバム:%2, 楽曲:%3 %1 free %1 空き Local Music Library ローカルミュージックライブラリ Audio CD オーディオCD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. 出力先のデバイスに十分な空き容量がありません。 選択した曲は%1 を消費しますが、 %2 しか残っていません。 曲は、正常にコピーされるために、より小さなファイルサイズに変換される必要があります。 There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. 出力先に十分な空き容量がありません。 選択した曲は%1 を消費しますが、%2 しか残っていません。 Copy Songs To Library 楽曲をライブラリに複写 Copy Songs To Device 楽曲をデバイスに複写 Copy Songs 楽曲を複写 Delete Songs 楽曲を削除 You have not configured the destination device. Continue with the default settings? 出力先のデバイスを設定していません。 規定の設定で継続しますか? Not Configured 未設定 Use Defaults 規定値を使用 You have not configured the source device. Continue with the default settings? 元デバイスを設定しておりません。 規定の設定で継続しますか? Are you sure you wish to stop? 本当に停止しますか? Stop 停止 Device has been removed! デバイスは取り外しされました! Device is not connected! デバイスは未接続です! Device is busy? デバイスが使用中? Device has been changed? デバイスが変更された? Clearing unused folders 未使用フォルダを削除 Calculate ReplayGain for ripped tracks? リッピング済みトラックでリプレイゲインを計算しますか? ReplayGain リプレイゲイン Calculate 計算 The destination filename already exists! 出力ファイル名は既に使用済みです! Song already exists! 楽曲が既に存在します! Song does not exist! 楽曲が存在しません! Failed to create destination folder!<br/>Please check you have sufficient permissions. 出力先フォルダの作成に失敗しました<br/>十分な権限があることを確認してください。 Source file no longer exists? 元ファイルが既に存在しない? Failed to copy. 複写に失敗しました。 Failed to delete. 削除に失敗しました。 Not connected to device. デバイスに未接続です。 Selected codec is not available. 選択コーデックは存在しません。 Transcoding failed. トランスコーディングに失敗しました。 Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) 一時ファイルの作成に失敗しました<br/> Failed to read source file. 元ファイルの読み込みに失敗。 Failed to write to destination file. 出力先ファイルの書込に失敗しました。 No space left on device. デバイスの空き容量がありません。 Failed to update metadata. メタデータの更新に失敗しました。 Failed to download track. トラックのダウンロードに失敗しました。 Failed to lock device. デバイスのロックに失敗しました。 Local Music Library Properties ローカルミュージックライブラリのプロパティ Error エラー Skip スキップ Auto Skip オートスキップ Retry 再試行 Artist: アーテイスト: Album: アルバム: Track: トラック: Source file: 入力元ファイル: Destination file: 出力先ファイル: File: ファイル: Saving cache キャッシュを保存中 Calculating... 計算中... Time remaining: 残時間: AlbumDetails Album Details アルバムの詳細 Artist: アーテイスト: Composer: 作曲家: Title: タイトル: Genre: ジャンル: Year: 年: Disc: ディスク: Single artist シングル アーティスト Tracks トラック Track トラック Artist アーティスト Title タイトル AlbumDetailsDialog Audio CD オーディオCD Apply "Various Artists" Workaround "Various Artists" による回避策を適用 Revert "Various Artists" Workaround "Various Artists" による回避策を元に戻す Capitalize 大文字に変更 Adjust Track Numbers トラック番号を調整 Tools ツール Apply "Various Artists" workaround? "Various Artists" による回避策を適用しますか? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" ’アルバムアーティスト' や 'アーティスト' を "Various Artists" に設定し 'タイトル' を "トラックアーティスト - トラックタイトル" に設定する Revert "Various Artists" workaround? "Various Artists" による回避策を元に戻しますか? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" 'アルバムアーティスト' と 'アーティスト'が同一で、'タイトル'の形式が'トラックアーティスト - トラックタイトル' の場合、’アーティスト’ は ’タイトル’ のトアックアーティストから取得され、’タイトル’ 自体はタイトルのみ(トラックタイトル)設定されます 。 例えば ’タイトル’が”ウォブル - ウォブル” の場合、’アーティスト’は ”ウォブル” に設定され、’タイトル’は’ウォブル’に設定されます Revert 元に戻す Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? 'タイトル', 'アーティスト', 'アルバムアーティスト', 'アルバム' の最初の1文字を大文字に変更しますか? Adjust track number by: トラック番号を調整: AlbumView Refresh Album Information アルバム情報の更新 Album アルバム Tracks トラック ArtistView Refresh Artist Information アーティスト情報の更新 Artist アーティスト Albums アルバム Web Links Webリンク Similar Artists 同様のアーティスト AudioCdDevice Reading disc ディスクの読込中 %n Tracks (%1) %n トラック (%1) AudioCdSettings Album and Track Information Retrieval アルバムとトラック情報を検索 Initially look up via: 最初に以下を参照: CDDB Host: CDDB ホスト: CDDB Port: CDDB ポート: Lookup information as soon as CD is inserted CDが挿入された際直ちに情報を検査する Audio Extraction オーディオ抽出 Full paranoia mode (best quality) フル パラノイアモード (最高の品質) Never skip on read error 読取エラーをスキップしない CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet キューシート Playlist プレイリスト CacheItem Deleting... 削除中... Calculating... 計算中... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata は、さまざまな情報(カバー, 歌詞, その他)をキャッシュします。 以下はCantataの現在のキャッシュ使用方法の概要です。 Covers カバー Scaled Covers スケーリングされたカバー Backdrops バックドロップ Lyrics 歌詞 Artist Information アーティスト情報 Album Information アルバム情報 Track Information トラック情報 Stream Listings ストリームリスト Podcast Directories Podcast ディレクトリ Wikipedia Languages Wikipedia 言語 Scrobble Tracks Scrobble トラック Delete All 全て削除 Delete all '%1' items? 全 '%1' アイテムを削除しますか? Delete Cache Items 全キャッシュアイテムを削除 Delete items from all selected categories? 全選択カテゴリからアイテムを削除しますか? CacheTree Name 名前 Item Count アイテム数 Space Used 使用中容量 CddbInterface Data Track データトラック Failed to open CD device CD デバイスのオープンに失敗 Track %1 トラック %1 Failed to create CDDB connection CDDB 接続の作成に失敗 Failed to contact CDDB server, please check CDDB and network settings CDDB サーバへの接続に失敗しました、CDDBとネットワーク設定を確認してください No matches found in CDDB CDDB内に一致項目がありません CDDB error: %1 CDDB エラー: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: 複数の一致候補がありました。以下から該当するものを選択してください: Artist アーティスト Title タイトル Disc Selection ディスクの選択 %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 ディスク %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers 歌詞プロバイダ Wikipedia Languages Wikipedia 言語 Other その他 ContextWidget &Artist アーティスト(&A) Al&bum アルバム(&b) &Track トラック(&T) CoverDialog Search 検索 Add a local file ローカルファイルを追加 Configure 設定 This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive カバーアート アーカイブ An image already exists for this artist, and the file is not writeable. このアーティスト向けのイメージは既に存在し、ファイルは上書不可能です。 A cover already exists for this album, and the file is not writeable. このアルバム向けのカバーは既に存在し、ファイルは上書不可能です。 '%1' Artist Image '%1' アーティストイメージ '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' アルバムカバー Failed to set cover! Could not download to temporary file! カバーの設定に失敗しました 一時ファイルへのダウンロードが出来ません! Failed to download image! イメージのダウンロードに失敗しました! Load Local Cover ローカルカバーの読込 Images (*.png *.jpg) 画像ファイル (*.png *.jpg) File is already in list! ファイルは既にリストにあります! Failed to read image! イメージの読込に失敗しました! Display 表示 Remove 削除 Failed to set cover! Could not make copy! カバーの設定に失敗しました! 複製を作成出来ません! Failed to set cover! Could not backup original! カバーの設定に失敗しました! オリジナルのバックアップが出来ません! Failed to set cover! Could not copy file to '%1'! カバーの設定に失敗しました! '%1' への複写が出来ません! Searching... 検索中... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>作曲家:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>演奏家:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>アーティスト:</b></td><td>%1</td></tr><tr><td align="right"><b>アルバム:</b></td><td>%2</td></tr><tr><td align="right"><b>年:</b></td><td>%3</td></tr> CoverPreview Image イメージ Downloading... ダウンロード中... Image (%1 x %2 %3%) Image (width x height zoom%) イメージ (%1 x %2 %3%) CustomActionDialog Name: 名前: Command: コマンド: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. 上のコマンドラインでは、%f はファイルリストに置き換えられ、%d はフォルダリストに置き換えられます。 どちらも指定されていない場合、ファイルのリストがコマンドに追加されます。 Add New Command 新規コマンド追加 Edit Command コマンド編集 CustomActions Custom Actions カスタムアクション CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Cantataに外部コマンドを呼び出させるには(たとえば、別のアプリケーションでタグを編集する)、以下のコマンドのエントリを追加します。 少なくとも1つのコマンドコマンドが定義されている場合、ライブラリ、フォルダ、プレイリストビューのコンテキストメニューに'カスタムアクション'エントリが追加されます。 Add 追加 Edit 編集 Remove 削除 Name 名前 Command コマンド Remove the selected commands? 選択済みコマンドを削除しますか? Device Updating (%1)... 更新中 (%1)... Updating (%1%)... 更新中 (%1%)... DevicePropertiesDialog Device Properties デバイスプロパティ DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. これらの設定は、デバイスが接続されている場合にのみ有効で編集可能です。 Name: 名前: Music folder: 音楽フォルダ: Copy album covers as: アルバムカバーを複写: Maximum cover size: 最大カバーサイズ: Default volume: 規定のボリウム: 'Various Artists' workaround 'Various Artists'の回避策 Automatically scan music when attached 接続された際に自動的に音楽をスキャンする Use cache キャッシュを使用する Filenames ファイル名 Filename scheme: ファイル名体系: VFAT safe VFAT セーフ Use only ASCII characters ASCII文字のみを使用する Replace spaces with underscores スペースをアンダーバーに置換する Append 'The' to artist names アーティスト名に「The」を付ける If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' アーティスト名が 'The'で始まる場合は、フォルダ名の先頭に追加します。 例えば 「The Beatles」は「Beatles, The」になります Transcoding コード変換中 Only transcode if source file is of a different format ソースファイルが異なる書式の場合のみコード変換する Only transcode if source is FLAC/WAV Don't copy covers カバーを複写しない Embed cover within each file 各ファイルにカバーを埋め込む No maximum size 最大サイズなし 400 pixels 400 ピクセル 300 pixels 300 ピクセル 200 pixels 200 ピクセル 100 pixels 100 ピクセル <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>デバイスにトラックを複写し、 'アルバムアーティスト'が 'Various Artists'に設定されている場合、Cantataはすべてのトラックの 'アーティスト'タグを 'Various Artists'に、トラック 'タイトル'タグを 'トラックアーティスト - トラックタイトル 'に設定します。<hr /> Cantataは、デバイスからコピーする際に、' アルバムアーティスト 'と' アーティスト 'の両方が' Various Artists 'に設定されているかどうかを確認します。 そうであれば、 'タイトル'タグから実際のアーティストを抽出し、 'タイトル'タグからアーティスト名を削除します。</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>本機能を有効にした際、Cantata は本デバイスのミュージックライブラリのキャッシュを生成します。これは、後続のライブラリースキャンを高速化するのに役立ちます(各ファイルのタグを読み取る代わりに、キャッシュファイルが使用されるため)。<hr/><b>注意:</b>別のアプリケーションを使用してデバイスのライブラリを更新すると、このキャッシュは古くなってしまいます。 これを修正するには、デバイスリストの「更新」アイコンをクリックします。 これにより、キャッシュファイルが削除され、デバイスの内容が再スキャンされます。</p> Do not transcode トランスコードしない Encoder エンコーダ Transcode to %1 %1 へトランスコード %1 (%2 free) name (size free) %1 (%2 空き) DevicesModel Configure Device デバイスの設定 Refresh Device デバイスの更新 Connect Device デバイスに接続 Disconnect Device デバイスからの切断 Edit CD Details CDの詳細を編集 Not Connected 未接続 No Devices Attached 接続済デバイスがありません DevicesPage Copy To Library ライブラリへ複写 Synchronise 同期 Forget Device デバイスを消去 Add Device デバイスを追加 Lookup album and track details? アルバムとトラックの詳細を検索しますか? Refresh 更新 Via CDDB CDDB経由 Via MusicBrainz MusicBrainz経由 Which type of refresh do you wish to perform? どのタイプの更新を行いますか? Partial - Only new songs are scanned (quick) 部分的 - 追加楽曲をスキャン (高速) Full - All songs are rescanned (slow) 完全 - 全楽曲を再スキャン (低速) Partial 部分的 Full 完全 Are you sure you wish to delete the selected songs? This cannot be undone. 選択楽曲を削除しますか? 本操作はやり直しできません。 Delete Songs 楽曲を削除 Are you sure you wish to forget '%1'? ’%1’ を消去しますか? Are you sure you wish to eject Audio CD '%1 - %2'? オーディオ CD %1 をイジェクトしますか - %2 ? Eject イジェクト Are you sure you wish to disconnect '%1'? %1 を切断しますか? Disconnect 切断 Please close other dialogs first. 他のダイアログを先に閉じてください。 DigitallyImported Not logged in ログインなし Logged in ログイン済 Unknown error 不明なエラー No subscriptions サブスクリプションなし You do not have an active subscription アクティブなサブスクリプションがありません Logged in (expiry:%1) ログイン済 (有効期限:%1) Session expired セッションが期限切れです DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. アカウントなしで無料で聴くことができますが、プレミアムメンバーは広告なしで高品質のストリームを聴くことができます。プレミアムアカウントにアップグレードするには、<a href="http://www.di.fm/premium/"> http://www.di.fm/premium/ </a>をご覧ください。 Premium Account プレミアムアカウント Username: ユーザ名: Password: パスワード: Stream type: ストリームタイプ: Status: ステータス: Login ログイン Session expiry: セッションの有効期限: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm これらの設定はDigitally Imported, JazzRadio.com, RockRadio.com, Sky.fm で有効です If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. アカウントの詳細を入力すると、ストリームのリストの下に「DI」ステータスアイテムが表示されます。 これはあなたがログインしているかどうかを示します。 Digitally Imported Settings デジタルでのインポート設定 MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated 未認証状態 Authenticating... 認証中... Authenticated 認証済み Logout ログアウト DockMenu Play 再生 Pause 一時停止 DynamicPlaylists Start Dynamic Playlist ダイナミックプレイリストの開始 Stop Dynamic Mode ダイナミックプレイリストの停止 Dynamic Playlists ダイナミックプレイリスト Dynamically generated playlists 動的に生成されたプレイリスト You need to install "perl" on your system in order for Cantata's dynamic mode to function. Cantata のダイナミックモードを動作させるにはこのシステムに "perl" をインストールする必要があります。 Failed to locate rules file - %1 ルールファイルの検索に失敗しました - %1 Failed to remove previous rules file - %1 以前のルールファイルの証書に失敗しました - %1 Failed to install rules file - %1 -> %2 ルールファイルのインストールに失敗しました - %1 -> %2 Dynamizer has been terminated. ダイナマイザは停止しました。 Awaiting response for previous command. (%1) 以前のコマンドに対する応答を待っています。 (%1) Saving rule ルールを保存中 Deleting rule ルールを削除中 Failed to save %1. (%2) %1 の保存に失敗しました。 (%2) Failed to delete rules file. (%1) ルールファイルの削除に失敗しました。 (%1) Failed to control dynamizer state. (%1) ダイナマイザの状態制御に失敗しました。 (%1) Failed to set the current dynamic rules. (%1) 現在のダイナミックルールの設定に失敗しました。 (%1) DynamicPlaylistsPage Add 追加 Edit 編集 Remove 削除 Remote dynamizer is not running. リモートダイナマイザーは動作していません。 Are you sure you wish to remove the selected rules? This cannot be undone. 選択済みのルールを削除しますか? この操作はやり直しできません。 Remove Dynamic Rules ダイナミックルールを削除 FancyTabWidget Configure... 設定... FileSettings Save downloaded covers, artist, and composer images, in music folder ダウンロード済のカバー, アーティスト, 作曲家のイメージを音楽フォルダに保存する Save downloaded lyrics in music folder ダウンロード済みの歌詞を音楽フォルダに保存する Save downloaded backdrops in music folder ダウンロード済みのバックドロップを音楽フォルダに保存する If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. 音楽フォルダ内にCantataのカバー、歌詞、または背景を保存することを選択した場合、このフォルダへの書き込みアクセス権がない場合、Cantataはファイルを自分のキャッシュフォルダに保存します。 Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantataは、2レベルの深さまでならば、音楽フォルダ階層内の背景、アーティスト、および作曲家の画像のみを保存できます。 すなわち「Artist / Album / Tracks」と表示されます。 FilenameSchemeDialog Example: 例: About filename schemes ファイル名体系について The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> アルバムのアーティストのこと。大半のアルバムではこれは<i>トラック アーティスト</i>と同じであり、コンピレーションアルバムではしばしば<i>Various Artists</i>になります。 Album Artist アルバムアーティスト The name of the album. アルバム名を指す。 Album Title アルバム名 The composer. 作曲家を指す。 Composer 作曲家 The artist of each track. 各トラックのアーティストを指す。 Track Artist トラックアーティスト The track title (without <i>Track Artist</i>). トラック名 (<i>トラック アーティスト</i>を含まない). Track Title トラック名 The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). トラック名 (<i>アルバムアーティスト</i>と異なる場合のみ<i>トラックアーティストを含む</i>)。 Track Title (+Artist) トラック名 (+アーティスト) The track number. トラック番号を指す。 Track # Track # The album number of a multi-album album. Often compilations consist of several albums. 複数のディスクを持つアルバムのアルバム番号を指す。多くの場合、コンピレーションアルバムは複数のディスクで構成されています。 CD # CD # The year of the album's release. アルバムのリリース年を指す。 Year The genre of the album. アルバムのジャンルを指す。 Genre ジャンル Filename Scheme ファイル名体系 Various Artists Example album artist Various Artists Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. 以下の変数は、各トラック名に対応する意味に置き換えられます。 <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>変数</em></th><th><em>ボタン</em></th><th><em>詳細</em></th></tr> FolderPage Open In File Manager ファイルマネージャで開く Are you sure you wish to delete the selected songs? This cannot be undone. 選択楽曲を削除しますか? 本操作はやり直しできません。 Delete Songs 楽曲を削除 FsDevice Updating... 更新中... Reading cache キャッシュ読込中 Saving cache キャッシュを保存中 %1 %2% Message percent %1 %2% GenreCombo Filter On Genre All Genres GroupedViewDelegate Audio CD オーディオCD Streams ストリーム %n Track(s) %n トラック InitialSettingsWizard Cantata First Run Cantataの最初の実行 Welcome to Cantata ようこそ Cantata へ <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantataは、Music Player Daemon (MPD)の機能豊富でユーザーフレンドリーなクライアントです。MPDは、音楽を演奏するための柔軟で強力なサーバーサイドのアプリケーションです。</p><p>MPD自体の詳細については、MPDのWebサイト<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>を参照ください。</p><p>この「ウィザード」は、Cantataが正しく機能するために必要な基本設定をガイドします。</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Cantata へようこそ</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>Cantataは、Music Player Daemon (MPD)の機能豊富でユーザーフレンドリーなクライアントです。MPDは、音楽を演奏するための柔軟で強力なサーバーサイドのアプリケーションです。MPD はシステムワイド、ユーザ単位で起動することが可能です。<br/><br/>Cantataに最初にMPDに接続する(または起動する)方法を選択してください:</p> Standard multi-user/server setup 標準マルチユーザ/サーバー セットアップ <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>音楽コレクションがユーザー間で共有されている場合、MPDインスタンスが別のマシンで実行中の場合、個人MPD設定が既にある場合、または他のクライアント(MPDroidなど)からのアクセスを有効にする場合はこのオプションを選択します。このオプションを選択すると、Cantata自体がMPDサーバーの起動と停止を制御できなくなります。MPDが既に設定され、実行されていることを確認する必要があります。</i> Basic single user setup 基本シングルユーザセットアップ <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>あなたの音楽コレクションが他の人と共有されておらず、CantataがMPDインスタンスを設定して制御したい場合は、このオプションを選択します。この設定はCantata専用で、他のMPDクライアント(MPDroidなど)からアクセス<b>出来ない</b>ようになります</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' 高度なMPD設定(複数のオーディオ出力、完全なDSDサポートなど)を希望する場合、'標準'を選択する<b>必要があります</b> For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. MPD自体の詳細については、MPDのWebサイト<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>を参照ください。<br/><br/>この「ウィザード」は、Cantataが正しく機能するために必要な基本設定をガイドします。 Connection details 接続の詳細 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. 以下の設定はCantataが必要な基本設定です。関連する詳細を入力し、[接続]ボタンを使用して接続をテストしてください。 Host: ホスト: Password: パスワード: Music folder: 音楽フォルダ: Connect 接続 The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. 「音楽フォルダ」設定は、カバーアート、歌詞などを検索するために使用されます。MPDインスタンスがリモートホスト上にある場合、これをHTTP URLに設定することができます。 Music folder 音楽フォルダ Please choose the folder containing your music collection. あなたの音楽コレクションを含むフォルダを選択してください。 Covers and Lyrics カバーと歌詞 <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata はインターネットから不明なカバーや歌詞をダウンロードします。</p><p>これらそれぞれについて、Cantataに関連するファイルをミュージックフォルダ内に保存するか、個人のキャッシュ/設定フォルダ内に保存するかを確認してください。</p> Save downloaded covers, artist, and composer images, in music folder ダウンロード済のカバー, アーティスト, 作曲家のイメージを音楽フォルダに保存する Save downloaded lyrics in music folder ダウンロード済みの歌詞を音楽フォルダに保存する Save downloaded backdrops in music folder ダウンロード済みのバックドロップを音楽フォルダに保存する If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. 音楽フォルダ内にCantataのカバー、歌詞、または背景を保存することを選択した場合、このフォルダへの書き込みアクセス権がない場合、Cantataはファイルを自分のキャッシュフォルダに保存します。 Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Cantataは、2レベルの深さまでならば、音楽フォルダ階層内の背景、アーティスト、および作曲家の画像のみを保存できます。 すなわち「Artist / Album / Tracks」と表示されます。 The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. 'Music folder'はHTTPアドレスに設定されており、Cantataは現在、外部HTTPサーバーにファイルをアップロードできません。したがって、上記の設定は未チェックのままにしておく必要があります。 Finished! 完了! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata が設定されました!<br/><br/>Cantataの設定ダイアログは、Cantataの外観をカスタマイズするために使用されるほか、追加のMPDホストなどを追加するために使用されます。 Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantataは 'AlbumArtist'タグが設定されている場合はトラックをアルバムにグループ化し、そうでない場合は 'Artist'タグにフォールバックします。複数のアーティストを含むアルバムをお持ちの場合は、グループ化が正しく機能するように 'AlbumArtist'タグを<b>設定</b>する必要があります。このシナリオでは、「Various Artists」を使用することをお勧めします。 <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>注意:</b> お使いのユーザアカウントは 'users' グループのメンバーではありません. Cantata はあなた(もしくは管理者が)ユーザアカウントをこのグループに追加することにより より良く動作します (アルバムカバー, 歌詞, その他の保存が正しい許可で行われる)。グループに使用中のアカウントを追加した場合は一旦ログアウト ログインを行ってアプリケーションを再起動することでこの機能が動作します。 Not Connected 未接続 Connection Established 接続が確立しました Connection Failed 接続に失敗しました Cantata will now terminate InputDialog Password パスワード Please enter password: パスワードを入力してください: InterfaceSettings Sidebar サイドバー Views ビュー Use the checkboxes below to configure which views will appear in the sidebar. 下のチェックボックスを使用して、サイドバーに表示するビューを設定します。 If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. 上記のチェックボックスをオンにしないと、他のビューの横に表示されます。上記の[情報]がチェックされていない場合、曲情報にアクセスするためのボタンがツールバーに追加されます。 Options オプション Style: スタイル: Position: ポジション: Only show icons, no text アイコンのみ表示しテキストは非表示にする Auto-hide 自動的に隠す Play Queue プレイキュー Initially collapse albums 初期動作時はアルバムをたたむ Automatically expand current album 現在のアルバムを自動的に拡大する Scroll to current track 現在のトラックまでスクロールする Prompt before clearing クリア作業実施前に警告する Separate action (and shortcut) for play queue search プレイキュー検索のためのセパレートアクション (とショートカット) Background Image 背景イメージ None なし Current album cover 現在のアルバムカバー Custom image: カスタム イメージ: Blur: ブラー: 10px 10px Opacity: 透明度: 40% 40% Toolbar ツールバー Show stop button 停止ボタンを表示 Show cover of current track 現トラックのカバーを表示 Show track rating トラックのレーティングを表示 External 外部 Enable MPRIS D-BUS interface MPRIS D-BUS インタフェースを有効化 Show popup messages when changing tracks トラック変更時にポップアップメッセージを表示 Show icon in notification area 通知エリアにアイコンを表示 Minimize to notification area when closed 閉じた際に通知エリアを最小化する On Start-up 起動時 Show main window メインウィンドウを表示 Hide main window メインウィンドウを隠す Restore previous state 以前の状態を復元する Tweaks 微調整 Artist && Album Sorting アーティスト && アルバムソート Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' アーティストとアルバムを並べ替えるときに無視するプレフィックスのリスト(カンマ区切り)を入力します。例えば 'The'に設定した場合、 'The Beatles'は 'Beatles'によってソートされます Enter comma separated list of prefixes... コンマ区切りのプレフィックスのリストを入力してください... Composer Support 作曲家のサポート By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. 既定では、Cantataは曲とアルバムをグループ化するために 'Album Artist'タグ('Album Artist'がない場合は'Artist'タグ)を使用します。'Classical'などの特定のジャンルについては、このグループ化を実行するために'Composer'タグ(設定されている場合)を使用することが好ましい場合があります。Cantataに 'Composer'タグを使用させたいジャンルの(カンマ区切りの)リストを入力してください。 Enter comma separated list of genres... コンマ区切りのジャンルのリストを入力してください... Single Tracks シングルトラック If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' コレクションに単一のトラックしか含まれていないアーティストがたくさんいる場合は、それぞれのアーティストがアーティストのリストに独自のエントリを持つのは面倒です。これを回避するには、これらのトラックを別のフォルダに配置し、このフォルダ名を下に入力すると、Cantataはアルバムアーティストの「Various Artists」で「Single Tracks」という名前のアルバムをグループ化します Folder that contains single track files... シングルトラックファイルを含むフォルダ名を入力してください... CUE Files CUE ファイル A cue file is a metadata file which describes how the tracks of a CD are laid out. キューファイルは、CDのトラックがどのようにレイアウトされているかを記述するメタデータファイルです。 Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. 上記のいずれかを変更するには、影響を受けるためにDBリフレッシュが必要です(場合によってはCantataを再起動する必要があります)。 General 一般設定 Fetch missing covers from Last.fm 未発見のカバーを Last.fm から取得する Show delete action in context menus コンテキストメニュー内に削除を表示 Enforce single-click activation of items シングルクリックでのアイテムのアクティブ化を強制する Changing the style setting will require a re-start of Cantata. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>これにより、Cantataのインターフェースが以下の通り変更されます: <ul><li>再生、コントロールボタンが33%広くなります</li><li>ビューは 'flickable'になります</li><li>項目をドラッグするには、左上隅を 'タッチ'する必要があります。</li><li>スクロールバーの幅は数ピクセルになります。</li><li>アクション(「プレイキューに追加」など)は常に表示されます(アイテムがマウスの下にあるときだけでなく)。</li><li>スピンボタンがテキストフィールドの横に+ボタンと - ボタンと追加されます。</li></ul></p> Make interface more touch friendly タッチ操作に適したインターフェースに変更 Show song information tooltips ツールチップに楽曲情報を表示 Support retina displays 高解像度ディスプレへの対応 Language: 言語: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. 「シングルクリックでのアイテムのアクティブ化を強制する」設定を変更するには、Cantataを再起動する必要があります。 Changing the language setting will require a re-start of Cantata. 言語設定の変更時はCantataを再起動する必要があります。 Changing the 'touch friendly' setting will require a re-start of Cantata. 「タッチフレンドリー」の設定を変更するには、Cantata の再起動が必要です。 Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. 高解像度ディスプレイのサポートを有効にすると、高解像度ディスプレイ上でより鮮明なアイコンが生成されるが、通常解像度のディスプレイ上ではあまり鮮明でないアイコンを生成することがあります。この設定を変更するには、Cantataを再起動する必要があります。 Library ライブラリ Folders フォルダ Playlists プレイリスト Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts インターネット - Streams, Jamendo, Maganatune, SoundCloud, Podcast Devices - UMS, MTP (e.g. Android), and AudioCDs デバイス - UMS, MTP(例えば Android), オーディオCD Search (via MPD) 検索(MPD経由) Info - Current song information (artist, album, and lyrics) 情報 - 現在の楽曲情報(アーティスト, アルバム, 歌詞) Large Small Tab-bar タブバー Left Right Top Bottom Images (*.png *.jpg) 画像ファイル (*.png *.jpg) 10px pixels 10px Notifications 通知 English (en) System default システム規定値 %1% value% %1% %1 px pixels %1 px ItemView Go Back 戻る Updating... 更新中... JamendoService The world's largest digital service for free music 世界最大のフリーミュージック向けディジタルサービス JamendoSettingsDialog Jamendo Settings Jamendo 設定 MP3 MP3 Ogg Ogg Streaming format: ストリーム形式: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined なし Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm lat.fmよりさらに読込 LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images アーティストイメージの表示 Sort Albums アルバムのソート Name 名前 Year Album, Artist, Year Album, Year, Artist Artist, Album, Year Artist, Year, Album Year, Album, Artist Year, Artist, Album Modified Date 変更日時 Group By グループ化 Genre ジャンル Artist アーティスト Album アルバム Are you sure you wish to delete the selected songs? This cannot be undone. 選択楽曲を削除しますか? 本操作はやり直しできません。 Delete Songs 楽曲を削除 LyricSettings Choose the websites you want to use when searching for lyrics. 歌詞の検索に使用したいサイトを選択してください。 LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Cantata が歌詞検索に失敗もしくは誤った楽曲を発見した際、このダイアログで新たな詳細検索を行ってください。例えば、現在の楽曲は実際にはカバーバージョンである場合、オリジナルのアーティストによる歌詞の検索が役立つかもしれません。 もし検索で新たな歌詞が見つからない場合、Cantata で表示されているオリジナルの楽曲タイトルとアーティストの関連付けが継続状態です。 Title: タイトル: Artist: アーテイスト: Search For Lyrics 歌詞の検索 MPDConnection Unknown 不明 Connection to %1 failed %1 への接続に失敗しました Connection to %1 failed - please check your proxy settings %1 への接続に失敗しました - プロキシ設定を確認ください Connection to %1 failed - incorrect password %1 への接続に失敗しました - 無効なパスワードです Connecting to %1 %1 に接続中 Failed to send command to %1 - not connected %1 へのコマンド送信に失敗しました - 未接続です Failed to load. Please check user "mpd" has read permission. 読込に失敗しました。ユーザ "mpd" に読込許可があるか確認してください。 Failed to load. MPD can only play local files if connected via a local socket. 読込に失敗しました。ローカルソケット経由で接続の際、 MPD はローカルファイルのみ再生可能です。 MPD reported the following error: %1 MPDが以下のエラーを返しました: %1 Failed to send command. Disconnected from %1 コマンド送信に失敗しました。%1 から切断されました Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b> から <b>%2</b> への名前変更に失敗 Failed to save <b>%1</b> <b>%1</b> への保存に失敗しました You cannot add parts of a cue sheet to a playlist! キューシートのパーツをプレイリストへ追加が出来ません! You cannot add a playlist to another playlist! 他のプレイリストへのプレイリストの追加が出来ません! Failed to send '%1' to %2. Please check %2 is registered with MPD. '%1'から’%2’への送信に失敗しました。%2がMPDに登録されているか確認してください。 Cannot store ratings, as the 'sticker' MPD command is not supported. 'sticker' MPD コマンドが未サポートなため、レーティングが格納できません。 MagnatuneService None なし Streaming ストリーミング MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com magnatne.com 経由のオンラインミュージック MagnatuneSettingsDialog Magnatune Settings Magnatune 設定 Username: ユーザ名: Password: パスワード: Membership: メンバーシップ: Downloads: ダウンロード: MainWindow [Dynamic] [ダイナミック] Exit Full Screen フルスクリーンから戻る Configure Cantata... Cantata の設定... Preferences 設定 Quit 終了 About Cantata... Cantata について... Show Window ウィンドウを表示 Server information... サーバー情報... Refresh Database データベースの更新 Refresh 更新 Connect 接続 Collection コネクション Outputs 出力先 Stop After Track トラック再生後停止 Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist 格納済プレイリストに追加 Crop Others その他の切取 Add Stream URL ストリーム URLの追加 Clear クリア Center On Current Track Expanded Interface 拡張インターフェース Show Current Song Information 現再生中の楽曲情報を表示 Full Screen フルスクリーン Random ランダム Repeat リピート Single シングル When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. ’シングル' 有効時、プレイバックは現在の楽曲再生後停止するか、'リピート'設定が有効な場合は現楽曲が繰り返し再生されます。 Consume コンシューム When consume is activated, a song is removed from the play queue after it has been played. コンシュームが有効な際、現在の楽曲再生が完了した際に楽曲がプレイキューから削除されます。 Find in Play Queue プレイキュー内で捜索 Play Stream 再生ストリーム Locate In Library ライブラリ内での捜索 Play next Edit Track Information (Play Queue) Expand All すべて拡張する Collapse All すべて折りたたむ Cancel キャンセル Play Queue プレイキュー Library ライブラリ Folders フォルダ Playlists プレイリスト Internet インターネット Devices デバイス Search 検索 Info 情報 Show Menubar メニューバーの表示 &Music ミュージック(&M) &Edit 編集(&E) &View ビュー(&V) &Queue キュー(&Q) &Settings 設定(&S) &Help ヘルプ(&H) Set Rating レーティングの設定 No Rating レーティングなし Failed to locate any songs matching the dynamic playlist rules. ダイナミックプレイリストルールに一致する楽曲が捜索出来ませんでした。 Connecting to %1 %1 に接続中 Refresh MPD Database? MPD データベースを更新しますか? About Cantata Cantata について <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> コンテキストビューの背景 <a href="http://fanart.tv">FanArt.tv</a> の協賛 Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> コンテキストビューのメータデータは<a href="http://www.wikipedia.org">Wikipedia</a> と <a href="http://www.last.fm">Last.fm</a> の協賛 Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> あなたのミュージックファンアートを<a href="http://fanart.tv">FanArt.tv</a>にアップロードしてください A Podcast is currently being downloaded Quiting now will abort the download. Podcastは現在ダウンロード中です 終了すると、ダウンロードが中止されます。 Abort download and quit ダウンロードを中止し終了する Please close other dialogs first. 他のダイアログを先に閉じてください。 Enabled: %1 有効化済: %1 Disabled: %1 無効化済: %1 Server Information サーバー情報 <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>サーバー</b></td></tr><tr><td align="right">プロトコル:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">稼働時間:&nbsp;</td><td>%4</td></tr><tr><td align="right">再生中:&nbsp;</td><td>%5</td></tr><tr><td align="right">ハンドラ:&nbsp;</td><td>%6</td></tr><tr><td align="right">タグ:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>データベース</b></td></tr><tr><td align="right">アーティスト:&nbsp;</td><td>%1</td></tr><tr><td align="right">アルバム:&nbsp;</td><td>%2</td></tr><tr><td align="right">楽曲:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPDが以下のエラーを返しました: %1 Cantata Canata Playback stopped 再生が停止しました Remove all songs from play queue? プレイキューからすべての楽曲を削除しますか? Priority プライオリティ Enter priority (0..255): プライオリティを入力 (0..255): Decrease priority for each subsequent track Playlist Name プレイリスト名 Enter a name for the playlist: プレイリスト名を入力: '%1' is used to store favorite streams, please choose another name. '%1' はお気に入りのストリームを保存するために使用されています、他の名前を選択してください。 A playlist named '%1' already exists! Add to that playlist? '%1' という名前のプレイリストは既に存在します プレイリストに追加しますか? Existing Playlist 存在するプレイリスト %n Track(s) %n トラック %n Tracks (%1) %n トラック (%1) MenuButton Menu メニュー MessageOverlay Cancel キャンセル Mpris (Stream) (ストリーム) MtpConnection Connecting to device... デバイスへの接続中... No devices found デバイスが見つかりません Connected to device デバイスに接続済 Disconnected from device デバイスに切断済 Updating folders... フォルダの更新中... Updating files... ファイルの更新中... Updating tracks... トラックの更新中... MtpDevice Not Connected 未接続 %1 free %1 空き MusicBrainz Failed to open CD device CD デバイスのオープンに失敗 Track %1 トラック %1 %1 (Disc %2) %1 (ディスク %2) No matches found in MusicBrainz MusicBrainz 内に一致項目がありません MusicLibraryModel Cue Sheet キューシート Playlist プレイリスト %n Track(s) %n トラック %n Artist(s) %n アーティスト %n Album(s) %n アルバム %n Tracks (%1) %n トラック (%1) %1 by %2 Album by Artist %1 by %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>追記:</b> %1</i> NowPlayingWidget (Stream) (ストリーム) OSXStyle &Window ウィンドウ(&W) Close 閉じる Minimize 最小化 Zoom ズーム OnlineDbService Downloading...%1% ダウンロード中...%1 % Parsing music list.... 楽曲リストの解析中... Failed to download ダウンロード失敗 %n Artist(s) %n アーティスト OnlineDbWidget Group By グループ化 Genre ジャンル Artist アーティスト Configure 設定 The music listing needs to be downloaded, this can consume over %1Mb of disk space 音楽リストをダウンロードする必要があります。これは%1Mb以上のディスク容量を消費する可能性があります Dowload music listing? 音楽リストをダウンロードしますか? Download ダウンロード Re-download music listing? ミュージックリストを再ダウンロードしますか? OnlineSearchService Searching... 検索中... OnlineSearchWidget No tracks found. トラックが見つかりません。 %n Tracks (%1) %n トラック (%1) OnlineSettings Use the checkboxes below to configure the list of active services. 以下のチェックボックスを使用し、アクティブなサービスのリストを設定します。 Configure Service サービスの設定 OnlineView Song Information 楽曲情報 OnlineXmlParser Failed to parse 解析に失敗 OpmlBrowsePage Reload 再読込 Failed to download directory listing ディレクトリリストのダウンロードに失敗しました Failed to parse directory listing ディレクトリリストの解析に失敗しました OtherSettings Background Image 背景イメージ None なし Artist image アーティスト イメージ Custom image: カスタム イメージ: Blur: ブラー: 10px 10px Opacity: 透明度: 40% 40% Automatically switch to view after: 次の後に自動的に切替する: Do not auto-switch 自動的にスイッチしない ms ms Dark background 暗い背景 Darken background, and use white text, regardless of current color palette. 暗い背景で、現在のカラーパレットに関わらず文字列を白色にします。 Always collapse into a single pane 常にシングルペインに変更する Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. 3つすべてを表示するのに十分な幅がある場合でも、'アーティスト','アルバム','トラック'のいずれかを表示する。 Only show basic wikipedia text 基本的な wikipedia テキストのみ表示する Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantataは、ウィキペディアのページの縮小されたバージョンのみを表示します(イメージ、リンクなどはありません)。このトリミングは必ずしも 100% 正確であるとは限りません。そのため、Cantataはデフォルトでイントロダクションのみ表示します。全ての記事を表示すると、解析エラーが発生する可能性があります。また、( 'キャッシュ'ページを使用して)現在キャッシュされている記事を削除する必要があります。 Images (*.png *.jpg) 画像ファイル (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder フォルダの選択 Select File ファイルの選択 PlayQueueModel Title タイトル Artist アーティスト Album アルバム # Track number Length 時間 Disc ディスク Year Original Year Genre ジャンル Priority プライオリティ Composer 作曲家 Performer 演奏者 Rating レーティング Remove Duplicates 複製を削除 Undo 元に戻す Redo やり直し Shuffle シャッフル Tracks トラック Albums アルバム Sort By ソートする Album Artist アルバムアーティスト Track Title トラック名 Track Number トラック番号 # (Track Number) # (トラック番号) PlayQueueView Remove 削除 PlaybackSettings Playback 再生 Fa&deout on stop: 停止時にフェードアウトさせる(&D): None なし ms ms Stop playback on exit 終了時に再生を停止する Inhibit suspend whilst playing 再生時にサスペンドを禁止する If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) 停止ボタンを押し続けると、現在再生中のトラックを停止するか、現在のトラックの後に停止するかを選択できるメニューが表示されます。(停止ボタンは、インターフェース / ツールバー セクションで有効にすることができます) Output 出力 <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>未接続!<br/>以下のエントリはCantataがMPDに接続されていないため、変更できません。</i> &Crossfade between tracks: トラック間のクロスフェード(&C): s s Replay &gain: リプレイゲイン(&G): About replay gain replay gain について Use the checkboxes below to control the active outputs. 下のチェックボックスを使用して、アクティブ出力を制御します。 Track トラック Album アルバム Auto 自動 <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>%1 に接続済<br/>以下のエントリは、現在接続中のMPDコレクションに適用されます。</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gainは、MP3やOgg Vorbisなどのコンピュータオーディオフォーマットで知覚される音量を標準化するために2001年に発表され提案された標準規格です。これはトラック/アルバムベースで動作し、現在、ますます多くのプレーヤーでサポートされています。<br/><br/>以下の通り、リプレイゲイン設定が使用されます:<ul><li><i>なし</i> - リプレイゲインが適用されません。</li><li><i>トラック</i> - 各楽曲のReplayGainタグ情報でボリウムが調整されます。</li><li><i>アルバム</i> - アルバムのReplayGainタグ情報でボリウムが調整されます。</li><li><i>自動</i> - ランダムプレイが有効な場合は楽曲のReplayGainタグを使用してボリウムが調整され、それ以外の場合はアルバムのタグが使用されます。</li></ul> PlaylistRule Type: タイプ: Include songs that match the following: 次のものと一致する曲を含む: Exclude songs that match the following: 次のものと一致しない曲を含む: Artist: アーテイスト: Artists similar to: 同様のアーティスト: Album Artist: アルバムアーティスト: Composer: 作曲家: Album: アルバム: Title: タイトル: Genre ジャンル From Year: 開始年: Any To Year: 終了年: Comment: コメント: Filename / path: Exact match 完全に一致 Only enter values for the tags you wish to be search on. 検索したいタグの値のみを入力してください。 For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. ジャンルについては、さまざまなジャンルに一致するようにアスタリスクで終了文字列を指定します。 例えば 'rock *'は 'Hard Rock'と 'Rock and Roll'に一致します。 PlaylistRuleDialog Dynamic Rule ダイナミックルール Smart Rule Add 追加 <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>エラー</b>: '開始年' は '終了年' より小さい必要があります</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>エラー:</b> 日付範囲が大きすぎます (最大でも %1 年間以内である必要あり)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules ダイナミックルールの名称 Add 追加 Edit 編集 Remove 削除 Songs with ratings between: - - Songs with duration between: seconds Number of songs in play queue: Order songs: About Rules ルールについて PlaylistRulesDialog Dynamic Rules ダイナミックルール None なし No Limit About dynamic rules ダイナミックルールについて Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 %1 の保存に失敗 A set of rules named '%1' already exists! Overwrite? '%1' という名前のルールセットは既に存在します! 上書きしますか? Overwrite Rules ルールの上書 Saving %1 %1 の保存中 PlaylistsModel New Playlist... 新規プレイリスト... Stored Playlists 保存済プレイリスト Standard playlists 標準プレイリスト %n Tracks (%1) %n トラック (%1) Smart Playlist スマートプレイリスト Plurals %n Track(s) %n トラック %n Tracks (%1) %n トラック (%1) %n Album(s) %n アルバム %n Artist(s) %n アーティスト %n Stream(s) %n ストリーム %n Entry(s) %n エントリ %n Rule(s) %n ルール %n Podcast(s) %n Podcasts %n Episode(s) %n エピソード %n Update(s) available %n 更新あり PodcastPage RSS: RSS: Website: Webサイト: Podcast details Podcast 詳細 Select a podcast to display its details 詳細を表示する podcast を選択してください PodcastSearchDialog Subscribe 購読する Enter URL URL を入力 Manual podcast URL マニュアル podcast URL Search %1 %1 を検索 Search for podcasts on %1 %1 の podcast を検索 Add Podcast Subscription Podcast の購読を追加する Browse %1 %1 をブラウズ Browse %1 podcasts %1 podcast をブラウズ You are already subscribed to this podcast! 既にこの podcast を購読済みです! Subscription added 購読を追加 PodcastSearchPage Enter search term... 検索文字列を入力... Search 検索 Failed to fetch podcasts from %1 %1 からの podcast の取得に失敗 There was a problem parsing the response from %1 %1 からのレスポンスの解析に問題が発生しました PodcastService Subscribe to RSS feeds RSS フィードを購読 %n Podcast(s) %n Podcasts %1 (%2) podcast name (num unplayed episodes) %n Episode(s) %n エピソード (Downloading: %1%) (ダウンロード中: %1%) Failed to parse %1 %1 の解析に失敗 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata は音声 podcast のみサポートします! %1 はビデオ potcast のみです。 Failed to download %1 %1 のダウンロードに失敗しました PodcastSettingsDialog Check for new episodes: 新規エピソードをチェック: Download episodes to: エピソードのダウンロード先: Download automatically: 自動的にダウンロード: Podcast Settings Podcast 設定 Manually 手動 Every 15 minutes 15分毎 Every 30 minutes 30分毎 Every hour 1時間毎 Every 2 hours 2時間毎 Every 6 hours 6時間毎 Every 12 hours 12時間毎 Every day 毎日 Every week 毎週 Don't automatically download episodes エピソードの自動ダウンロどーを行わない Latest episode 最新のエピソード Latest %1 episodes 最新の %1 エピソード All episodes 全エピソード PodcastUrlPage URL URL Enter podcast URL... podcast URL を入力... Load 読込 Enter podcast URL below, and press 'Load' podcast URL を以下で入力し ’読込’ を押してください Invalid URL! 無効なURL! Failed to fetch podcast! podcast の取得に失敗しました! Failed to parse podcast. podcast の解析に失敗しました。 Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata は音声 podcast のみサポートします! 入力された URL はビデオ potcast のみです。 PodcastWidget Add Subscription 購読を追加 Remove Subscription 購読の削除 Download Episodes エピソードのダウンロード Delete Downloaded Episodes ダウンロード済エピソードの削除 Cancel Download ダウンロードのキャンセル Mark Episodes As New エピソードを新規としてマーキング Mark Episodes As Listened エピソードを視聴済みとしてマーキング Show Unplayed Only Unsubscribe from '%1'? '%1' から購読削除しますか? Do you wish to download the selected podcast episodes? 選択した podcast エピソードをダウンロードしますか? Cancel podcast episode downloads (both current and any that are queued)? ポッドキャストのエピソードのダウンロードをキャンセルしますか(現在のものとキュー内の双方)? Do you wish to the delete downloaded files of the selected podcast episodes? 選択済 podcast エピソードのダウンロード済みファイルを削除しますか? Do you wish to mark the selected podcast episodes as new? 選択済 podcast エピソードを新規としてマーキングしますか? Do you wish to mark the selected podcast episodes as listened? 選択済 podcast エピソードを視聴済みとしてマーキングしますか? Refresh all subscriptions? 全ての購読を更新しますか? Refresh 更新 Refresh All 全て更新 Refresh all subscriptions, or only those selected? 全ての購読もしくは選択済みを更新しますか? Refresh Selected 選択済みを更新 PowerManagement Cantata is playing a track Cantata はトラックを再生中 PreferencesDialog Collection コネクション Collection Settings コレクションの設定 Playback 再生 Playback Settings 再生設定 Downloaded Files ダウンロード済ファイル Downloaded Files Settings ダウンロード済ファイルの設定 Interface インタフェース Interface Settings インタフェースの設定 Info 情報 Info View Settings 情報ビューの設定 Scrobbling Scrobbing Scrobbling Settings Scrobbing 設定 Audio CD オーディオCD Audio CD Settings Audio CD 設定 Proxy プロキシ Proxy Settings プロキシ設定 Shortcuts ショートカット Keyboard Shortcut Settings キーボード ショートカット 設定 Cache キャッシュ Cached Items キャッシュ済アイテム Custom Actions カスタムアクション Cantata Preferences Cantata の設定 Configure 設定 ProxySettings Mode: モード: Type: タイプ: HTTP Proxy HTTP プロキシ SOCKS Proxy SOCKS プロキシ Host: ホスト: Port: ポート: Username: ユーザ名: Password: パスワード: No proxy プロキシなし Use the system proxy settings システムプロキシ設定を使用 Manual proxy configuration 手動プロキシ設定 QObject Track listing トラックリスト Read more on wikipedia wikipedia で更に確認 Open in browser ブラウザで開く <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href="http://en.wikipedia.org/wiki/Advanced_Audio_Coding">Advanced Audio Coding</a> (AAC) はディジタルオーディオ向けの特許取得済み非可逆コーデックです。<br>AACは、一般に、類似のビットレートのMP3よりも優れた音質を実現します。 これは、iPodやその他のポータブルミュージックプレーヤーにとっては合理的な選択です。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataで使用される<b> AAC </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR">可変ビットレート(VBR)</a>の設定をサポートしており、これを使用した場合ビットレート値はオーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレートの数値は、エンコードされたトラックの<a href="http://www.ffmpeg.org/faq.html#SEC21">平均ビットレート</a>の見積もりにすぎません。<br>ポータブルプレーヤーで音楽を聴くには、<b>150kb/s</b>が適しています。<br/><b>120kb/s</b>未満は音楽には不満足で、<b>200kb/s</b>以上はおそらく過剰です。 Expected average bitrate for variable bitrate encoding 可変ビットレート符号化の予想平均ビットレート Smaller file より小さなファイル Better sound quality より良いサウンド品質 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href="http://en.wikipedia.org/wiki/MP3">MPEG Audio Layer 3</a> (MP3) は、不可逆データ圧縮形式を使用した特許取得済みのデジタルオーディオコーデックです。<br>その欠点にもかかわらず、コンシューマオーディオストレージの一般的なフォーマットであり、携帯音楽プレーヤーで広くサポートされています。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataが使用する<b>MP3</b>エンコーダは、<a href="http://en.wikipedia.org/wiki/MP3#VBR">可変ビットレート(VBR)</a>の設定をサポートしており、ビットレート値は、オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレート測定値は、符号化されたトラックの平均ビットレートの推定値にすぎません。<br><b> 160kb/s</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<b>120kb/s</b>未満は音楽に不満足かもしれませんし、<b>205kb/s</b>以上はおそらく過剰です。 Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href="http://en.wikipedia.org/wiki/Vorbis">Ogg Vorbis</a>は、非可逆オーディオ圧縮のためのオープンでロイヤリティフリーのオーディオコーデックです。<br>同等以上の品質でMP3よりも小さいファイルを作成します。Ogg Vorbisは、特にそれをサポートするポータブルミュージックプレイヤーにとって、万能の優れた選択肢です。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataで使用される<b> Vorbis </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder">可変ビットレート(VBR)</a>の設定をサポートしており、ビットレート値は、オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化される。このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>Vorbisエンコーダは、-1〜10の間の品質評価を使用して、特定の期待されるオーディオ品質レベルを定義します。このスライダのビットレート測定値は、品質値が与えられた符号化トラックの平均ビットレートの概算値(Vorbis提供)です。実際、より新しい、より効率的なVorbisバージョンでは、実際のビットレートはさらに低くなります。<br><b>5</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<br/><b>3</b>以下は音楽には不満足で、<b>8</b>以上ははおそらく過剰です。 Quality rating 品質評価 Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href="http://en.wikipedia.org/wiki/Opus_(audio_format)"> Opus </a>は、非可逆データ圧縮形式を使用した特許のないデジタルオーディオコーデックです。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br>Cantataが使用する<b> Opus </b>エンコーダは、<a href="http://en.wikipedia.org/wiki/Variable_bitrate">可変ビットレート(VBR)</a>設定をサポートしており、これは、ビットレート値 オーディオコンテンツの複雑さに基づいてトラックに沿って変動します。データのより複雑な区間は、複雑でない区間よりも高いビットレートで符号化されます。; このアプローチは、トラック全体で一定のビットレートを使用するよりも、全体的に優れた品質とファイルサイズを実現します。<br>このため、このスライダのビットレート測定値は、符号化されたトラックの平均ビットレートの推定値にすぎません。<br><b>128kb/s</b>は、ポータブルプレーヤーで音楽を聴くのに適しています。<br/><b>100kb/s</b>未満は音楽に不満があり、<b>256kb/s</b>以上はおそらく過剰です。 Bitrate ビットレート Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href="http://en.wikipedia.org/wiki/Apple_Lossless"> Apple Lossless </a>(ALAC)は、デジタル音楽のロスレス圧縮のためのオーディオコーデックです。<br>Apple Music PlayerおよびFLACをサポートしないプレーヤにおすすめです。 FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href="http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec">フリーロスレスオーディオコーデック</a>(FLAC)は、デジタル音楽の可逆圧縮のためのオープンでロイヤリティフリーのコーデックです。<br> オーディオ品質を損なうことなく音楽を保存するには、FLACは優れた選択肢です。 The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href="http://flac.sourceforge.net/documentation_tools_flac.html">圧縮レベル</a>は、<b> FLACでエンコードする際のファイルサイズと圧縮速度のトレードオフを表す0〜8の整数値です </b>。<br/>圧縮レベルを<b>0</b>に設定すると、圧縮時間は最も短くなりますが、ファイルサイズは比較的大きくなります。<br/>一方、圧縮レベルが<b>8</b>の場合、圧縮はかなり遅くなりますが、ファイルが最も小さくなります。<br/>FLACはロスレスコーデックであるため、圧縮レベルに関係なく出力のオーディオ品質はまったく同じです。<br/>また、<b>5</b>以上のレベルでは圧縮時間が大幅に増加しますが、ファイルサイズはわずかに小さくなるだけなので、推奨しません。 Compression level 圧縮レベル Faster compression より早い圧縮 Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href="http://en.wikipedia.org/wiki/Windows_Media_Audio"> Windows Media Audio </a>(WMA)は、損失の多いオーディオ圧縮のためにMicrosoftによって開発された独自のコーデックです。<br>Ogg Vorbisをサポートしていない携帯音楽プレーヤーにのみおすすめです。 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. ビットレートは、そのオーディオトラックが1秒辺りに使用するデータの量の尺度を示しています。<br><b>WMA</b>形式独自のの制限と独自エンコーダのリバースエンジニアリングの難しさのため、Cantataが使用するWMAエンコーダは<a href="http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio">固定ビットレート (CBR)</a> 設定のみ使用します。<br>この理由から、このスライダーのビットレートは、コード化されたトラックのビットレートのかなり正確な見積もりです。<b>136kb/s</b>は、ポータブルプレーヤーでの音楽聴取に適しています。 <br/><b>112kb/s</b>未満は音楽には不満足で、<b>182kb/s</b>以上はおそらく過剰です。 Empty filename. ファイル名なし。 Invalid filename. (%1) 無効なファイル名。 (%1) Failed to save %1. %1 への保存に失敗しました。 Failed to delete rules file. (%1) ルールファイルの削除に失敗しました。 (%1) Invalid command. (%1) 無効なコマンドです。 (%1) Could not remove active rules link. アクティブなルールリンクの削除が出来ません。 Active rules is not a link. アクティブなルールはリンクではありません。 Could not create active rules link. アクティブルールリンクの生成に失敗しました。 Rules file, %1, does not exist. ルールファイル %1 は存在しません。 Incorrect arguments supplied. 不適切な引数が指定されました。 Unknown method called. 不明なメソッドが呼ばれました。 Unknown error 不明なエラー Artist アーティスト SimilarArtists 関連アーティスト AlbumArtist アルバムアーティスト Composer 作曲家 Comment コメント Album アルバム Title タイトル Genre ジャンル Date 日付 File Include Exclude (Exact) (一致) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 name width x height Current Cover 現在のカバー CoverArt Archive カバーアート アーカイブ Grouped Albums グループ化されたアルバム Table テーブル Parse in Library view, and show in Folders view ライブラリビュー内で解析され、フォルダビュー内で表示 Only show in Folders view フォルダービュー内のみで表示 Do not list 表記しない %1 Tracks Plural (N!=1) %1 トラック 1 Track (%1) Singular 1 トラック (%1) %1 Tracks (%2) Plural (N!=1) %1 トラック (%2) %1 Albums Plural (N!=1) %1 アルバム %1 Artists Plural (N!=1) %1 アーティスト %1 Streams Plural (N!=1) %1 ストリーム %1 Entries Plural (N!=1) %1 エントリ %1 Rules Plural (N!=1) %1 ルール %1 Podcasts Plural (N!=1) %1 Podcasts %1 Episodes Plural (N!=1) %1 エピソード %1 Updates available Plural (N!=1) %1 更新あり Previous Track 前のトラック Next Track 次のトラック Play/Pause 再生/一時停止 Stop 停止 Stop After Current Track 現在トラック再生後停止 Stop After Track トラック再生後停止 Increase Volume 音量Up Decrease Volume 音量Down Save As 別名で保存 Append 追加 Append To Play Queue プレイキューの末尾に追加 Append And Play 末尾に追加し再生 Add And Play 追加し再生 Append To Play Queue And Play プレイキューの末尾に追加し再生 Insert After Current 現楽曲の直後に挿入 Append Random Album ランダムアルバムの末尾に追加 Play Now (And Replace Play Queue) 直ちに再生(し、プレイキューを更新) Add With Priority プライオリティ付きで追加 Set Priority プライオリティの設定 Highest Priority (255) 最高位のプライオリティ(255) High Priority (200) 高プライオリティ(200) Medium Priority (125) 中プライオリティ(125) Low Priority (50) 低プライオリティ(50) Default Priority (0) 規定のプライオリティ(0) Custom Priority... カスタム プライオリティ... Add To Playlist プレイリストに追加 Organize Files ファイルの整理 Edit Track Information トラック情報の編集 ReplayGain リプレイゲイン Copy Songs To Device 楽曲をデバイスに複写 Delete Songs 楽曲を削除 Set Image イメージの設定 Remove 削除 Find 検索 Add To Play Queue プレイキューへの追加 Parse error loading cache file, please check your songs tags. キャッシュファイル読込中の解析エラーです。楽曲タグを確認してください。 Other その他 Default 規定 "%1" (%2:%3) name (host:port) Single Tracks シングルトラック Personal パーソナル Unknown 不明 Various Artists Various Artists Album artist アルバム アーティスト Performer 演奏者 Track number トラック番号 Disc number ディスク番号 Year Orignal Year Length 時間 <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> on <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> by <b>%2</b> on <b>%3</b> Invalid service 無効なサービス Invalid method 無効なメソッド Authentication failed 認証に失敗 Invalid format 無効な書式 Invalid parameters 無効なパラメータ Invalid resource specified 無効なリソースが指定されました Operation failed 操作に失敗しました Invalid session key 無効なセッションキー Invalid API key 無効なAPIキー Service offline サービスがオフライン Last.fm is currently busy, please try again in a few minutes Last.fm は現在ビジー状態です。数分後に再試行してください Rate-limit exceeded レート制限を超えました General 一般設定 Digitally Imported Local and National Radio (ListenLive) ローカルと全国ラジオ (ListenLive) &OK &OK &Cancel &Cancel &Yes &Yes &No &No &Discard 破棄(&D) &Save 保存(&S) &Apply 適用(&A) &Close 閉じる(&C) &Help ヘルプ(&H) &Overwrite 上書(&O) &Reset リセット(&R) &Continue 継続(&C) &Delete 削除(&D) &Stop 停止(&S) &Remove 削除(&R) &Previous 前(&P) &Next 後(&N) Close 閉じる Error エラー Information 情報 Warning 警告 Question 質問 %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) ベーシックツリー(アイコンなし) Simple Tree シンプルツリー Detailed Tree 詳細ツリー List リスト Grid グリッド %n Track(s) %n トラック %n Tracks (%1) %n トラック (%1) %n Album(s) %n アルバム %n Artist(s) %n アーティスト %n Stream(s) %n ストリーム %n Entry(s) %n エントリ %n Rule(s) %n ルール %n Podcast(s) %n Podcasts %n Episode(s) %n エピソード %n Update(s) available %n 更新あり RemoteDevicePropertiesDialog Device Properties デバイスプロパティ Connection 接続 Music Library ミュージックライブラリ Add Device デバイスを追加 A remote device named '%1' already exists! Please choose a different name. '%1' という名前のリモートデバイスは既に存在します! 他の名前を選択してください。 RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. これらの設定はデバイスが未接続時のみ編集可能です。 Type: タイプ: Name: 名前: Options オプション Host: ホスト: Port: ポート: User: ユーザー: Domain: ドメイン: Password: パスワード: Share: シェア: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' パスワードをここに入力した場合、Cantata のコンフィグファイル内に<b>未暗号化状態</b>で保存されます。共有にアクセスする前にカンタータのパスワードプロンプトを表示するには、パスワードを ' - 'に設定します Service name: サービス名: Folder: フォルダ: Extra Options: 追加オプション: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. sshfsの仕組みのため、適切なssh-askpassアプリケーション(ksshaskpass、ssh-askpass-gnomeなど)がパスワードを入力する必要があります。 This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. このダイアログは、リモートデバイスを(Samba経由などで)追加する場合や、ローカルにマウントされたフォルダにアクセスする場合にのみ使用します。 USBを介して接続された通常のメディアプレーヤーの場合、Cantataはデバイスが接続されると自動的にデバイスを表示します。 Samba Share Samba 共有 Samba Share (Auto-discover host and port) Samba 共有(ホストとポートを自動検索) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder ローカルにマウントされたフォルダ RemoteFsDevice Available 利用可能 Not Available 利用不可 Failed to resolve connection details for %1 %1への接続詳細を解決できませんでした Connecting... 接続中... Password prompting does not work when cantata is started from the commandline. Cantata をコマンドラインから起動した際はパスワードプロンプトが正常に動作しません。 No suitable ssh-askpass application installed! This is required for entering passwords. 適切なSSH-askpass アプリケーションがインストールされていません!パスワード入力のために必要です。 Mount point ("%1") is not empty! マウントポイント ("%1") は空ではありません! "sshfs" is not installed! "sshfs" がインストールされていません! Disconnecting... 切断中... "fusermount" is not installed! "fusemount" がインストールされていません! Failed to connect to "%1" "%1" への接続に失敗しました Failed to disconnect from "%1" ”%1” からの切断に失敗しました Updating tracks... トラックの更新中... Not Connected 未接続 Capacity Unknown 容量不明 %1 free %1 空き RgDialog ReplayGain リプレイゲイン Show All Tracks 全トラックの表示 Show Untagged Tracks タグ付けされていないトラックの表示 Remove From List リストから削除 Artist アーティスト Album アルバム Title タイトル Album Gain アルバムゲイン Track Gain トラックゲイン Album Peak アルバムピーク Track Peak トラックピーク Scan スキャン Update ReplayGain tags in tracks? トラック内のReplayGainタグを更新しますか? Update Tags タグの更新 Abort scanning of tracks? トラックのスキャンを中止しますか? Abort 中止 Abort reading of existing tags? 存在するタグの読込を中止しますか? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> <b>全</b>トラックをスキャンしますか?<br/><br/><i>すべてのタグにはReplayGainタグがあります。</i> Do you wish to scan all tracks, or only tracks without existing tags? すべてのトラックをスキャンしますか、それともタグなしトラックをスキャンしますか? Untagged Tracks 未タグ付けトラック All Tracks すべてのトラック Scanning tracks... トラックのスキャン中... Reading existing tags... 存在するタグを読み込み中... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (破損タグ?) Failed to update the tags of the following tracks: 以下のトラックのタグ更新に失敗しました: Device has been removed! デバイスは取り外しされました! Device is not connected. デバイスが未接続です。 Device is busy? デバイスが使用中? %1 dB %1 dB Failed 失敗 Original: %1 dB オリジナル: %1 dB Original: %1 オリジナル: %1 Remove the selected tracks from the list? リストから選択済トラックを削除しますか? Remove Tracks トラックの削除 RulesPlaylists - Rating: %1..%2 - レーティング: %1..%2 Album Artist アルバムアーティスト Artist アーティスト Album アルバム Composer 作曲家 Date 日付 Genre ジャンル Rating レーティング File Age Random ランダム %n Rule(s) %n ルール , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 エラー: %2 ScrobblingLove %1: Loved Current Track %1: 現トラックをラブ設定しました %1: Love Current Track %1: 現トラックをラブ設定する ScrobblingSettings Scrobble using: Scrobble 使用: Username: ユーザ名: Password: パスワード: Status: ステータス: Login ログイン Scrobble tracks Scrobble トラック Show 'Love' button 'Love' ボタンを表示する %1 (via MPD) scrobbler name (via MPD) %1 (MPD 経由) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. '(MPD経由)'とマークされたscrobblerを使用している場合(%1など)は、これを既に起動して実行しておく必要があります。 Cantataはこれを介してトラックのみを'ラブ' 設定することができ、scrobblingを有効または無効にすることはできません。 Authenticating... 認証中... Authenticated 認証済み Not Authenticated 未認証状態 ScrobblingStatus %1: Scrobble Tracks %1: トラックをScrobble SearchModel # (Track Number) # (トラック番号) SearchPage Locate In Library ライブラリ内での捜索 Artist: アーテイスト: Composer: 作曲家: Performer: 演奏者: Album: アルバム: Title: タイトル: Genre: ジャンル: Comment: コメント: Date: 日付: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. ’Date’ タグで楽曲を検索します<br/><br/>通常、年を入力するだけで十分です。 Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: 変更済: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. ファイルが変更された日付(YYYY/MM/DD - 例 2015/01/31)。<br/><br>または、ファイルの修正されてからの経過日数を確認するためは日数指定して下さい。 File: ファイル: Any: 全検索: No tracks found. トラックが見つかりません。 %n Tracks (%1) %n トラック (%1) SearchWidget Search... 検索... Close Search Bar 検索バーを閉じる ServerSettings Collection: コレクション: Name: 名前: Host: ホスト: Password: パスワード: Music folder: 音楽フォルダ: Cover filename: カバーファイル名: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>ダウンロードしたカバーを保存するためのファイル名(拡張子なし)。<br/>空白のままにしておくと、「cover」が使用されます。<br/><br/><i>%artist% は現在の曲のアルバムアーティストに置き換えられ、%album% はアルバム名に置き換えられます。</i></p> HTTP stream URL: HTTP ストリーム URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. 「音楽フォルダ」の設定は、カバーアートを検索するために使用されます。MPDが別のマシンにあり、HTTPを介してカバーにアクセスできる場合は、HTTP URLに設定されます。それがHTTP URLに設定されておらず、このフォルダ(およびそのサブフォルダ)への書き込み権限もある場合、Cantataはダウンロードしたすべてのカバーをそれぞれのアルバムフォルダに保存します。 If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'カバー ファイル名'を特定しない場合、Cantata は規定値の<code>cover</code> を使用する 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. 「HTTP Stream URL」は、HTTPストリームに出力するようにMPDが設定されていて、Cantataがそのストリームを再生できるようにしたい場合にのみ使用します。 If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. 「ミュージックフォルダ」の設定を変更した場合、音楽データベースを手動で更新する必要があります。これは、 'アーティスト' または 'アルバム' ビューの 'データベースの更新' ボタンを押すことで実行できます。 This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? どのタイプのコレクションに接続しますか? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) 標準 - ミュージックコレクションが共有されている、他のマシーンにあり既にセットアップ済、もしくは他のクライアントからアクセスを許可したい場合(例 MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. 基本 - ミュージックコレクションが他の端末と共有されておらず、Cantata はMPDインスタンスを設定し制御します。本セットアップは Cantata でのみ適用され、他のMPDクライアントはアクセス<b>出来ません</b>。 <i><b>NOTE:</b> %1</i> <i><b>追記:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' 高度なMPD設定(複数のオーディオ出力、完全なDSDサポートなど)を希望する場合、'標準'を選択する<b>必要があります</b> Add Collection コレクションの追加 Standard 標準 Basic 基本 Delete '%1'? ’%1’ を削除しますか? Delete 削除 New Collection %1 新規コレクション %1 Default 規定 ServiceStatusLabel Logged into %1 %1 へログイン <b>NOT</b> logged into %1 %1 にログイン<b>しない</b> ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: 検索: Shortcut for Selected Action 選択アクションへのショートカット Default: 既定: None なし Custom: カスタム: SinglePageWidget Refresh 更新 View ビュー SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add 追加 Edit 編集 Remove 削除 Are you sure you wish to remove the selected rules? This cannot be undone. 選択済みのルールを削除しますか? この操作はやり直しできません。 Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. 楽曲ファイルへアクセスできません! Cantata の”ミュージックフォルダ” とMPD の "music_drectory" 設定を確認してください。 Cannot access song files! Please check that the device is still attached. 楽曲ファイルへアクセスできません! デバイスが未だに接続されているかを確認してください。 SongView Lyrics 歌詞 Information 情報 Metadata メタデータ Scroll Lyrics 歌詞をスクロール Refresh Lyrics 歌詞を更新 Edit Lyrics 歌詞を編集 Delete Lyrics File 歌詞ファイルを削除 Refresh Track Information トラック情報を更新 Cancel キャンセル Track トラック Reload lyrics? Reload from disk, or delete disk copy and download? 瑕疵を再読込しますか? ディスクから再読込もしくはディスクから削除しダウンロードし直しますか? Reload 再読込 Reload From Disk ディスクから再読込 Download ダウンロード Current playing song has changed, still perform search? 現在の再生中楽曲は変更しました。検索を継続しますか? Song Changed 楽曲が変更されました Perform Search 検索を実行する Delete lyrics file? 歌詞ファイルを削除しますか? Delete File ファイルの削除 Artist アーティスト Album artist アルバム アーティスト Composer 作曲家 Lyricist 作詞家 Conductor 指揮者 Remixer レミキサー Album アルバム Subtitle サブタイトル Track number トラック番号 Disc number ディスク番号 Genre ジャンル Date 日付 Original date オリジナル日付 Comment コメント Copyright 版権 Label ラベル Catalogue number カタログ番号 Title sort タイトル ソート Artist sort アーティスト ソート Album artist sort アルバムアーティスト ソート Album sort アルバム ソート Encoded by エンコード Encoder エンコーダ Mood ムード Media メディア Bitrate ビットレート Sample rate サンプルレート Channels チャンネル Tagging time タグ付け時間 Performer (%1) 演奏者 (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits ビット Performer 演奏者 Year Filename ファイル名 Fetching lyrics via %1 %1 より歌詞を取得中 SoundCloudService Search for tracks from soundcloud.com soundcloud.com からのトラックを検索 SpaceLabel Calculating... 計算中... Total space used: %1 全使用容量: %1 SqlLibraryModel %n Artist(s) %n アーティスト %n Album(s) %n アルバム %n Tracks (%1) %n トラック (%1) Cue Sheet キューシート Playlist プレイリスト StoredPlaylistsPage Rename 名前を変更 Remove Duplicates 複製を削除 Initially Collapse Albums 初期動作時はアルバムをたたむ Are you sure you wish to remove the selected playlists? This cannot be undone. 選択したプレイリストを削除しますか? この操作はやり直しできません。 Remove Playlists プレイリストの削除 Playlist Name プレイリスト名 Enter a name for the playlist: プレイリスト名を入力: A playlist named '%1' already exists! Overwrite? '%1' というプレイリストは既に存在します 上書しますか? Overwrite Playlist プレイリストの上書 Rename Playlist プレイリストの名前変更 Enter new name for playlist: プレイリストの新しい名前を入力: Cannot add songs from '%1' to '%2' '%1' から '%2' へ楽曲の追加が出来ません StreamDialog Add stream to favourites Name: 名前: URL: URL: Add Stream ストリームに追加 Edit Stream ストリームの編集 <i><b>ERROR:</b> Invalid protocol</i> <i><b>エラー:</b> 無効なプロトコル</i> StreamFetcher Loading %1 %1 読込中 StreamProviderListDialog Installed インストール済 Update available 利用可能な更新 Check the providers you wish to install/update. インストール/アップデートするプロバイダを確認します。 Install/Update Stream Providers ストリームプロバイダのインストール/更新 Downloading list... リストのダウンロード中... Failed to download list of stream providers! ストリームプロバイダリストのダウンロードに失敗しました! Installing/updating %1 %1 のインストール/更新中 Failed to install '%1' '%1' のインストールに失敗 Failed to download '%1' '%1' のダウンロードに失敗 Install/update the selected stream providers? 選択したストリームプロバイダをインストール/更新しますか? Install the selected stream providers? 選択したストリームプロバイダをインストールしますか? Update the selected stream providers? 選択したストリームプロバイダを更新しますか? Install/Update インストール/更新 Abort installation/update? インストール/更新を中止しますか? Abort 中止 %n Update(s) available %n 更新あり Downloading %1 %1 ダウンロード中 Update all updateable providers 全ての更新可能プロバイダを更新 StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search ストリーム検索 Search for radio streams ラジオストリームの検索 Enter string to search 検索文字列を入力してください Not Loaded 未読込 Loading... 読込中... %n Entry(s) %n エントリ StreamSearchPage Added '%1'' to favorites '%1' をお気に入りに追加しました StreamsBrowsePage Import Streams Into Favorites ストリームからお気に入りにインポート Export Favorite Streams お気に入りをストリームにエクスポート Add New Stream To Favorites 新規ストリームをお気に入りに追加 Edit 編集 Seatch For Streams ストリームを検索 Configure 設定 Digitally Imported Service name Import Streams ストリームのインポート XML Streams (*.xml *.xml.gz *.cantata) XML ストリーム (*.xml *.xml.gz *.cantata) Export Streams ストリームのエクスポート XML Streams (*.xml.gz) XML ストリーム (*.xml.gz) Failed to create '%1'! '%1' の作成に失敗しました! Stream '%1' already exists! ストリーム '%1' は既に存在します! A stream named '%1' already exists! '%1' と言う名前のストリームは既に存在します! Bookmark added ブックマークが追加されました Already bookmarked 既にブックマークが存在する Already in favorites 既にお気に入りに存在する Reload '%1' streams? '%1' ストリームを再読み込みしますか? Are you sure you wish to remove bookmark to '%1'? 本当に '%1' へのブックマークを削除しますか? Are you sure you wish to remove all '%1' bookmarks? 本当に全ての '%1' ブックマークを削除しますか? Are you sure you wish to remove the %1 selected streams? 本当に '%1' 選択済みストリームを削除しますか? Are you sure you wish to remove '%1'? 本当に '%1' を削除しますか? Added '%1'' to favorites '%1' をお気に入りに追加しました StreamsModel Bookmarks ブックマーク TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites お気に入り Bookmark Category ブックマーク カテゴリ Add Stream To Favorites ストリームをお気に入りに追加 Configure Digitally Imported デジタルでのインポートを設定 Reload 再読込 Streams ストリーム Radio stations ラジオステーション Not Loaded 未読込 Loading... 読込中... %n Entry(s) %n エントリ StreamsSettings Use the checkboxes below to configure the list of active providers. アクティブなプロバイダのリストを設定するには、以下のチェックボックスを使用します。 Built-in categories are shown in italic, and these cannot be removed. 組み込みのカテゴリはイタリック体で表示され、削除することはできません。 Configure Streams ストリームの設定 From File... Download... ダウンロード... Configure Provider プロバイダの設定 Install インストール Remove 削除 Install Streams ストリームのインストール Cantata Streams (*.streams) Cantata ストリーム (*.streams) A category named '%1' already exists! Overwrite? '%1' と言う名前のカテゴリは既に存在します! 上書しますか? Failed top open package file. パッケージファイルを開くことができません。 Invalid file format! 無効なファイルフォーマットです! Failed to create stream category folder! ストリームカテゴリフォルダの作成に失敗しました! Failed to save stream list! ストリームリストの保存に失敗しました! Are you sure you wish to remove '%1'? 本当に '%1' を削除しますか? Failed to remove streams folder! ストリームフォルダの削除に失敗しました! SyncCollectionWidget Search 検索 Check Items アイテムにチェックを入れる Uncheck Items アイテムのチェックを外す SyncDialog Library: ライブラリ: Device: デバイス: Loading all songs from library, please wait... ライブラリから全楽曲を読込中、お待ちください... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>ライブラリ</code>には、ライブラリにはあるが、デバイスにはない曲だけが表示されます。 同様に、<code>デバイス</code>にはデバイスにのみにある曲がリストされます。<br/><code>デバイス</code>にコピーしたい楽曲を<code>ライブラリ</code>から選択し、<code>デバイス</code>から<code>ライブラリ </code>にコピーしたい楽曲を選択してください。その後<code>同期</code>ボタンを押します。 Synchronize 同期 Device and library are in sync. デバイスとライブラリは同期中です。 Loading all songs from library, please wait...%1%... ライブラリから全楽曲を読込中です、お待ちください...%1%... Local Music Library Properties ローカルミュージックライブラリのプロパティ Device has been removed! デバイスは取り外しされました! Device has been changed? デバイスが変更された? Device is busy? デバイスが使用中? TableView Stretch Columns To Fit Window 行がウィンドウサイズに合うように拡大する Left Center 中心 Right Alignment 位置合わせ TagEditor Track: トラック: Title: タイトル: Artist: アーテイスト: Album artist: アルバムアーティスト: Composer: 作曲家: Album: アルバム: Track number: トラック番号: Disc number: ディスク番号: Genre: ジャンル: Year: 年: Rating: レーティング: <i>(Various)</i> <i>(Various)</i> Comment: コメント: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') 複数のジャンルはコンマで区切る必要があります(例: 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. 評価は外部データベースに保存され、曲のファイルには格納され<b>ません</b>。 Tags タグ Tools ツール Apply "Various Artists" Workaround "Various Artists" による回避策を適用 Revert "Various Artists" Workaround "Various Artists" による回避策を元に戻す Set 'Album Artist' from 'Artist' 'アーティスト' から 'アルバムアーティスト' を設定 Capitalize 大文字に変更 Adjust Track Numbers トラック番号を調整 Read Ratings from File ファイルからレーティングを読込 Write Ratings to File ファイルにレーティングを書込 All tracks 全トラック Apply "Various Artists" workaround to <b>all</b> tracks? <b>全</b> トラックに "Various Artists" 回避策を適用しますか? Apply "Various Artists" workaround? "Various Artists" による回避策を適用しますか? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i> 'アルバムアーティスト' と 'アーティスト' を "Various Artists" に設定し、'タイトル' を "トラックアーティスト - トラックタイトル" に設定します</i> Revert "Various Artists" workaround on <b>all</b> tracks? <b>全</b> トラックへの "Various Artists" 回避策を元に戻しますか? Revert "Various Artists" workaround "Various Artists" 回避策を元に戻す <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>'アルバムアーティスト' が 'アーティスト' と同じで、 'タイトル' の書式が "トラックアーティスト - トラックタイトル" の場合、 'アーティスト' は 'タイトル' から取得し 'タイトル' はトラックタイトルとなります。例えば <br/><br/> 'タイトル' が "Wibble - Wobble" の場合、 'アーティスト' には "Wibble" が 'タイトル' には "Wobble" が設定されます</i> Revert 元に戻す Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? ('アルバムアーティスト' が空の際) <b>全ての</b> トラックに 'アルバムアーティスト' に 'アーティスト' を設定しますか? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? ('アルバムアーティスト' が空の際) 'アルバムアーティスト' に 'アーティスト' を設定しますか? Album Artist from Artist アーティスト から アルバムアーティスト設定 Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? <b>全ての</b> トラックのテキストフィールド1文字目を大文字にしますか(例えば 'Title', 'Artist'など)? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? テキストフィールド1文字目を大文字にしますか(例えば 'Title', 'Artist'など)? Adjust the value of each track number by: 各トラック番号を調整する: Adjust track number by: トラック番号を調整: Read ratings for all tracks from the music files? ミュージックファイルからの全トラックのレーティングを読込しますか? Read rating from music file? 音楽ファイルからレーティングを読込しますか? Ratings レーティング Read Ratings レーティングの読込 Read Rating レーティングの読込 Read, and updated, ratings from the following tracks: 以下のトラックからレーティングを読込,更新する: Not all Song ratings have been read from MPD! MPDからどの楽曲のレーティングも読み込みできません! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. 楽曲のレーティングは楽曲ファイルに保存されていませんが、MPDの’スティッカー' データベースに保存されています。これらの情報を実際のファイルに保存するには、CantataはまずMPDからこの情報を読込する必要があります。 Song rating has not been read from MPD! 楽曲のレーティングがMPDから読込されていません! Write ratings for all tracks to the music files? 音楽ファイルに全トラックのレーティングを書き込みしますか? Write rating to music file? 音楽ファイルにレーティングを書き込みしますか? Write Ratings レーティングの書込 Write Rating レーティングの書込 Failed to write ratings of the following tracks: 以下のトラックのレーティング書込に失敗: Failed to write rating to music file! 音楽ファイルへのレーティング書込に失敗! All tracks [modified] 全トラック [修正済] %1 [modified] %1 [修正済] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (破損タグ?) Failed to update the tags of the following tracks: 以下のトラックのタグ更新に失敗しました: Would you also like to rename your song files, so as to match your tags? 楽曲ファイル名をタグと一致するように変更しますか? Rename Files ファイル名を変更 Rename 名前を変更 Device has been removed! デバイスは取り外しされました! Device is not connected. デバイスが未接続です。 Device is busy? デバイスが使用中? TagSpinBox (Various) (Various) ThinSplitter Reset Spacing TitleWidget Click to go back クリックし戻る Add All To Play Queue プレイキューにすべて追加 Add All And Replace Play Queue すべて追加しプレイキューを書換 ToggleList Available: 利用可能: Selected: 選択済: TrackOrganiser Filenames ファイル名 Filename scheme: ファイル名体系: VFAT safe VFAT セーフ Use only ASCII characters ASCII文字のみを使用する Replace spaces with underscores スペースをアンダーバーに置換する Append 'The' to artist names アーティスト名に「The」を付ける Original Name オリジナル名称 New Name 新規名称 Ratings will be lost if a file is renamed. ファイル名が変更された際にレーティングは失われます。 Organize Files ファイルの整理 Rename 名前を変更 Remove From List リストから削除 Abort renaming of files? ファイル名の変更を中止しますか? Abort 中止 Source file does not exist! 入力元ファイルが存在しません! Skip スキップ Auto Skip オートスキップ Destination file already exists! 出力先ファイルは既に存在します! Failed to create destination folder! 出力先フォルダの作成に失敗しました! Failed to rename '%1' to '%2' '%1' から '%2' への名前変更に失敗しました Remove the selected tracks from the list? リストから選択済トラックを削除しますか? Remove Tracks トラックの削除 Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. 楽曲のレーティングは楽曲ファイル内には保存されていませんが、MPDの’sticker’データベースにはあります。 ファイルの名前を変更する(もしくは楽曲のあるフォルダ名を変更する)と、その楽曲へのレーティングは失われます。 Device has been removed! デバイスは取り外しされました! Device is not connected. デバイスが未接続です。 Device is busy? デバイスが使用中? TrayItem Cantata Canata Now playing 現再生中 UltimateLyricsProvider (Polish Translations) (ポーランド語 翻訳) (Portuguese Translations) (ポルトガル語 翻訳) UmsDevice Not Scanned 未スキャン Not Connected 未接続 %1 free %1 空き ValueSlider (recommended) (推奨) View Cancel キャンセル VolumeSlider Mute ミュート Unmute ミュート解除 Volume %1% (Muted) ボリューム %1% (ミュート中) Volume %1% Volume %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | アーティスト|バンド|歌手|ボーカリスト|ミュージシャン album|score|soundtrack Search pattern for an album, separated by | アルバム|楽譜|サウンドトラック WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. アーティストやアルバム情報の検索の際に使用するwikipediaでの言語を選択してください。 Reload 再読込 cantata-2.2.0/translations/cantata_ko.ts000066400000000000000000026330731316350454000203420ustar00rootroot00000000000000 Refresh Album Information 음반정보 새로 읽기 Album 음반 Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Refresh Artist Information 연주자정보 새로 읽기 Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) 연주자 Albums 음반 Web Links 웹 연결 Similar Artists 비슷한 연주자 Lyrics Providers 가사 찾기 Wikipedia Languages 위키피디아 언어 Other 기타 Reset Spacing 간격 새로 맞추기 &Artist 연주자(&A) Al&bum 음반(&b) &Track 곡(&T) Read more on last.fm last.fm에서 더 읽기 If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. 가사를 찾지 못하거나 잘못된 가사를 찾으면, 이 대화상자를 이용해서 새로운 검색을 입력하시기 바랍니다. 예로 지금 곡이 리메이크라면 원곡의 가사를 찾는 것이 도움이 될 수 있습니다. 만약 이 검색이 새로운 가사를 찾는다면, 원곡 이름이나 연주자와 여전히 연관이 있을 수 있습니다. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) 제목: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) 연주자: Search For Lyrics 가사 찾기 Choose the websites you want to use when searching for lyrics. 가사를 찾을 때 사용할 웹사이트를 선택합니다. Song Information 곡 정보 Images (*.png *.jpg) 이미지 (*.png *.jpg) 10px pixels 10화소 %1% value% %1% %1 px pixels %1 px Lyrics 가사 Information 정보 Metadata 메타데이터 Scroll Lyrics 가사 올리기 Refresh Lyrics 가사 새로 읽기 Edit Lyrics 가사수정 Delete Lyrics File 가사파일 지우기 Refresh Track Information 곡 정보 새로 읽기 Cancel 취소 Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Reload lyrics? Reload from disk, or delete disk copy and download? 가사를 다시 읽을까요? 디스크에서 다시 읽거나, 아니면 디스크 복사본을 지우고 내려받을까요? Reload 다시 읽기 Reload From Disk 디스크에서 다시 읽기 Download 내려받기 Current playing song has changed, still perform search? 지금 연주되는 곡이 바뀌었는데도 여전히 찾을까요? Song Changed 곡 바뀜 Perform Search 찾기 Delete lyrics file? 가사를 지울까요? Delete File 파일 삭제 Album artist 음반 연주자 Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) 작곡가 Lyricist 작사가 Conductor 지휘자 Remixer 리믹서 Subtitle 부제 Track number 곡 번호 Disc number 음반 번호 Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) 장르 Date 날짜 Original date 원본 날짜 Comment 설명 Copyright 저작권 Label 음반사 Catalogue number 목록 번호 Title sort 제목 정렬 Artist sort 연주자 정렬 Album artist sort 음반 연주자 정렬 Album sort 음반 정렬 Encoded by 인코딩 Encoder 인코더 Mood 분위기 Media 매체 Bitrate 비트레이트 Sample rate 샘플레이트 Channels 채널 Tagging time 태그 시간 Performer (%1) 공연가 (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits 비트 Performer 공연가 Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) 연도 Filename 파일이름 Fetching lyrics via %1 %1에서 가사 가져옴 (Polish Translations) (폴란드어 번역) (Portuguese Translations) (포르투갈어 번역) Track listing 곡 목록 Read more on wikipedia 위키피디아에서 더 읽기 Open in browser 브라우저에서 열기 artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | 연주자|밴드|가수|보컬리스트|음악가 album|score|soundtrack Search pattern for an album, separated by | 음반|평점|사운드트랙 Choose the wikipedia languages you want to use when searching for artist and album information. 연주자와 음반정보를 찾을 때 사용할 언어를 선택합니다. Cantata is playing a track 칸타타가 곡을 연주하고 있습니다 <b>INVALID</b> <b>무효</b> <i>(When different)</i> <i>(다를 때)</i> Artists:%1, Albums:%2, Songs:%3 연주자:%1, 음반:%2, 곡:%3 %1 free %1 남음 Local Music Library 로컬 음악 음원 Audio CD 오디오 CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. 대상 장치에 여유 공간이 부족합니다. 선택된 곡에 필요한 공간은 %1이지만, 남은 공간은 %2입니다. 선택된 곡을 복사하려면, 더 작은 파일 크기로 변환해야 합니다. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. 대상 장치에 여유 공간이 부족합니다. 선택된 곡에 필요한 공간은 %1이지만, 남은 공간은 %2입니다. Copy Songs To Library 음원으로 곡 복사 Copy Songs To Device 장치로 곡 복사 Copy Songs 곡 복사 Delete Songs 곡 지우기 You have not configured the destination device. Continue with the default settings? 대상 장치가 설정되지 않았습니다. 기본 설정으로 계속할까요? Not Configured 설정 안 됨 Use Defaults 기본값 사용 You have not configured the source device. Continue with the default settings? 원본 장치가 설정되지 않았습니다. 기본 설정으로 계속할까요? Are you sure you wish to stop? 정지할까요? Stop 정지 Device has been removed! 장치가 제거되었습니다! Device is not connected! 장치가 연결되지 않았습니다! Device is busy? 장치가 사용 중입니까? Device has been changed? 장치가 바뀌었습니까? Clearing unused folders 사용되지 않는 폴더 지우기 Calculate ReplayGain for ripped tracks? 리핑된 곡의 리플레이게인을 계산할까요? ReplayGain 리플레이게인 Calculate 계산 The destination filename already exists! 대상 파일이름이 이미 있습니다! Song already exists! 곡이 이미 있습니다! Song does not exist! 곡이 없습니다! Failed to create destination folder!<br/>Please check you have sufficient permissions. 대상 폴더를 만들 수 없음!<br/>권한이 있는지 확인 바랍니다. Source file no longer exists? 원본 파일이 더 이상 존재하지 않습니까? Failed to copy. 복사할 수 없음. Failed to delete. 지울 수 없음. Not connected to device. 장치에 연결되어 있지 않음. Selected codec is not available. 선택된 코덱을 사용할 수 없습니다. Transcoding failed. 변환 실패. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) 임시 파일을 만들 수 없음.<br/>(MTP 장치로 변환에 필요.) Failed to read source file. 원본 파일을 읽을 수 없음. Failed to write to destination file. 대상 파일을 쓸 수 없음. No space left on device. 장치에 여유 공간이 없음. Failed to update metadata. 메타데이터를 업데이트할 수 없음. Failed to download track. 곡을 내려받을 수 없음. Failed to lock device. 장치를 잠글 수 없음. Local Music Library Properties 로컬 음악 음원 속성 Error 오류 Skip 건너뜀 Auto Skip 자동 건너뜀 Retry 다시 시도 Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) 음반: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) 곡: Source file: 원본: Destination file: 대상 파일: File: 파일: Calculating... 계산 중... %1 (Estimated) time (Estimated) %1 (예상) Time remaining: 남은 시간: Saving cache 캐시 저장 Apply "Various Artists" Workaround "여러 연주자" 해결 적용 Revert "Various Artists" Workaround "여러 연주자" 해결 되돌리기 Capitalize 대문자로 Adjust Track Numbers 곡 번호 조정 Tools 도구 Apply "Various Artists" workaround? "여러 연주자" 해결을 적용할까요? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" '음반연주자'와 '연주자'를 "여러 연주자"로 설정하고, '제목'을 "곡 연주자 - 곡 제목"으로 바꿉니다 Revert "Various Artists" workaround? "여러 연주자" 해결을 되돌릴까요? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" '음반연주자'가 '연주자'와 같고 '제목'이 "곡 연주자 - 곡 제목" 형태이면, '연주자'는 '제목'으로부터 나오고 '제목'은 제목자체로부터 설정됩니다. 예로 '제목'이 "유재하 - 내 마음에 비친 내 모습"이면, '연주자'가 "유재하"로 설정되고 '제목'은 "내 마음에 비친 내 모습"이 됩니다 Revert 되돌림 Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? '제목', '연주자', '음반연주자', '음반'의 첫 글자를 대문자로 만들까요? Adjust track number by: 곡 번호 조정: Reading disc 디스크 읽음 CDDB CDDB MusicBrainz MusicBrainz Data Track 자료 수록 Failed to open CD device CD 장치를 읽을 수 없음 Track %1 %1 곡 Failed to create CDDB connection CDDB 연결 안 됨 Failed to contact CDDB server, please check CDDB and network settings CDDB 서버에 연결할 수 없으니, CDDB와 네트워크를 확인해야 합니다 No matches found in CDDB CDDB에 자료 없음 CDDB error: %1 CDDB 오류: %1 Multiple matches were found. Please choose the relevant one from below: 여러 자료가 있으니, 아래에서 하나를 고르시기 바랍니다: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) 제목 Disc Selection 디스크 선택 %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 음반 %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... 업데이트 중 (%1)... Updating (%1%)... 업데이트 중 (%1%)... Device Properties 장치 속성 Don't copy covers 표지 복사 안 함 Embed cover within each file 각 파일 안에 표지 내장 No maximum size 최대 크기 없음 400 pixels 400 화소 300 pixels 300 화소 200 pixels 200 화소 100 pixels 100 화소 <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>곡을 장치로 복사하고 '음반연주자'가 '여러 연주자'일 때, 칸타타는 모든 곡의 '연주자' 태그를 '여러 연주자'로 설정하고 '이름' 태그를 '곡 연주자 - 곡 제목'으로 설정합니다.<hr/> 장치로부터 복사할 때, '음반연주자'와 '연주자'가 모두 '여러 연주자'로 설정되어 있으면 '제목' 태그로부터 실제 연주자를 추출하고 '제목' 태그에서 연주자 이름을 지웁니다.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>이 설정을 사용하면 장치의 음악 음원에 캐시를 만듭니다. 모든 파일의 태그를 읽는 대신에 캐시 파일을 이용하므로 음원 검색을 빠르게 하는 데에 도움이 됩니다.<hr/><b>참고:</b> 만약 장치의 음원을 업데이트하는 데에 다른 응용 프로그램을 사용하면, 이 캐시는 쓸모 없어질 수 있습니다. 이를 해결하려면, 장치 리스트의 새로 읽기 단추를 눌러 캐시 파일을 지우고 장치를 다시 읽습니다.</p> Do not transcode 변환 안 함 Transcode to %1 %1로 변환 %1 (%2 free) name (size free) %1 (%2 남음) Copy To Library 음원으로 복사 Synchronise 동기화 Forget Device 장치 끊기 Add Device 장치 연결 Lookup album and track details? 음반과 곡의 상세정보를 찾습니까? Refresh 새로 읽기 Via CDDB CDDB 사용 Via MusicBrainz MusicBrainz 사용 Which type of refresh do you wish to perform? 어떻게 새로 읽을까요? Partial - Only new songs are scanned (quick) 부분 - 새 음악만 읽기 (빠름) Full - All songs are rescanned (slow) 전체 - 모든 음악 다시 읽기 (느림) Partial 부분 Full 전체 Are you sure you wish to delete the selected songs? This cannot be undone. 선택된 곡을 지울까요? 되돌릴 수 없습니다. Are you sure you wish to forget '%1'? '%1'을 끊을까요? Are you sure you wish to eject Audio CD '%1 - %2'? 오디오 CD '%1 - %2'을 뺄까요? Eject 빼기 Are you sure you wish to disconnect '%1'? '%1'을 끊을까요? Disconnect 연결 끊음 Please close other dialogs first. 다른 대화창을 먼저 닫으시기 바랍니다. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EA%B3%A0%EA%B8%89_%EC%98%A4%EB%94%94%EC%98%A4_%EB%B6%80%ED%98%B8%ED%99%94</a> (AAC)는 디지털 오디오를 위한 특허 받은 손실 코덱입니다.<br>AAC는 일반적으로 비슷한 비트레이트에서 MP3보다 나은 음질을 들려줍니다. iPod나 다른 휴대용 음악 연주기에서는 좋은 선택입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br> 칸타타에 쓰인 <b>AAC</b> 인코더는 <a href=http://ko.wikipedia.org/wiki/%EA%B0%80%EB%B3%80_%EB%B9%84%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%8A%B8>가변 비트레이트(VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 <a href=http://www.ffmpeg.org/faq.html#SEC21>평균 비트레이트</a> 예측값일 뿐입니다. <br><b>150kb/s</b>는 휴대용 연주기의 음악감상에 좋은 선택입니다. <br/><b>120kb/s 이하</b>는 음악이 만족스럽지 않을 수 있습니다. <b>200kb/s</b>는 너무 높을 수 있습니다. Expected average bitrate for variable bitrate encoding 가변 비트레이트 인코딩에서의 평균 기댓값 Smaller file 더 작은 파일 Better sound quality 더 좋은 음악 품질 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) 는 손실 데이터 압축을 이용하여 특허 받은 디지털 오디오 코덱입니다.<br>이러한 단점에도 불구하고, 소비자 오디오의 범용 저장 포맷으로 휴대용 음악 연주기에 널리 쓰입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>칸타타에 쓰인 <b>MP3</b> 인코더는 <a href=http://en.wikipedia.org/wiki/MP3#VBR>가변 비트레이트 (VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 평균 비트레이트일 뿐입니다.<br><b>160kb/s</b>은 휴대용 재생기에서 음악 듣기에 좋은 선택입니다.<br/><b>120kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>205kb/s 이상</b>은 너무 높을 수 있습니다. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a>는 손실 데이터 압축을 이용하여 특허 받은 디지털 오디오 코덱입니다.<br>동일하거나 더 높은 품질에서 MP3보다 더 작은 파일을 만듭니다. Ogg Vorbis는 전반적으로, 특히 지원하는 휴대용 음악 연주기에는 아주 좋은 선택입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>Cantata에 쓰인 <b>Vorbis</b> 인코더는 <a href=http://ko.wikipedia.org/wiki/Vorbis>가변 비트레이트 (VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>Vorbis 인코더는 -1부터 10까지의 품질 범위를 가집니다.이 슬라이더에 쓰인 비트레이트는 Vorbis에서 제공된 개략적인 추측에 불과합니다.실은 새롭고 더 효율적인 Vorbis 버전의 실제 비트레이트는 더 낮습니다.<br><b>5</b>는 휴대용 연주기에서의 음악감상에 좋은 선택입니다.<br/><b>3이하</b>는 음악이 불만족스러울 수 있습니다.<b>8이상</b>은 너무 높을 수 있습니다. Quality rating 품질 순위 Opus 오푸스(Opus) <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EC%98%A4%ED%91%B8%EC%8A%A4_%28%EC%98%A4%EB%94%94%EC%98%A4_%ED%8F%AC%EB%A7%B7%29>Opus</a>는 손실 데이터 압축을 이용하는 사용료가 없는 오디오 코덱입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>칸타타에 쓰인 <b>오푸스</b> 인코더는 <a href=http:http://ko.wikipedia.org/wiki/%EA%B0%80%EB%B3%80_%EB%B9%84%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%8A%B8>가변 비트레이트</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 평균 비트레이트일 뿐입니다.<br><b>128kb/s</b>은 휴대용 재생기에서 음악 듣기에 좋은 선택입니다.<br/><b>100kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>256kb/s 이상</b>은 너무 높을 수 있습니다. Apple Lossless 애플 무손실 <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_%EB%AC%B4%EC%86%90%EC%8B%A4>애플 무손실</a> (ALAC)은 디지털 음악의 무손실 압축을 위한 오디오 코덱입니다.<br>애플 제품이나 FLAC을 지원하지 않는 연주기에서만 추천합니다. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC)은 무손실 디지털 음악을 위해서 공개된, 사용료가 없는 코덱입니다.<br>오디오 품질을 손상시키지 않고 음악을 저장하려면, FLAC은 훌륭한 선택입니다. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html> 압축률</a>은 <b>FLAC</b>으로의 인코딩과정에서, 압축 속도와 파일 크기의 균형을 나타내는 0에서 8까지의 정수값입니다.<br/> 압축률을 <b>0</b>으로 하면 압축 시간은 가장 짧지만 상대적으로 큰 파일 크기가 커지며<br/>반면에, 압축률을 <b>8</b>로 하면, 압축은 오래 걸리지만 가장 작은 파일을 만듭니다.<br/>FLAC은 무손실 코덱이므로 압축률에 상관없이 음악 출력품질은 동일합니다.<br/>또한, <b>5</b>이상의 값은 압축 시간을 늘이지만 파일 크기는 약간만 작아지므로 추천하지 않습니다. Compression level 압축률 Faster compression 더 빠른 압축 Windows Media Audio 윈도 미디어 오디오 (WMA) <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EC%9C%88%EB%8F%84_%EB%AF%B8%EB%94%94%EC%96%B4_%EC%98%A4%EB%94%94%EC%98%A4>Windows Media Audio</a> (WMA)는 손실 압축을 위해 마이크로소프트에 의해 개발된 독점 코덱입니다.<br>Ogg Vorbis를 지원하지 않는 휴대용 음악 연주기에만 추천됩니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br><b>WMA</b> 독점 포맷의 한계로 역설계가 어려워, 칸타타에 쓰인 WMA 인코더는 <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>고정 비트레이트 (CBR)</a> 설정입니다.<br>따라서 이 슬라이더의 비트레이트 측정값은 인코딩된 트랙의 상당히 정확한 예측치입니다.<br><b>136kb/s</b>는 휴대용 음악 연주기에서 좋은 선택입니다.<br/><b>112kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>182kb/s 이상</b>은 너무 높을 수 있습니다. Filename Scheme 파일이름 구성 Various Artists Example album artist 유재하 Wibble Example artist 유재하 Vivaldi Example composer 유재하 Now 5001 Example album 사랑하기 때문에 Wobble Example song name 내 마음에 비친 내 모습 Dance Example genre Ballad The following variables will be replaced with their corresponding meaning for each track name. 아래 변수는 각 곡명에 해당하는 뜻에 따라 바뀝니다. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>변수</em></th><th><em>단추</em></th><th><em>설명</em></th></tr> Updating... 업데이트 중... Reading cache 캐시 읽음 %1 %2% Message percent %1 %2% Connecting to device... 장치 연결 중... No devices found 장치가 없음 Connected to device 장치에 연결됨 Disconnected from device 장치와 분리됨 Updating folders... 폴더 업데이트 중... Updating files... 파일 업데이트 중... Updating tracks... 곡 업데이트 중... Not Connected 연결 안 됨 %1 (Disc %2) %1 (디스크 %2) No matches found in MusicBrainz MusicBrainz에서 찾을 수 없음 Connection 연결 Music Library 음악 음원 A remote device named '%1' already exists! Please choose a different name. 원격 장치인 '%1'은 이미 있음! 다른 이름을 선택해야 합니다. Samba Share 삼바 공유 Samba Share (Auto-discover host and port) 삼바 공유 (호스트와 포트 자동 검색) Secure Shell (sshfs) 보안 셸 (sshfs) Locally Mounted Folder 로컬 폴더 마운트 Available 사용 가능 Not Available 사용 불가 Failed to resolve connection details for %1 %1의 상세정보에 연결 안 됨 Connecting... 연결 중... Password prompting does not work when cantata is started from the commandline. 칸타타가 명령어라인에서 실행되면 비밀번호 입력이 안됩니다. No suitable ssh-askpass application installed! This is required for entering passwords. 적당한 ssh-askpass 응용프로그램이 설치되지 않았습니다! 이는 패스워드 입력에 필요합니다. Mount point ("%1") is not empty! 마운트 포인트 ("%1")가 비어있지 않습니다! "sshfs" is not installed! "sshfs"가 설치되지 않았습니다! Disconnecting... 연결해제 중... "fusermount" is not installed! "fusermount"가 설치되지 않았습니다! Failed to connect to "%1" "%1"에 연결 안 됨 Failed to disconnect from "%1" "%1"로부터 연결 해제 안 됨 Capacity Unknown 용량을 모름 Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) 찾기 Check Items 항목 확인 Uncheck Items 항목 확인 안 함 Library: 음원: Device: 장치: Loading all songs from library, please wait... 음원의 모든 곡을 읽는 중으로, 잠시 기다리세요... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>음원</code>은 음원의 곡들만 있고 장치의 곡들은 없습니다. 비슷하게 <code>장치</code>는 장치의 곡들만 있습니다.<br/><code>장치</code>에 복사하려는 <code>음원</code>의 곡을 선택하고, <code>음원</code>에 복사하려는<code>장치</code>의 곡을 선택합니다. 그런 다음 <code>동기화</code> 단추를 누릅니다. Synchronize 동기화 Device and library are in sync. 장치와 음원이 동기화됨. Loading all songs from library, please wait...%1%... 음원의 모든 곡을 읽는 중으로, 잠시 기다리세요...%1%... Not Scanned 검색 안 됨 (recommended) (추천) Empty filename. 빈 파일명. Invalid filename. (%1) 무효 파일명 (%1) Failed to save %1. %1 저장 안됨. Failed to delete rules file. (%1) 규정 파일 지워지지 않음. (%1) Invalid command. (%1) 무효 명령. (%1) Could not remove active rules link. 활성화된 규정 연결을 지울 수 없음. Active rules is not a link. 활성화된 규정이 연결이 아님. Could not create active rules link. 활성화된 규정 연결을 만들 수 없음. Rules file, %1, does not exist. 규정 파일, %1,이 없음. Incorrect arguments supplied. 잘못된 변수가 주어짐. Unknown method called. 모르는 방법을 요청함. Unknown error 모르는 오류 Start Dynamic Playlist 활동 연주목록 시작 Stop Dynamic Mode 활동 방식 중지 Dynamic Playlists 활동 연주목록 Dynamically generated playlists 동적 생성 연주목록 - Rating: %1..%2 - 평점: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. 칸타타의 활동 상태를 작동하려면 "perl"을 시스템에 설치해야 합니다. Failed to locate rules file - %1 규정 파일을 찾을 수 없음 - %1 Failed to remove previous rules file - %1 이전 규정 파일을 지울 수 없음 - %1 Failed to install rules file - %1 -> %2 규정 파일을 설치할 수 없음 - %1 -> %2 Dynamizer has been terminated. 활동자가 중지됨. Saving rule 규정 저장 Deleting rule 규정 지우기 Awaiting response for previous command. (%1) 이전 명령어 응답 대기 중. (%1) Failed to save %1. (%2) %1 저장 안됨. (%2) Failed to control dynamizer state. (%1) 활동자 상태 관리 안 됨. (%1) Failed to set the current dynamic rules. (%1) 현재 활동 규정 설정 안 됨. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) 추가 Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) 수정 Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) 지우기 Remote dynamizer is not running. 원격 활동자가 실행 중이 아님. Are you sure you wish to remove the selected rules? This cannot be undone. 선택된 규정을 지울까요? 되돌릴 수 없습니다. Remove Dynamic Rules 활동 규정 지우기 Dynamic Rule 활동 규정 <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>오류</b>: '시작 연도'는 '마지막 연도'보다 작아야 합니다</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>오류:</b> 날짜 범위가 너무 큽니다 (최대 %1년 이내여야 합니다)</i> SimilarArtists 비슷한 연주자 AlbumArtist 음반연주자 Include 포함 Exclude 제외 (Exact) (정확) Dynamic Rules 활동 규정 None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) 없음 About dynamic rules 활동 규정에 대하여 Failed to save %1 %1 저장 안 됨 A set of rules named '%1' already exists! Overwrite? 규정 이름인 '%1'는 이미 있음! 덮어쓸까요? Overwrite Rules 규정 덮어쓰기 Saving %1 %1로 저장함 Deleting... 지우는 중... Name 이름 Item Count 항목 수 Space Used 사용 공간 Total space used: %1 전체사용공간: %1 Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. 칸타타는 다양한 정보(음반 표지, 가사 등)를 저장합니다. 아래는 현재 캐시 사용량 요약입니다. Covers 음반표지 Scaled Covers 비율표지 Backdrops 배경 Artist Information 연주자정보 Album Information 음반정보 Track Information 곡 정보 Stream Listings 스트림 목록 Podcast Directories 팟캐스트 디렉터리 Scrobble Tracks 곡 스크로블 Delete All 모두 지우기 Delete all '%1' items? '%1' 항목을 모두 지울까요? Delete Cache Items 캐시항목을 지웁니다 Delete items from all selected categories? 선택된 카테고리를 모두 지울까요? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover 현재 표지 CoverArt Archive 보관된 음반표지 Image 이미지 Downloading... 내려받는 중... Image (%1 x %2 %3%) Image (width x height zoom%) 이미지 (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. 이 연주자의 이미지가 이미 있으므로, 파일 쓰기가 되지 않습니다. A cover already exists for this album, and the file is not writeable. 이 음반의 표지가 이미 있으므로, 파일 쓰기가 되지 않습니다. '%1' Artist Image %1 연주자 이미지 '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' 음반표지 Failed to set cover! Could not download to temporary file! 표지 설정에 실패함! 임시 파일에 내려받을 수 없음! Failed to download image! 이미지를 내려받을 수 없음! Load Local Cover 로컬 표지 읽기 File is already in list! 파일이 이미 목록에 있음! Failed to read image! 이미지를 읽을 수 없음! Display 보기 Failed to set cover! Could not make copy! 표지를 설정할 수 없음! 복사할 수 없음! Failed to set cover! Could not backup original! 표지를 설정할 수 없음! 원본을 백업할 수 없음! Failed to set cover! Could not copy file to '%1'! 표지를 설정할 수 없음! '%1'에 복사할 수 없음! Searching... 찾기... Custom Actions 사용자 메뉴 Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) 이름: Command: 명령어: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. 위 명령어줄에서, %f는 파일항목으로 %d는 폴더항목으로 대체됩니다. 둘다 없으면, 명령어에 파일항목이 덧붙여집니다. c-format Add New Command 새 명령어 추가 Edit Command 명령어 수정 To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. 외부 명령어 (예. 다른 어플로 태그 수정)를 사용하려면, 아래 항목을 추가합니다. 명령어가 정의되면, '사용자 메뉴' 항목이 음원, 폴더, 음원목록에 추가됩니다. Command 명령어 Remove the selected commands? 선택된 명령어를 지울까요? Open In File Manager 파일관리자 열기 Connection Established 연결됨 Connection Failed 연결 안 됨 Grouped Albums 앨범으로 묶음 Table 항목으로 따로 Parse in Library view, and show in Folders view 음원 보기에서 분석하고, 폴더 보기에 보여줌 Only show in Folders view 폴더 보기에만 보여줌 Do not list 보이지 않음 Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) 연주순서 Library 음원 Folders 폴더 Playlists 연주목록 Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts 인터넷 - 스트림, Jamendo, Magnatune, SoundCloud, 팟캐스트 Devices - UMS, MTP (e.g. Android), and AudioCDs 장치 - UMS, MTP (예, 안드로이드), 오디오CD Search (via MPD) 찾기 (MPD를 통해서) Info - Current song information (artist, album, and lyrics) 정보 - 현재 곡 정보 (연주자, 음반, 가사) Large 크게 Small 작게 Tab-bar 꼬리표 Left 왼쪽 Right 오른쪽 Top Bottom 아래 Notifications 알림 System default 시스템 기본값 Show Artist Images 연주자 이미지 보기 Sort Albums 음반 정렬 Album, Artist, Year 음반, 연주자, 연도 Album, Year, Artist 음반, 연도, 연주자 Artist, Album, Year 연주자, 음반, 연도 Artist, Year, Album 연주자, 연도, 음반 Year, Album, Artist 연도, 음반, 연주자 Year, Artist, Album 연도, 연주자, 음반 Modified Date 바뀐 날짜 Group By 묶기 Configure Cantata... 칸타타 설정... Preferences 설정 Quit 나가기 About Cantata... Qt-only 칸타타에 대하여... Show Window 창 보기 Server information... 서버정보... Refresh Database 데이터베이스 새로 읽기 Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) 연결 Collection 음원 Outputs 출력 Stop After Track 곡 다음 정지 Add To Stored Playlist 저장된 연주목록에 추가 Crop Others 나머지 지우기 Add Stream URL 스트림 URL 추가 Clear 지우기 Center On Current Track 지금 곡을 가운데로 Expanded Interface 넓게 보기 Show Current Song Information 지금 곡 정보보기 Full Screen 전체화면 Random 무작위 Repeat 반복 Single 단일 When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. '단일'이 선택되면, 지금 곡 다음에 연주를 멈춥니다. '반복'이 선택되면, 곡은 반복됩니다. Consume 써버리기 When consume is activated, a song is removed from the play queue after it has been played. 써버리기가 선택되면, 곡은 연주 후에 연주순서에서 없어집니다. Find in Play Queue 연주순서에서 찾기 Play Stream 스트림 연주 Locate In Library 음원에서 찾기 Expand All 전체확장 Collapse All 전체축소 Internet 인터넷 Devices 장치 Info 정보 Show Menubar 메뉴 줄 보기 &Music 음악(&M) &Edit 수정(&E) &View 보기(&V) &Queue 순서(&Q) &Settings 설정(&S) &Help 도움말(&H) Set Rating 평점 설정 No Rating 평점 없음 Failed to locate any songs matching the dynamic playlist rules. 활동 연주목록 규정에 맞는 음악을 찾지 못했습니다. Connecting to %1 %1에 연결 Refresh MPD Database? MPD 데이터베이스를 새로 읽을까요? About Cantata Qt-only 칸타타에 대하여 <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Qt-only <b>Cantata %1</b><br/><br/>MPD 클라이언트.<br/><br/>&copy; 2011-2017 Craig Drummond<br/><a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a>에 따라 배포 Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> <a href="http://lowblog.nl">QtMPC</a> 기반 - &copy; 2007-2010 The QtMPC 개발자<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only 찾아보기 배경 도움은 <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only 찾아보기 자료 도움은 <a href="http://www.wikipedia.org">위키피디아</a>와 <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> 나만의 음악 팬아트를 올려주실 곳은 <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. 팟캐스트를 내려받고 있습니다 지금 취소하면 내려받기를 그만둡니다. Abort download and quit 내려받기를 그만두고 취소 Enabled: %1 사용 가능: %1 Disabled: %1 사용 불가능: %1 Server Information 서버정보 <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>서버</b></td></tr><tr><td align="right">프로토콜:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">가동시간:&nbsp;</td><td>%4</td></tr><tr><td align="right">연주:&nbsp;</td><td>%5</td></tr><tr><td align="right">처리:&nbsp;</td><td>%6</td></tr><tr><td align="right">태그:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>데이터베이스</b></td></tr><tr><td align="right">연주자:&nbsp;</td><td>%1</td></tr><tr><td align="right">음반:&nbsp;</td><td>%2</td></tr><tr><td align="right">곡:&nbsp;</td><td>%3</td></tr><tr><td align="right">지속시간:&nbsp;</td><td>%4</td></tr><tr><td align="right">업데이트:&nbsp;</td><td>%5</td></tr> Cantata (%1) 칸타타 (%1) MPD reported the following error: %1 MPD가 다음 오류를 보고함: %1 Cantata 칸타타 Playback stopped 연주중지 Remove all songs from play queue? 연주순서의 모든 곡을 지울까요? Priority 우선순위 Enter priority (0..255): 우선순위 입력 (0..255): Playlist Name 연주목록 이름 Enter a name for the playlist: 연주목록 이름을 입력합니다: '%1' is used to store favorite streams, please choose another name. '%1'이 즐겨찾는 스트림 저장에 사용되므로, 다른 이름을 선택합니다. A playlist named '%1' already exists! Add to that playlist? 연주목록 이름 '%1'이 이미 있습니다! 연주목록에 추가할까요? Existing Playlist 이미 있는 연주목록 Auto 자동 <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i><b>%1에 연결</b><br/>아래 목록들은 지금 연결된 MPD 음원에 적용됩니다.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i><b>연결 안 됨!</b><br/>칸타타가 MPD에 연결되지 않았으므로 아래 목록은 수정되지 않습니다.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> 리플레이게인은 MP3나 Ogg Vorbis 같은 오디오 포맷의 소리 크기를 맞추기 위하여 2001년에 제안된 표준입니다. 곡이나 음반 단위로 작동하며 이제는 점점 더 많은 연주기가 지원합니다.<br/><br/>아래 설정이 사용될 수 있습니다:<ul><li><i>안 함</i> - 리플레이게인을 적용 안 함.</li><li><i>곡</i> - 곡의 리플레이게인 태그를 이용하여 음량을 조정함.</li><li><i>음반</i> - 음반의 리플레이게인 태그를 이용하여 음량을 조정함.</li><li><i>자동</i> - 무작위연주는 곡의 리플레이게인 태그를, 그 밖은 음반의 태그를 이용하여 음량을 조정함.</li></ul> Rename 이름 바꾸기 Remove Duplicates 중복 지우기 Initially Collapse Albums 처음에 음반 접기 Are you sure you wish to remove the selected playlists? This cannot be undone. 선택된 연주목록을 지울까요? 돌이킬 수 없습니다. Remove Playlists 연주목록 지움 A playlist named '%1' already exists! Overwrite? 연주목록 이름 '%1'은 이미 있습니다! 덮어쓸까요? Overwrite Playlist 연주목록을 덮어씀 Rename Playlist 연주목록 이름 바꾸기 Enter new name for playlist: 새로운 연주목록을 입력합니다: Cannot add songs from '%1' to '%2' '%1'에서 '%2'로 곡을 추가할 수 없음 1 Track 한 곡 %1 Tracks 1 Track (%2) 한 곡 (%2) %1 Tracks (%2) 1 Album 한 음반 %1 Albums 1 Artist 한 연주자 %1 Artists 1 Stream 한 스트림 %1 Streams 1 Entry 한 항목 %1 Entries 1 Rule 한 규정 %1 Rules 1 Podcast 한 팟캐스트 %1 Podcasts 1 Episode 한 에피소드 %1 Episodes 1 Update available 한 업데이트 있음 %1 Updates available Collection Settings 음원 설정 Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) 연주 Playback Settings 연주 설정 Downloaded Files 내려받은 파일 Downloaded Files Settings 내려받은 파일 설정 Interface 인터페이스 Interface Settings 인터페이스 설정 Info View Settings 정보보기 설정 Scrobbling 스크로블링 Scrobbling Settings 스크로블링 설정 Audio CD Settings 오디오 CD 설정 Proxy 프락시 Proxy Settings Qt-only 프락시 설정 Shortcuts Qt-only 단축키 Keyboard Shortcut Settings Qt-only 키보드 단축키 설정 Cache 캐시 Cached Items 캐시항목 Cantata Preferences 칸타타 설정 Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) 설정 Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) 작곡가: Performer: 공연가: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) 장르: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) 설명: Date: 날짜: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. '날짜'태그로 곡을 찾습니다.<br/><br/>대게 연도만 넣어도 충분합니다. Modified: 바뀜: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. 이후에 바뀐 파일을 찾기위해 날짜 (YYYY/MM/DD - e.g. 2015/01/31)를 넣습니다.<br/><br>아니면 이전에 바뀐 날짜 수를 넣습니다. Any: 모두: No tracks found. 곡을 찾지 못함. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) 호스트: Which type of collection do you wish to connect to? 어떤 형식의 음원에 연결할까요? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) 표준 - 다른 기기에서 이미 설정된 음원을 공유하거나, 다른 클라이언트(예.MPDroid)에서 접속할 수 있습니다 Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. 기본 - 음원을 공유하지 않고 칸타타가 MPD를 설정하고 제어합니다. 이것은 칸타타만의 설정으로 다른 MPD 클라이언트에서는 접속이 <b>안</b>됩니다. If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' i18n: file: gui/initialsettingswizard.ui:236 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel_2) MPD의 고급 설정을 하려면(예. 다중 오디오 출력, DSD 지원 등) <b>반드시</b> '표준'을 선택해야 합니다 <i><b>NOTE:</b> %1</i> <i><b>참고:</b> %1</i> Add Collection 음원 추가 Standard 표준 Basic 기본 Delete '%1'? '%1'을 지울까요? Delete 지우기 New Collection %1 새로운 음원 %1 Default 기본값 Previous Track 이전 곡 Next Track 다음 곡 Play/Pause 연주/멈춤 Stop After Current Track 지금 곡 다음 정지 Increase Volume 음량 올림 Decrease Volume 음량 내림 Save As 다른 이름으로 저장 Append 덧붙이기 Append To Play Queue 연주목록에 덧붙이기 Append And Play 덧붙이고 연주 Add And Play 더하고 연주 Append To Play Queue And Play 연주목록에 덧붙이고 연주 Insert After Current 지금 다음에 끼워 넣기 Append Random Album 무작위 음반 덧붙이기 Play Now (And Replace Play Queue) 지금 연주 (연주순서도 바꿈) Add With Priority 우선순위 추가 Set Priority 우선순위 설정 Highest Priority (255) 최고 우선순위 (255) High Priority (200) 높은 우선순위 (200) Medium Priority (125) 중간 우선순위 (125) Low Priority (50) 낮은 우선순위 (50) Default Priority (0) 기본 우선순위 (0) Custom Priority... 사용자 우선순위... Add To Playlist 연주목록 추가 Organize Files 파일구성 Edit Track Information 곡 정보 수정 Set Image 이미지 설정 Find 찾기 Add To Play Queue 연주순서 추가 Now playing 지금 연주 중 Play 연주 Pause 멈춤 Cue Sheet Cue 시트 Playlist 연주목록 Configure Device 장치 설정 Refresh Device 장치 새로 읽기 Connect Device 장치 연결 Disconnect Device 장치 분리 Edit CD Details CD 상세정보 수정 No Devices Attached 장치 연결 안 됨 Not logged in 로그인 안 됨 Logged in 로그인 됨 No subscriptions 가입 안 됨 You do not have an active subscription 가입이 되지 않았음 Logged in (expiry:%1) 로그인 됨 (만기:%1) Session expired 세션 닫힘 Parse error loading cache file, please check your songs tags. 캐시 파일을 읽는 오류를 분석하고, 곡 태그를 확인해야 합니다. %1 by %2 Album by Artist %2의 %1 New Playlist... 새로운 연주목록... Stored Playlists 저장된 연주목록 Standard playlists 기본 연주목록 Smart Playlist 고급 연주목록 # Track number # Length 길이 Disc 디스크 Rating 평점 Undo 되돌리기 Redo 다시 돌리기 Shuffle 뒤섞기 Sort By 정렬하기 Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) 음반연주자 Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) 곡 제목 # (Track Number) # (곡 번호) TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search 스트림 찾기 Search for radio streams 라디오 스트림 찾기 Enter string to search 찾을 문자를 입력합니다 Not Loaded 읽지 않음 Loading... 읽는 중... Bookmarks 책갈피 IceCast IceCast Favorites 즐겨찾기 Bookmark Category 카테고리를 책갈피에 추가 Add Stream To Favorites 스트림을 즐겨찾기에 추가 Configure Digitally Imported Digitally Imported 설정 Streams 스트림 Radio stations 라디오 방송 Unknown 모름 "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed %1에 연결 안 됨 Connection to %1 failed - please check your proxy settings %1에 연결할 수 없음 - 프록시 설정을 확인해야 합니다 Connection to %1 failed - incorrect password %1에 연결 안 됨 - 틀린 암호 Failed to send command to %1 - not connected %1에 명령어 전달 안 됨 - 연결 안 됨 Failed to load. Please check user "mpd" has read permission. 연결 안 됨. "mpd" 사용자가 읽기 권한이 있는지 확인해야 합니다. Failed to load. MPD can only play local files if connected via a local socket. 불러오기 안 됨. MPD는 로컬에 연결된 파일만 불러올 수 있습니다. Failed to send command. Disconnected from %1 명령어 전달 안 됨. %1 연결 해제 Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b>에서 <b>%2</b>로 이름 바꾸기 안 됨 Failed to save <b>%1</b> <b>%1</b> 저장 안 됨 You cannot add parts of a cue sheet to a playlist! 큐시트의 일부를 연주목록에 추가할 수 없습니다! You cannot add a playlist to another playlist! 연주목록을 다른 연주목록에 추가할 수 없습니다! Failed to send '%1' to %2. Please check %2 is registered with MPD. '%1'을 %2로 보낼 수 없음. %2가 MPD에 등록되어 있는지 확인해야 합니다. Cannot store ratings, as the 'sticker' MPD command is not supported. MPD 'sticker' 명령어를 지원하지 않아, 평점을 저장할 수 없습니다. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) 한 곡들 Personal 개인 Various Artists 여러 연주자 <b>%1</b> on <b>%2</b> Song on Album <b>%2</b>의 <b>%1</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%3</b>에서 <b>%2</b>의 <b>%1</b> No proxy 프락시 없음 Use the system proxy settings 시스템 프락시 설정 사용 Manual proxy configuration 수동 프락시 설정 The world's largest digital service for free music 무료 음악을 위한 최대 디지털 서비스 Jamendo Settings Jamendo 설정 MP3 MP3 Ogg Ogg Streaming format: 스트림 형식: Streaming 스트림 MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Online music from magnatune.com magnatune.com의 온라인 음악 Magnatune Settings Magnatune 설정 Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) 사용자: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) 비밀번호: Membership: 회원: Downloads: 내려받기: Failed to parse 분석 안 됨 Downloading...%1% 내려받는 중...%1% Parsing music list.... 음악 항목 분석 중.... Failed to download 내려받을 수 없음 The music listing needs to be downloaded, this can consume over %1Mb of disk space 음악 항목을 내려받는 데에 %1Mb 디스크 용량이 필요합니다 Dowload music listing? 음악 항목을 내려받을까요? Re-download music listing? 음악 항목을 다시 내려받을까요? RSS: RSS: Website: 웹사이트: Podcast details 팟캐스트 상세정보 Select a podcast to display its details 상세정보를 보여줄 팟캐스트를 선택합니다 Enter search term... 검색단어입력... Failed to fetch podcasts from %1 %1의 팟캐스트를 가져올 수 없음 There was a problem parsing the response from %1 %1의 응답을 분석할 수 없습니다 Failed to download directory listing 디렉터리 항목을 내려받을 수 없음 Failed to parse directory listing 디렉터리 항목을 분석할 수 없음 URL URL Enter podcast URL... 팟캐스트 URL 입력... Load 읽기 Enter podcast URL below, and press 'Load' 아래에 팟캐스트 URL을 입력하고, '읽기'를 누릅니다 Invalid URL! 잘못된 URL! Failed to fetch podcast! 팟캐스트를 가져올 수 없음! Failed to parse podcast. 팟캐스트 분석 안 됨. Cantata only supports audio podcasts! The URL entered contains only video podcasts. 오디오 팟캐스트만 지원합니다! 입력한 URL은 비디오 팟캐스트만 포함하고 있습니다. Subscribe 구독 Enter URL URL 입력 Manual podcast URL 수동 팟캐스트 URL Search %1 %1 찾기 Search for podcasts on %1 %1에서 팟캐스트 찾기 Add Podcast Subscription 팟캐스트 구독 추가 Browse %1 %1 보기 Browse %1 podcasts %1 팟캐스트 보기 You are already subscribed to this podcast! 이미 이 팟캐스트를 구독하고 있습니다! Subscription added 구독 추가됨 Subscribe to RSS feeds RSS 구독 %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (내려받는 중: %1%) Failed to parse %1 %1 분석 안 됨 Cantata only supports audio podcasts! %1 contains only video podcasts. 오디오 팟캐스트만 지원합니다! %1은 비디오 팟캐스트만 있습니다. Failed to download %1 %1을 내려받을 수 없음 Check for new episodes: 새로운 에피소드 확인: Download episodes to: 에피소드 내려받기: Download automatically: 자동으로 내려받기: Podcast Settings 팟캐스트 설정 Manually 수동 Every 15 minutes 15분마다 Every 30 minutes 30분마다 Every hour 1시간마다 Every 2 hours 2시간마다 Every 6 hours 6시간마다 Every 12 hours 12시간마다 Every day 매일 Every week 매주 Don't automatically download episodes 자동으로 에피소드를 내려받지 않기 Latest episode 최신 에피소드 Latest %1 episodes 최신 %1 에피소드 All episodes 모든 에피소드 Add Subscription 구독 추가 Remove Subscription 구독 삭제 Download Episodes 에피소드 내려받기 Delete Downloaded Episodes 내려받은 에피소드 지우기 Cancel Download 내려받기 취소 Mark Episodes As New 에피소드를 안 들은 것으로 하기 Mark Episodes As Listened 에피소드를 들은 것으로 하기 Unsubscribe from '%1'? '%1'을 구독 취소할까요? Do you wish to download the selected podcast episodes? 선택된 팟캐스트 에피소드를 내려받을까요? Cancel podcast episode downloads (both current and any that are queued)? 팟캐스트 에피소드 내려받기를 취소할까요 (지금과 대기 모두)? Do you wish to the delete downloaded files of the selected podcast episodes? 선택된 팟캐스트 에피소드에서 내려받은 파일을 지울까요? Do you wish to mark the selected podcast episodes as new? 선택된 팟캐스트 에피소드를 안 들은 것으로 할까요? Do you wish to mark the selected podcast episodes as listened? 선택된 팟캐스트 에피소드를 읽은 것으로 할까요? Refresh all subscriptions? 전체 구독을 새로 읽을까요? Refresh All 전체 새로 읽기 Refresh all subscriptions, or only those selected? 전체나 선택된 구독만 새로 읽을까요? Refresh Selected 선택 새로 읽기 Search for tracks from soundcloud.com soundcloud.com에서 곡 찾기 Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) 배경 화면 Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) 연주자이미지 Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) 사용자 이미지: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) 흐림: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10화소 Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) 불투명: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) 알아서 찾아보기로 바꿈: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) 알아서 바꾸지 않음 ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) 어두운 기본배경 Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) 색상 팔레트와 상관없이, 어두운 배경과 흰 글자를 씁니다. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) 항상 하나의 창으로 줄이기 Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) 세 화면 모두 볼 수 있는 폭이더라도, '연주자', '음반'이나 '가사'만 봅니다. Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) 위키피디아 기본 본문만 보기 Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) 칸타타는 위키피디아의 이미지나 링크가 없이 단순화해서 보여줍니다. 이 단순화는 항상 100% 정확하지는 않아서, 기본 본문을 보여주게 됩니다. 전체 본문에서는 오류가 있을 수 있습니다. 또한 '캐시' 설정을 이용하여 저장된 기본 본문을 지워야 합니다. no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) 사용 가능: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) 선택됨: Calculating size of files to be copied, please wait... i18n: file: devices/actiondialog.ui:86 i18n: ectx: property (text), widget (QLabel, fileS) 복사될 파일 크기를 계산 중으로, 기다려 주세요... Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) 복사해오기: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (설정 필요) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) 음악 복사하기: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) 대상 형식: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) 곡 덮어쓰기 To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) 복사하기: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) 음반 상세정보 Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) 연도: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) 디스크: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) 한 연주자 Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) 음반과 곡 정보검색 Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) 첫 찾기: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) CDDB 호스트: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) CDDB 포트: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) CD를 넣으면 바로 정보 검색 Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) 오디오추출 Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Paranoia 완전 기능 (최고품질) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) 읽기오류 건너뛰지 않음 These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) 이 설정은 장치가 연결되었을 때에만 유효하고 수정할 수 있습니다. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) 음악 폴더: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) 음반표지 저장하기: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) 최대 표지 크기: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) 기본 음량: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) '여러 연주자' 해결 Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) 연결되면 알아서 음악검색 Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) 캐시 사용 Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) 파일이름 Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) 파일이름 구성: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) 안전한 VFAT Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) ASCII 문자만 표시 Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) 빈칸을 밑줄로 바꿈 Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) 연주자 이름에 'The'를 붙입니다 If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' i18n: file: devices/devicepropertieswidget.ui:195 i18n: ectx: property (toolTip), widget (QCheckBox, ignoreThe) 연주자가 'The'로 시작하면, 이를 폴더 이름 뒤에 붙입니다. 예. 'The Beatles'는 'Beatles, The'가 됩니다 Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) 변환 Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) 원본 파일이 다른 포맷일 때만 변환 Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) 예: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) 파일이름 구성에 대하여 The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) 음반의 연주자. 대부분의 경우, <i>곡의 연주자</i>와 동일합니다. 편집음반의 경우, 대게 <i>여러 연주자</i>입니다. The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) 음반명. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) 음반제목 The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) 작곡가. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) 각 곡의 연주자. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) 곡 연주자 The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) 곡명 (<i>곡 연주자</i>없이). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) 곡명 (만약 <i>음반연주자</i>와 다르다면, <i>곡 연주자</i>와 함께). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) 곡 제목 (+연주자) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) 곡 번호. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) 곡 # The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) 다수 음반의 음반 수.편집음반의 경우 대게 여러 장으로 구성됩니다. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) 음반발행연도. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) 음반 장르. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) 이 설정은 장치가 연결되지 않았을 때에만 수정할 수 있습니다. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) 형태: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) 선택 Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) 포트: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) 사용자: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) 도메인: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) 공유: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) 여기에 비밀번호를 입력하면, 설정 파일에 <b>비 암호화</b>되어 저장됩니다. 공유에 접근하기 전에 비밀번호를 입력하려면, 비밀번호를 '-'로 설정합니다 Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) 서비스 이름: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) 폴더: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) 기타 선택: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) sshfs의 작동 원리에 따라, 비밀번호 입력을 위해서는 적당한 ssh-askpass 응용프로그램 (ksshaskpass, ssh-askpass-gnome, 등.)이 필요합니다. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) 이 대화상자는 원격장치를 추가(예. Samba)하거나 로컬 마운트된 폴더에 연결할 때만 사용됩니다. 일반 미디어 연주기나 USB 장치는 연결되면 자동으로 표시됩니다. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) 새로운 활동 규정 - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) 규정에 대하여 Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) 아래와 맞는 음악 포함: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) 아래와 맞는 음악 제외: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) 비슷한 연주자: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) 음반연주자: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) 시작 연도: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) 모두 To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) 마지막 연도: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) 정확한 맞춤 Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) 찾고자 하는 태그값만 입력합니다. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) 장르에서 별표(*)로 끝나는 문자열은 다양한 장르를 포함합니다. 예) 'rock*'은 'Hard Rock'과 'Rock and Roll'을 포함. Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) 로컬 파일 추가 Save downloaded covers, artist, and composer images, in music folder i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) 내려받은 음반, 연주자, 작곡가 이미지를 음악 폴더에 저장 Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) 내려받은 가사를 음악 폴더에 저장 Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) 내려받은 배경을 음악 폴더에 저장 If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) 음악폴더에 음반표지, 가사나 배경을 저장하는데 쓰기 권한이 없다면, 개인 캐시 폴더에 파일을 저장합니다. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) 두 단계 이상이면 배경, 연주자, 작곡가 이미지만 음악 폴더에 저장할 수 있습니다. 즉 '연주자/음반/곡'. Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) 처음으로 칸타타 실행 Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) 칸타타로 맞이합니다 <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>칸타타는 기능이 다양하고 사용하기가 편리한 Music Player Daemon (MPD) 클라이언트 입니다. MPD는 음악 연주에 사용되는 백그라운드 어플입니다.</p><p>MPD에 대한 더 많은 정보는, 다음 웹사이트를 참조 바랍니다. <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>이 '마법사'는 칸타타가 제대로 동작하기 위한 사용자의 기본 설정을 돕습니다.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>칸타타로 맞이합니다</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> i18n: file: gui/initialsettingswizard.ui:134 i18n: ectx: property (text), widget (QLabel, label_8) <p>칸타타는 기능이 다양하고 사용하기 쉬운 Music Player Daemon (MPD) 클라이언트입니다. MPD는 유연하고, 강력한 음악 서버 프로그램입니다. MPD는 시스템 전체나 개인 사용자 기반으로 시작할 수 있습니다.<br/><br/>칸타타가 MPD를 연결하는 방식을 선택합니다:</p> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) 표준 다수 사용자/서버 설정 <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> i18n: file: gui/initialsettingswizard.ui:172 i18n: ectx: property (text), widget (BuddyLabel, label_10) <i>음원을 다른 사용자와 공유하고, MPD가 다른 기기에서 실행되거나, 개인설정을 하고, 다른 클라이언트 (예.MPDroid)에서 접속하려면, 이것을 선택합니다. MPD가 이미 설정이 되고 가동 중인 것을 확인해야 합니다.</i> Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) 기본 단일 사용자 설정 <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> i18n: file: gui/initialsettingswizard.ui:217 i18n: ectx: property (text), widget (BuddyLabel, label_9) <i>음원을 다른 사용자와 공유하지 않고 칸타타가 MPD를 설정하고 제어하려면, 이것을 선택합니다. 이것은 칸타타만의 설정으로 다른 MPD 클라이언트(예.MPDroid)에서는 접속이 <b>안</b>됩니다.</i> For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. i18n: file: gui/initialsettingswizard.ui:259 i18n: ectx: property (text), widget (QLabel, label_11) MPD에 대한 더 많은 정보는 웹사이트<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>를 참조합니다.<br/><br/>이 '마법사'는 칸타타가 제대로 작동하기 위한 기본 설정을 돕습니다. Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) 연결 상세정보 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) 아래 설정은 칸타타에서 기본적으로 필요한 정보입니다. 관련 상세정보를 입력해 주시고, 연결해 보려면 '연결' 단추를 누릅니다. The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) '음악 폴더' 설정은 음반표지나 가사 등을 찾기 위해 사용됩니다. 만약 MPD가 원격 호스트에 있다면 HTTP URL로 설정할 수 있습니다. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) 음악 폴더 Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) 음악 폴더를 선택합니다. Covers and Lyrics i18n: file: gui/initialsettingswizard.ui:620 i18n: ectx: property (text), widget (QLabel, label_6f) 음반표지와 가사 <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>인터넷으로부터 없는 음반표지와 가사를 내려받습니다.</p><p>이를 위하여, 음악폴더나 개인 캐시/설정 폴더에 내려받을 지를 확인합니다.</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) '음악 폴더'가 HTTP 주소로 지정되어, 외부 HTTP 서버에 파일을 올릴 수 없습니다. 따라서 위의 설정은 사용하지 않아야 합니다. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) 완료! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) 이제 칸타타가 설정되었습니다!<br/><br/>칸타타의 설정 대화창은 MPD 호스트 등을추가하는 것 이외에 외관을 개인화하는 데에 사용할 수 있습니다. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) 칸타타는 '음반연주자' 태그가 있으면 이를 이용해서 곡을 음반으로 묶고, 아니라면 '연주자' 태그를 이용합니다. 여러연주자 음반을 제대로 묶기 위해서는 <b>반드시</b> '음반연주자' 태그를 설정해야 합니다. 이때는 '여러연주자'를 사용하기를 추천합니다. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>경고:</b> 지금 '사용자' 그룹에 포함되어 있지 않습니다. 이 그룹의 사용자라면 음반표지와 가사를 저장하는 등의 기능을 더 잘 수행합니다. 만약 본인이나 관리자가 사용자를 이 그룹에 추가하였다면 다시 로그인을 해야 합니다. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) 옆줄 Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) 보기 Use the checkboxes below to configure which views will appear in the sidebar. i18n: file: gui/interfacesettings.ui:48 i18n: ectx: property (text), widget (QLabel, label_2) 옆줄 보기를 설정하려면 아래 네모 칸을 사용합니다. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. i18n: file: gui/interfacesettings.ui:61 i18n: ectx: property (text), widget (NoteLabel, sbPlayQueueLabel) 위에서 '연주목록'이 선택되지 않으면, 다른 보기의 옆에 보입니다. 위에서 '정보보기'가 선택되지 않으면, 곡 정보를 보는 도구 모음에 단추가 추가됩니다. Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) 모양: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) 위치: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) 아이콘만 보임 Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) 자동 숨김 Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) 처음에 음반 펼치지 않기 Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) 지금 음반을 자동으로 펼치기 Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) 지금 곡으로 이동 Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) 지우기 전에 물어보기 Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) 연주목록에서 찾기 (또는 단축키) Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) 현재 음반표지 Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) 도구 모음 Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) 정지 단추 보기 Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) 지금 곡 표지 보기 Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) 곡 평점 보기 External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) 외부 Enable MPRIS D-BUS interface i18n: file: gui/interfacesettings.ui:404 i18n: ectx: property (text), widget (QCheckBox, enableMpris) MPRIS D-BUS 인터페이스 사용 Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) 곡이 바뀌면 팝업 표시 Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) 알림 영역에 아이콘 보기 Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) 닫으면 알림 영역으로 최소화 On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) 시작할 때 Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) 창 보기 Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) 창 숨기기 Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) 이전 상태로 Tweaks i18n: file: gui/interfacesettings.ui:482 i18n: ectx: attribute (title), widget (QWidget, tab_4z) 바꾸기 Artist && Album Sorting i18n: file: gui/interfacesettings.ui:488 i18n: ectx: property (title), widget (QGroupBox, groupBox_2p) 연주자와 음반 정렬 Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' i18n: file: gui/interfacesettings.ui:494 i18n: ectx: property (text), widget (QLabel, labelp) 연주자와 음반을 정렬할 때 건너뛸 첫 단어를 (쉼표로 나누어) 입력합니다. 예) 'The'로 되어있으면 'The Beatles'는 'Beatles'로 정렬됩니다 Enter comma separated list of prefixes... i18n: file: gui/interfacesettings.ui:504 i18n: ectx: property (placeholderText), widget (LineEdit, ignorePrefixes) 쉼표로 나누어진 접두사를 입력합니다... Composer Support i18n: file: gui/interfacesettings.ui:514 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) 작곡가 지원 By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. i18n: file: gui/interfacesettings.ui:520 i18n: ectx: property (text), widget (QLabel, label) 곡이나 음반을 묶기 위해서 기본적으로 '음반 연주자' 태그(없으면 '연주자' 태그)를 씁니다. 'Classical' 등 특정 장르는 '작곡가' 태그로 묶는 것이 좋을 수 있습니다. '작곡가' 태그를 사용할 장르를 쉼표로 나누어 입력합니다. Enter comma separated list of genres... i18n: file: gui/interfacesettings.ui:530 i18n: ectx: property (placeholderText), widget (LineEdit, composerGenres) 쉼표로 나누어진 장르를 입력합니다... If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' i18n: file: gui/interfacesettings.ui:546 i18n: ectx: property (text), widget (QLabel, label_3) 음원에 한 곡만을 가진 연주자가 많다면, 연주자 항목에 두기가 번거로울 수 있습니다. 해결 방법으로 이 곡들을 별도 폴더에 두고 이 폴더를 아래에 입력하면, '여러 연주자' 음반 연주자의 '한 곡들' 아래에 묶습니다 Folder that contains single track files... i18n: file: gui/interfacesettings.ui:556 i18n: ectx: property (placeholderText), widget (LineEdit, singleTracksFolders) 한 곡 파일을 둔 폴더... CUE Files i18n: file: gui/interfacesettings.ui:566 i18n: ectx: property (title), widget (QGroupBox, groupBox_3xx) CUE 파일 A cue file is a metadata file which describes how the tracks of a CD are laid out. i18n: file: gui/interfacesettings.ui:572 i18n: ectx: property (text), widget (QLabel, label_3x) cue 파일은 CD 곡의 배치를 알려주는 메타데이터 파일입니다. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. i18n: file: gui/interfacesettings.ui:588 i18n: ectx: property (text), widget (NoteLabel, tweaksLabel) 위 값을 바꾸면 데이터베이스 새로 읽어야 (그리고 칸타타를 다시 시작해야) 적용됩니다. General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) 일반 Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) 표지가 없으면 Last.fm에서 가져오기 Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) 메뉴에 지우기 보기 Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) 한 번 클릭 사용 <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>칸타타의 인터페이스를 아래와 같이 바꿉니다: <ul><li>연주와 컨트롤버튼은 33% 더 넓어집니다</li><li>보기는 '깜빡'거립니다</li><li>드래그하려면, 상단-좌측 모서리를 '터치'해야 합니다</li><li>스크롤바는 숨겨집니다</li><li>동작(예. '연주순서 추가')는 항상 보입니다 (마우스 아래에 항목이 있지 않아도)</li><li>회전 단추가 문자영역 옆에 +와 -를 가집니다</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) 인터페이스를 터치 친화적으로 만들기 Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) 곡 정보 툴팁 보기 Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) 평점 표시 지원 Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) 언어: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) '한 번 클릭 사용' 설정을 바꾸면 다시 시작해야 합니다. Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) 언어 설정을 바꾸면 다시 시작해야 합니다. Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) '터치 친화적' 설정을 바꾸면 다시 시작해야 합니다. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) 레티나 디스플레이 기능을 켜면 해당기기에서 아이콘이 보다 선명하게 보이지만, 해당기기가 아니면 덜 선명하게 보입니다. 이 설정을 바꾸면 다시 시작해야 합니다. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [활동] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) 전체화면 나가기 Fa&deout on stop: i18n: file: gui/playbacksettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, label_6b) 정지시 소리 줄임(&d): Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) 빠져나가면 연주 정지 Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) 연주 중에 절전기능 사용 않기 If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) 정지 단추를 누르고 있으면, 바로 정지할지 지금 곡 다음에 정지할지를 선택하는 메뉴가 보입니다. (정지 단추는 인터페이스/도구 모음에서 켤 수 있습니다) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) 출력 &Crossfade between tracks: i18n: file: gui/playbacksettings.ui:112 i18n: ectx: property (text), widget (BuddyLabel, crossfadingLabel) 곡간 소리 줄임(&C): s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) Replay &gain: i18n: file: gui/playbacksettings.ui:135 i18n: ectx: property (text), widget (BuddyLabel, replayGainLabel) 리플레이 게인(&g): About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) 리플레이게인에 대하여 Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) 출력을 선택하려면 아래 항목을 켭니다. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) 음원: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) 표지 파일이름: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>내려받은 표지를 저장할 확장자없는 파일이름.<br/>비어있으면 'cover'가 사용됩니다.<br/><br/><i>%artist%은 지금 곡의 음반연주자로, %album%은 음반명으로 바뀝니다.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) HTTP 스트림 URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. i18n: file: gui/serversettings.ui:171 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) '음악 폴더' 설정은 음반표지를 찾기 위해 사용됩니다. MPD가 다른 기기에 있고 표지가 HTTP를 통해야 한다면, HTTP URL로 설정할 수 있습니다. HTTP URL로 설정되지 않고 이 폴더에 쓰기 권한이 있다면, 내려받은 표지를 각각의 음반 폴더에 저장합니다. 이 폴더는 장치로(부터) 전송하는 음악파일을 찾는데에도 사용됩니다. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) '표지 파일이름'이 설정되지 않으면, 기본 <code>표지</code>를 사용합니다 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) 'HTTP 스트림 URL'은 MPD가 HTTP 스트림으로 출력하도록 설정되어있고, 칸타타로 그 스트림을 연주할 때에만 의미가 있습니다. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) '음악폴더' 설정을 바꾸면, 음악데이터베이스를 직접 업데이트해야 합니다. 이는 '연주자'나 '음반' 보기의 '데이터베이스 새로 읽기' 단추를 누르면 됩니다. Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) 상태: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP 프락시 SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS 프락시 Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) 서비스를 선택하려면 아래 항목을 사용합니다. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) 서비스설정 Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) 스크로블링 사용: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) 상태: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) 로그인 Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) 곡 스크로블링 Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) '좋아요' 단추 보기 You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) 계정이 없이 무료로 사용할 수 있으나, 프리미엄 회원은 광고 없이 고품질 스트림을 들을 수 있습니다. 프리미엄 계정으로 업그레이드하려면 <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> 을 방문합니다. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) 프리미엄 계정 Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) 스트림 형식: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) 세션 만료: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) 이 설정은 Digitally Imported, JazzRadio.com, RockRadio.com, Sky.fm에 적용됩니다 If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) 계정을 입력하면, 스트림 항목 아래에 'DI' 상태가 표시됩니다. 이것은 로그인 여부를 나타냅니다. Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) 서비스를 선택하려면 아래 항목을 사용합니다. Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) 기본 설치된 카테고리는 기울어져 보이는데, 이는 삭제할 수 없습니다. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) 찾기: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) 선택된 동작의 단축키 Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) 기본: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) 개인: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) 음반연주자: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) 곡 번호: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) 디스크 번호: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) 평점: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(여러)</i> Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') i18n: file: tags/tageditor.ui:210 i18n: ectx: property (text), widget (PlainNoteLabel, label_7x) 다중 장르는 쉼표로 나뉘어야 합니다 (예. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) 평점은 외부 데이터베이스에 저장되며, 곡 파일에 저정되지 <b>않</b>습니다. Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) 원래 이름 New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) 새 이름 Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) 평점은 파일이름이 바뀌면 사라집니다. Your names NAME OF TRANSLATORS 이름 Your emails EMAIL OF TRANSLATORS 이메일 Show All Tracks 전곡 보기 Show Untagged Tracks 태그가 없는 곡 보기 Remove From List 항목에서 지웁니다 Album Gain 음반 게인 Track Gain 곡 게인 Album Peak 음반 피크 Track Peak 곡 피크 Scan 검색 Update ReplayGain tags in tracks? 곡의 리플레이게인 태그를 업데이트할까요? Update Tags 태그 업데이트 Abort scanning of tracks? 곡 검색을 취소할까요? Abort 취소 Abort reading of existing tags? 기존 태그 읽기를 취소할까요? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> <b>모든</b> 곡을 검색할까요?<br/><br/><i>모든 곡이 리플레이게인 태그가 있음.</i> Do you wish to scan all tracks, or only tracks without existing tags? 모든 곡이나 태그가 없는 곡만 검색할까요? Untagged Tracks 태그가 없는 곡 All Tracks 모든 곡 Scanning tracks... 곡 검색 중... Reading existing tags... 태그를 읽는 중... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (태그 오류?) Failed to update the tags of the following tracks: 다음 곡들의 태그를 업데이트할 수 없음: Device is not connected. 장치가 연결되지 않았음. %1 dB %1 dB Failed 실패 Original: %1 dB 원곡: %1 dB Original: %1 원곡: %1 Remove the selected tracks from the list? 항목의 선택된 곡을 지울까요? Remove Tracks 곡을 지움 Invalid service 무효 서비스 Invalid method 무료 방법 Authentication failed 인증 실패 Invalid format 무효 포맷 Invalid parameters 무효 변수 Invalid resource specified 무효 자원 지정 Operation failed 기능 실패 Invalid session key 무효 세션 키 Invalid API key 무효 API 키 Service offline 서비스 오프라인 Last.fm is currently busy, please try again in a few minutes Last.fm이 지금 혼잡하므로, 잠시 후에 다시 해야 합니다 Rate-limit exceeded 평점-한도 초과 %1 error: %2 %1 오류: %2 %1: Loved Current Track %1: 지금 곡 좋아함 %1: Love Current Track %1: 지금 곡 좋아요 %1 (via MPD) scrobbler name (via MPD) %1 (MPD를 통해서) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. (%1 같은)'(MPD를 통해서)'를 선택해서 스크로블링을 하려면, 먼저 이를 명시하고 실행해야 합니다. 칸타타에서 곡을 '좋아요' 할 수만 있으며, 스크로블링을 가능하거나 불가능하게 할 수는 없습니다. Authenticating... 인증 중... Authenticated 인증됨 Not Authenticated 인증 안됨 %1: Scrobble Tracks %1: 곡 스크로블 Digitally Imported Settings Digitally Imported 설정 MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout 로그아웃 URL: URL: Add Stream 스트림 추가 Edit Stream 스트림 수정 <i><b>ERROR:</b> Invalid protocol</i> <i><b>오류:</b> 무효 프로토콜</i> Loading %1 읽는 중 %1 Installed 설치됨 Update available 업데이트 있음 Check the providers you wish to install/update. 설치/업데이트하려는 서비스를 선택합니다. Install/Update Stream Providers 스트림 설치/업데이트 Downloading list... 항목을 내려받는 중... Digitally Imported Digitally Imported Local and National Radio (ListenLive) 지역과 국가 라디오 (ListenLive) Failed to download list of stream providers! 스트림 항목 내려받기 실패함! Installing/updating %1 %1 설치/업데이트하는 중 Failed to install '%1' '%1' 설치 실패함 Failed to download '%1' '%1' 내려받기 실패함 Install/update the selected stream providers? 선택된 스트림 제공자를 설치/업데이트할까요? Install the selected stream providers? 선택된 스트림 제공자를 설치할까요? Update the selected stream providers? 선택된 스트림 제공자를 업데이트할까요? Install/Update 설치/업데이트 Abort installation/update? 설치/업데이트를 취소합니까? Downloading %1 %1 내려받는 중 Update all updateable providers 모두 업데이트 Import Streams Into Favorites 스트림을 즐겨찾기로 가져오기 Export Favorite Streams 즐겨찾기 스트림을 내보내기 Add New Stream To Favorites 새로운 스트림을 즐겨찾기에 추가 Seatch For Streams 스트림 찾기 Digitally Imported Service name Digitally Imported Import Streams 스트림 불러오기 XML Streams (*.xml *.xml.gz *.cantata) XML 스트림 (*.xml *.xml.gz *.cantata) Export Streams 스트림 내보내기 XML Streams (*.xml.gz) XML 스트림 (*.xml.gz) Failed to create '%1'! '%1'을 만들 수 없음! Stream '%1' already exists! 스트림 '%1'은 이미 있습니다! A stream named '%1' already exists! 스트림 이름 '%1'은 이미 있습니다! Bookmark added 책갈피 추가됨 Already bookmarked 책갈피 이미 있음 Already in favorites 즐겨찾기에 있음 Reload '%1' streams? 스트림 '%1'을 다시 읽을까요? Are you sure you wish to remove bookmark to '%1'? '%1'의 책갈피를 지울까요? Are you sure you wish to remove all '%1' bookmarks? '%1'의 모든 책갈피를 지울까요? Are you sure you wish to remove the %1 selected streams? 선택된 스트림 %1을 지울까요? Are you sure you wish to remove '%1'? '%1'을 지울까요? Added '%1'' to favorites '%1'을 즐겨찾기에 추가함 Configure Streams 스트림 설정 From File... 파일로부터... Download... 내려받기... Configure Provider 서비스 설정 Install 설치 Install Streams 스트림 설치 Cantata Streams (*.streams) 칸타타 스트림 (*.streams) A category named '%1' already exists! Overwrite? 카테고리 '%1' 은 이미 있습니다! 덮어쓸까요? Failed top open package file. 패키지 파일을 열 수 없음. Invalid file format! 파일 포맷 오류! Failed to create stream category folder! 카테고리 폴더를 만들 수 없음! Failed to save stream list! 스트림 항목을 저장할 수 없음! Failed to remove streams folder! 스트림 폴더를 지울 수 없음! &OK 확인(&O) &Cancel 취소(&C) &Yes 예(&Y) &No 아니오(&N) &Discard 취소(&D) &Save 저장(&S) &Apply 적용(&A) &Close 닫기(&C) &Overwrite 덮어쓰기(&O) &Reset 재설정(&R) &Continue 계속(&C) &Delete 지우기(&D) &Stop 정지(&S) &Remove 지우기(&R) &Previous 이전(&P) &Next 다음(&N) Configure... 설정... Password 비밀번호 Please enter password: 비밀번호 입력: Close 닫기 Warning 주의 Question 질문 &Window 창(&W) Minimize 최소화 Zoom 확대 Select Folder 폴더 선택 Select File 파일 선택 %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags 태그 Set 'Album Artist' from 'Artist' '연주자'에서 '음반연주자' 설정 Read Ratings from File 파일로부터 평점 읽기 Write Ratings to File 파일에 평점 쓰기 All tracks 전곡 Apply "Various Artists" workaround to <b>all</b> tracks? "여러 연주자" 해결을 <b>전</b>곡에 적용할까요? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>'음반연주자'와 '연주자'를 "여러 연주자"로 설정하고, '제목'을 "곡 연주자 - 곡 제목"으로 바꿉니다</i> Revert "Various Artists" workaround on <b>all</b> tracks? "여러 연주자" 해결을 <b>전</b>곡에 되돌릴까요? Revert "Various Artists" workaround "여러 연주자" 해결을 되돌림 <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>'음반연주자'가 "곡 연주자 - 곡 제목" 형태의 '연주자'와 '제목'이 같다면, '연주자'는 '제목'으로부터 나오고 '제목' 자체는 제목으로 설정됩니다. 예로 <br/><br/>'제목'이 "유재하 - 내 마음에 비친 내 모습"이고 '연주자'가 "유재하"로 설정되면, '제목'은 "내 마음에 비친 내 모습"이 됩니다</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 만약 음반연주자가 비어있다면, '연주자'에서 '음반연주자'를 설정하는 것을 <b>전</b>곡에 적용할까요? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? 만약 음반연주자가 비어있다면, '연주자'에서 '음반연주자'를 설정할까요? Album Artist from Artist 연주자에서 음반연주자 Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? <b>전</b>곡의 문자열 (예. '제목', '연주자' 등) 첫글자를 대문자로 할까요? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? 문자열 (예. '제목', '연주자' 등) 첫글자를 대문자로 할까요? Adjust the value of each track number by: 각 곡 순서를 값대로 늘여갑니다: Read ratings for all tracks from the music files? 음악 파일 모든 곡의 평점을 읽을까요? Read rating from music file? 음악 파일의 평점을 읽을까요? Ratings 평점 Read Ratings 평점 읽기 Read Rating 평점 읽기 Read, and updated, ratings from the following tracks: 아래 곡의 평점을 읽고 업데이트 함: Not all Song ratings have been read from MPD! MPD로부터 모든 곡의 평점을 읽지 않았음! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. 곡 평점은 파일이 아니라, MPD의 'sticker' 데이터베이스에 저정됩니다.실제 파일에 저장하기 위해서, 칸타타는 MPD로부터 평점을 먼저 읽어야 합니다. Song rating has not been read from MPD! MPD로부터 곡 평점을 읽지 않았음! Write ratings for all tracks to the music files? 모든 곡의 평점을 음악 파일에 쓸까요? Write rating to music file? 평점을 음악 파일에 쓸까요? Write Ratings 평점 쓰기 Write Rating 평점 쓰기 Failed to write ratings of the following tracks: 아래 파일의 평점을 쓸 수 없음: Failed to write rating to music file! 음악 파일에 평점을 쓸 수 없음! All tracks [modified] 전곡 [바뀜] %1 [modified] %1 [바뀜]ㄱ Would you also like to rename your song files, so as to match your tags? 태그에 맞추기 위해서, 곡들의 이름을 바꾸겠습니까? Rename Files 파일이름 바꾸기 Abort renaming of files? 파일 이름 바꾸기를 취소할까요? Source file does not exist! 원본 파일이 없음! Destination file already exists! 대상 파일이 이미 있음! Failed to create destination folder! 대상 폴더를 만들 수 없음! Failed to rename '%1' to '%2' 파일이름을 '%1'에서 '%2'로 바꿀 수 없습니다 Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. 곡 평점은 파일이 아니라, MPD의 'sticker' 데이터베이스에 저정됩니다. 파일 (또는 저장된 폴더) 이름을 바꾸면, 곡의 평점은 지워집니다. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>작곡가:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>공연가:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>연주자:</b></td><td>%1</td></tr><tr><td align="right"><b>음반:</b></td><td>%2</td></tr><tr><td align="right"><b>연도:</b></td><td>%3</td></tr> Filter On Genre 장르 선택 All Genres 모든 장르 Go Back 돌아가기 Menu 메뉴 (Stream) (스트림) Search... 찾기... Close Search Bar 검색 줄 닫기 Logged into %1 %1로 로그인됨 <b>NOT</b> logged into %1 %1로 로그인 <b>안</b>됨 Basic Tree (No Icons) 기본 나무 모양 (아이콘 없음) Simple Tree 단순 나무 모양 Detailed Tree 상세 나무 모양 List 목록 Grid 격자 View 보기 Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. 곡 파일에 접근할 수 없음! 칸타타의 "음악 폴더"와, MPD의 "music_directory" 설정을 확인 바랍니다. Cannot access song files! Please check that the device is still attached. 곡 파일에 접근할 수 없음! 장치가 아직 연결되어 있는지 확인 바랍니다. Stretch Columns To Fit Window 칸을 늘려 윈도에 맞추기 Center 가운데 Alignment 정렬 (Various) (여러) Click to go back 돌아가려면 누름 Add All To Play Queue 모두 연주목록에 추가 Add All And Replace Play Queue 모두 추가하고 연주목록 바꾸기 Mute 조용히 Unmute 조용히 안 함 Volume %1% (Muted) 음량 %1% (조용히 함) Volume %1% 음량 %1% 1 Track Singular 한 곡 %1 Tracks Plural (N!=1) %1 곡 1 Track (%1) Singular 한 곡 (%1) %1 Tracks (%2) Plural (N!=1) %1 곡 (%2) 1 Album Singular 한 음반 %1 Albums Plural (N!=1) %1 음반 1 Artist Singular 한 연주자 %1 Artists Plural (N!=1) %1 연주자 1 Stream Singular 한 스트림 %1 Streams Plural (N!=1) %1 스트림 1 Entry Singular 한 항목 %1 Entries Plural (N!=1) %1 항목 1 Rule Singular 한 규정 %1 Rules Plural (N!=1) %1 규정 1 Podcast Singular 한 팟캐스트 %1 Podcasts Plural (N!=1) %1 팟캐스트 1 Episode Singular 한 에피소드 %1 Episodes Plural (N!=1) %1 에피소드 1 Update available Singular 한 업데이트 있음 %1 Updates available Plural (N!=1) %1 업데이트 있음 ActionDialog Calculating size of files to be copied, please wait... 복사될 파일 크기를 계산 중으로, 기다려 주세요... Copy songs from: 복사해오기: Configure 설정 (Needs configuring) (설정 필요) Copy songs to: 음악 복사하기: Destination format: 대상 형식: Overwrite songs 곡 덮어쓰기 To copy: 복사하기: <b>INVALID</b> <b>무효</b> <i>(When different)</i> <i>(다를 때)</i> Artists:%1, Albums:%2, Songs:%3 연주자:%1, 음반:%2, 곡:%3 %1 free %1 남음 Local Music Library 로컬 음악 음원 Audio CD 오디오 CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. 대상 장치에 여유 공간이 부족합니다. 선택된 곡에 필요한 공간은 %1이지만, 남은 공간은 %2입니다. 선택된 곡을 복사하려면, 더 작은 파일 크기로 변환해야 합니다. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. 대상 장치에 여유 공간이 부족합니다. 선택된 곡에 필요한 공간은 %1이지만, 남은 공간은 %2입니다. Copy Songs To Library 음원으로 곡 복사 Copy Songs To Device 장치로 곡 복사 Copy Songs 곡 복사 Delete Songs 곡 지우기 You have not configured the destination device. Continue with the default settings? 대상 장치가 설정되지 않았습니다. 기본 설정으로 계속할까요? Not Configured 설정 안 됨 Use Defaults 기본값 사용 You have not configured the source device. Continue with the default settings? 원본 장치가 설정되지 않았습니다. 기본 설정으로 계속할까요? Are you sure you wish to stop? 정지할까요? Stop 정지 Device has been removed! 장치가 제거되었습니다! Device is not connected! 장치가 연결되지 않았습니다! Device is busy? 장치가 사용 중입니까? Device has been changed? 장치가 바뀌었습니까? Clearing unused folders 사용되지 않는 폴더 지우기 Calculate ReplayGain for ripped tracks? 리핑된 곡의 리플레이게인을 계산할까요? ReplayGain 리플레이게인 Calculate 계산 The destination filename already exists! 대상 파일이름이 이미 있습니다! Song already exists! 곡이 이미 있습니다! Song does not exist! 곡이 없습니다! Failed to create destination folder!<br/>Please check you have sufficient permissions. 대상 폴더를 만들 수 없음!<br/>권한이 있는지 확인 바랍니다. Source file no longer exists? 원본 파일이 더 이상 존재하지 않습니까? Failed to copy. 복사할 수 없음. Failed to delete. 지울 수 없음. Not connected to device. 장치에 연결되어 있지 않음. Selected codec is not available. 선택된 코덱을 사용할 수 없습니다. Transcoding failed. 변환 실패. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) 임시 파일을 만들 수 없음.<br/>(MTP 장치로 변환에 필요.) Failed to read source file. 원본 파일을 읽을 수 없음. Failed to write to destination file. 대상 파일을 쓸 수 없음. No space left on device. 장치에 여유 공간이 없음. Failed to update metadata. 메타데이터를 업데이트할 수 없음. Failed to download track. 곡을 내려받을 수 없음. Failed to lock device. 장치를 잠글 수 없음. Local Music Library Properties 로컬 음악 음원 속성 Error 오류 Skip 건너뜀 Auto Skip 자동 건너뜀 Retry 다시 시도 Artist: 연주자: Album: 음반: Track: 곡: Source file: 원본: Destination file: 대상 파일: File: 파일: Saving cache 캐시 저장 Calculating... 계산 중... Time remaining: 남은 시간: AlbumDetails Album Details 음반 상세정보 Artist: 연주자: Composer: 작곡가: Title: 제목: Genre: 장르: Year: 연도: Disc: 디스크: Single artist 한 연주자 Tracks Track Artist 연주자 Title 제목 AlbumDetailsDialog Audio CD 오디오 CD Apply "Various Artists" Workaround "여러 연주자" 해결 적용 Revert "Various Artists" Workaround "여러 연주자" 해결 되돌리기 Capitalize 대문자로 Adjust Track Numbers 곡 번호 조정 Tools 도구 Apply "Various Artists" workaround? "여러 연주자" 해결을 적용할까요? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" '음반연주자'와 '연주자'를 "여러 연주자"로 설정하고, '제목'을 "곡 연주자 - 곡 제목"으로 바꿉니다 Revert "Various Artists" workaround? "여러 연주자" 해결을 되돌릴까요? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" '음반연주자'가 '연주자'와 같고 '제목'이 "곡 연주자 - 곡 제목" 형태이면, '연주자'는 '제목'으로부터 나오고 '제목'은 제목자체로부터 설정됩니다. 예로 '제목'이 "유재하 - 내 마음에 비친 내 모습"이면, '연주자'가 "유재하"로 설정되고 '제목'은 "내 마음에 비친 내 모습"이 됩니다 Revert 되돌림 Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? '제목', '연주자', '음반연주자', '음반'의 첫 글자를 대문자로 만들까요? Adjust track number by: 곡 번호 조정: AlbumView Refresh Album Information 음반정보 새로 읽기 Album 음반 Tracks ArtistView Refresh Artist Information 연주자정보 새로 읽기 Artist 연주자 Albums 음반 Web Links 웹 연결 Similar Artists 비슷한 연주자 AudioCdDevice Reading disc 디스크 읽음 %n Tracks (%1) %n 곡 (%2) AudioCdSettings Album and Track Information Retrieval 음반과 곡 정보검색 Initially look up via: 첫 찾기: CDDB Host: CDDB 호스트: CDDB Port: CDDB 포트: Lookup information as soon as CD is inserted CD를 넣으면 바로 정보 검색 Audio Extraction 오디오추출 Full paranoia mode (best quality) Paranoia 완전 기능 (최고품질) Never skip on read error 읽기오류 건너뛰지 않음 CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Cue 시트 Playlist 연주목록 CacheItem Deleting... 지우는 중... Calculating... 계산 중... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. 칸타타는 다양한 정보(음반 표지, 가사 등)를 저장합니다. 아래는 현재 캐시 사용량 요약입니다. Covers 음반표지 Scaled Covers 비율표지 Backdrops 배경 Lyrics 가사 Artist Information 연주자정보 Album Information 음반정보 Track Information 곡 정보 Stream Listings 스트림 목록 Podcast Directories 팟캐스트 디렉터리 Wikipedia Languages 위키피디아 언어 Scrobble Tracks 곡 스크로블 Delete All 모두 지우기 Delete all '%1' items? '%1' 항목을 모두 지울까요? Delete Cache Items 캐시항목을 지웁니다 Delete items from all selected categories? 선택된 카테고리를 모두 지울까요? CacheTree Name 이름 Item Count 항목 수 Space Used 사용 공간 CddbInterface Data Track 자료 수록 Failed to open CD device CD 장치를 읽을 수 없음 Track %1 %1 곡 Failed to create CDDB connection CDDB 연결 안 됨 Failed to contact CDDB server, please check CDDB and network settings CDDB 서버에 연결할 수 없으니, CDDB와 네트워크를 확인해야 합니다 No matches found in CDDB CDDB에 자료 없음 CDDB error: %1 CDDB 오류: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: 여러 자료가 있으니, 아래에서 하나를 고르시기 바랍니다: Artist 연주자 Title 제목 Disc Selection 디스크 선택 %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 음반 %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers 가사 찾기 Wikipedia Languages 위키피디아 언어 Other 기타 ContextWidget &Artist 연주자(&A) Al&bum 음반(&b) &Track 곡(&T) CoverDialog Search 찾기 Add a local file 로컬 파일 추가 Configure 설정 This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. 음반표지에 쓰인 파일을 바꿀 때에만 쓰이고, 내장 음반표지는 바꾸지 않습니다. CoverArt Archive 보관된 음반표지 An image already exists for this artist, and the file is not writeable. 이 연주자의 이미지가 이미 있으므로, 파일 쓰기가 되지 않습니다. A cover already exists for this album, and the file is not writeable. 이 음반의 표지가 이미 있으므로, 파일 쓰기가 되지 않습니다. '%1' Artist Image %1 연주자 이미지 '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' 음반표지 Failed to set cover! Could not download to temporary file! 표지 설정에 실패함! 임시 파일에 내려받을 수 없음! Failed to download image! 이미지를 내려받을 수 없음! Load Local Cover 로컬 표지 읽기 Images (*.png *.jpg) 이미지 (*.png *.jpg) File is already in list! 파일이 이미 목록에 있음! Failed to read image! 이미지를 읽을 수 없음! Display 보기 Remove 지우기 Failed to set cover! Could not make copy! 표지를 설정할 수 없음! 복사할 수 없음! Failed to set cover! Could not backup original! 표지를 설정할 수 없음! 원본을 백업할 수 없음! Failed to set cover! Could not copy file to '%1'! 표지를 설정할 수 없음! '%1'에 복사할 수 없음! Searching... 찾기... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>작곡가:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>공연가:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>연주자:</b></td><td>%1</td></tr><tr><td align="right"><b>음반:</b></td><td>%2</td></tr><tr><td align="right"><b>연도:</b></td><td>%3</td></tr> CoverPreview Image 이미지 Downloading... 내려받는 중... Image (%1 x %2 %3%) Image (width x height zoom%) 이미지 (%1 x %2 %3%) CustomActionDialog Name: 이름: Command: 명령어: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. 위 명령어줄에서, %f는 파일항목으로 %d는 폴더항목으로 대체됩니다. 둘다 없으면, 명령어에 파일항목이 덧붙여집니다. Add New Command 새 명령어 추가 Edit Command 명령어 수정 CustomActions Custom Actions 사용자 메뉴 CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. 외부 명령어 (예. 다른 어플로 태그 수정)를 사용하려면, 아래 항목을 추가합니다. 명령어가 정의되면, '사용자 메뉴' 항목이 음원, 폴더, 음원목록에 추가됩니다. Add 추가 Edit 수정 Remove 지우기 Name 이름 Command 명령어 Remove the selected commands? 선택된 명령어를 지울까요? Device Updating (%1)... 업데이트 중 (%1)... Updating (%1%)... 업데이트 중 (%1%)... DevicePropertiesDialog Device Properties 장치 속성 DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. 이 설정은 장치가 연결되었을 때에만 유효하고 수정할 수 있습니다. Name: 이름: Music folder: 음악 폴더: Copy album covers as: 음반표지 저장하기: Maximum cover size: 최대 표지 크기: Default volume: 기본 음량: 'Various Artists' workaround '여러 연주자' 해결 Automatically scan music when attached 연결되면 알아서 음악검색 Use cache 캐시 사용 Filenames 파일이름 Filename scheme: 파일이름 구성: VFAT safe 안전한 VFAT Use only ASCII characters ASCII 문자만 표시 Replace spaces with underscores 빈칸을 밑줄로 바꿈 Append 'The' to artist names 연주자 이름에 'The'를 붙입니다 If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' 연주자가 'The'로 시작하면, 이를 폴더 이름 뒤에 붙입니다. 예. 'The Beatles'는 'Beatles, The'가 됩니다 Transcoding 변환 Only transcode if source file is of a different format 원본 파일이 다른 포맷일 때만 변환 Only transcode if source is FLAC/WAV 원본 파일이 FLAC/WAV일 때만 변환 Don't copy covers 표지 복사 안 함 Embed cover within each file 각 파일 안에 표지 내장 No maximum size 최대 크기 없음 400 pixels 400 화소 300 pixels 300 화소 200 pixels 200 화소 100 pixels 100 화소 <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>곡을 장치로 복사하고 '음반연주자'가 '여러 연주자'일 때, 칸타타는 모든 곡의 '연주자' 태그를 '여러 연주자'로 설정하고 '이름' 태그를 '곡 연주자 - 곡 제목'으로 설정합니다.<hr/> 장치로부터 복사할 때, '음반연주자'와 '연주자'가 모두 '여러 연주자'로 설정되어 있으면 '제목' 태그로부터 실제 연주자를 추출하고 '제목' 태그에서 연주자 이름을 지웁니다.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>이 설정을 사용하면 장치의 음악 음원에 캐시를 만듭니다. 모든 파일의 태그를 읽는 대신에 캐시 파일을 이용하므로 음원 검색을 빠르게 하는 데에 도움이 됩니다.<hr/><b>참고:</b> 만약 장치의 음원을 업데이트하는 데에 다른 응용 프로그램을 사용하면, 이 캐시는 쓸모 없어질 수 있습니다. 이를 해결하려면, 장치 리스트의 새로 읽기 단추를 눌러 캐시 파일을 지우고 장치를 다시 읽습니다.</p> Do not transcode 변환 안 함 Encoder 인코더 Transcode to %1 %1로 변환 %1 (%2 free) name (size free) %1 (%2 남음) DevicesModel Configure Device 장치 설정 Refresh Device 장치 새로 읽기 Connect Device 장치 연결 Disconnect Device 장치 분리 Edit CD Details CD 상세정보 수정 Not Connected 연결 안 됨 No Devices Attached 장치 연결 안 됨 DevicesPage Copy To Library 음원으로 복사 Synchronise 동기화 Forget Device 장치 끊기 Add Device 장치 연결 Lookup album and track details? 음반과 곡의 상세정보를 찾습니까? Refresh 새로 읽기 Via CDDB CDDB 사용 Via MusicBrainz MusicBrainz 사용 Which type of refresh do you wish to perform? 어떻게 새로 읽을까요? Partial - Only new songs are scanned (quick) 부분 - 새 음악만 읽기 (빠름) Full - All songs are rescanned (slow) 전체 - 모든 음악 다시 읽기 (느림) Partial 부분 Full 전체 Are you sure you wish to delete the selected songs? This cannot be undone. 선택된 곡을 지울까요? 되돌릴 수 없습니다. Delete Songs 곡 지우기 Are you sure you wish to forget '%1'? '%1'을 끊을까요? Are you sure you wish to eject Audio CD '%1 - %2'? 오디오 CD '%1 - %2'을 뺄까요? Eject 빼기 Are you sure you wish to disconnect '%1'? '%1'을 끊을까요? Disconnect 연결 끊음 Please close other dialogs first. 다른 대화창을 먼저 닫으시기 바랍니다. DigitallyImported Not logged in 로그인 안 됨 Logged in 로그인 됨 Unknown error 모르는 오류 No subscriptions 가입 안 됨 You do not have an active subscription 가입이 되지 않았음 Logged in (expiry:%1) 로그인 됨 (만기:%1) Session expired 세션 닫힘 DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. 계정이 없이 무료로 사용할 수 있으나, 프리미엄 회원은 광고 없이 고품질 스트림을 들을 수 있습니다. 프리미엄 계정으로 업그레이드하려면 <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> 을 방문합니다. Premium Account 프리미엄 계정 Username: 사용자: Password: 비밀번호: Stream type: 스트림 형식: Status: 상태: Login 로그인 Session expiry: 세션 만료: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm 이 설정은 Digitally Imported, JazzRadio.com, RockRadio.com, Sky.fm에 적용됩니다 If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. 계정을 입력하면, 스트림 항목 아래에 'DI' 상태가 표시됩니다. 이것은 로그인 여부를 나타냅니다. Digitally Imported Settings Digitally Imported 설정 MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated 인증 안됨 Authenticating... 인증 중... Authenticated 인증됨 Logout 로그아웃 DockMenu Play 연주 Pause 멈춤 DynamicPlaylists Start Dynamic Playlist 활동 연주목록 시작 Stop Dynamic Mode 활동 방식 중지 Dynamic Playlists 활동 연주목록 Dynamically generated playlists 동적 생성 연주목록 You need to install "perl" on your system in order for Cantata's dynamic mode to function. 칸타타의 활동 상태를 작동하려면 "perl"을 시스템에 설치해야 합니다. Failed to locate rules file - %1 규정 파일을 찾을 수 없음 - %1 Failed to remove previous rules file - %1 이전 규정 파일을 지울 수 없음 - %1 Failed to install rules file - %1 -> %2 규정 파일을 설치할 수 없음 - %1 -> %2 Dynamizer has been terminated. 활동자가 중지됨. Awaiting response for previous command. (%1) 이전 명령어 응답 대기 중. (%1) Saving rule 규정 저장 Deleting rule 규정 지우기 Failed to save %1. (%2) %1 저장 안됨. (%2) Failed to delete rules file. (%1) 규정 파일 지워지지 않음. (%1) Failed to control dynamizer state. (%1) 활동자 상태 관리 안 됨. (%1) Failed to set the current dynamic rules. (%1) 현재 활동 규정 설정 안 됨. (%1) DynamicPlaylistsPage Add 추가 Edit 수정 Remove 지우기 Remote dynamizer is not running. 원격 활동자가 실행 중이 아님. Are you sure you wish to remove the selected rules? This cannot be undone. 선택된 규정을 지울까요? 되돌릴 수 없습니다. Remove Dynamic Rules 활동 규정 지우기 FancyTabWidget Configure... 설정... FileSettings Save downloaded covers, artist, and composer images, in music folder 내려받은 음반, 연주자, 작곡가 이미지를 음악 폴더에 저장 Save downloaded lyrics in music folder 내려받은 가사를 음악 폴더에 저장 Save downloaded backdrops in music folder 내려받은 배경을 음악 폴더에 저장 If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. 음악폴더에 음반표지, 가사나 배경을 저장하는데 쓰기 권한이 없다면, 개인 캐시 폴더에 파일 저장합니다. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. 두 단계 이상이면 배경, 연주자, 작곡가 이미지만 음악 폴더에 저장할 수 있습니다. 즉 '연주자/음반/곡'. FilenameSchemeDialog Example: 예: About filename schemes 파일이름 구성에 대하여 The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> 음반의 연주자. 대부분의 경우, <i>곡의 연주자</i>와 동일합니다. 편집음반의 경우, 대게 <i>여러 연주자</i>입니다. Album Artist 음반연주자 The name of the album. 음반명. Album Title 음반제목 The composer. 작곡가. Composer 작곡가 The artist of each track. 각 곡의 연주자. Track Artist 곡 연주자 The track title (without <i>Track Artist</i>). 곡명 (<i>곡 연주자</i>없이). Track Title 곡 제목 The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). 곡명 (만약 <i>음반연주자</i>와 다르다면, <i>곡 연주자</i>와 함께). Track Title (+Artist) 곡 제목 (+연주자) The track number. 곡 번호. Track # 곡 # The album number of a multi-album album. Often compilations consist of several albums. 다수 음반의 음반 수.편집음반의 경우 대게 여러 장으로 구성됩니다. CD # CD # The year of the album's release. 음반발행연도. Year 연도 The genre of the album. 음반 장르. Genre 장르 Filename Scheme 파일이름 구성 Various Artists Example album artist 여러 연주자 Wibble Example artist 유재하 Vivaldi Example composer 유재하 Now 5001 Example album 사랑하기 때문에 Wobble Example song name 내 마음에 비친 내 모습 Dance Example genre Ballad The following variables will be replaced with their corresponding meaning for each track name. 아래 변수는 각 곡명에 해당하는 뜻에 따라 바뀝니다. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>변수</em></th><th><em>단추</em></th><th><em>설명</em></th></tr> FolderPage Open In File Manager 파일관리자 열기 Are you sure you wish to delete the selected songs? This cannot be undone. 선택된 곡을 지울까요? 되돌릴 수 없습니다. Delete Songs 곡 지우기 FsDevice Updating... 업데이트 중... Reading cache 캐시 읽음 Saving cache 캐시 저장 %1 %2% Message percent %1 %2% GenreCombo Filter On Genre 장르 선택 All Genres 모든 장르 GroupedViewDelegate Audio CD 오디오 CD Streams 스트림 %n Track(s) %n 곡 InitialSettingsWizard Cantata First Run 처음으로 칸타타 실행 Welcome to Cantata 칸타타로 맞이합니다 <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>칸타타는 기능이 다양하고 사용하기가 편리한 Music Player Daemon (MPD) 클라이언트 입니다. MPD는 음악 연주에 사용되는 백그라운드 어플입니다.</p><p>MPD에 대한 더 많은 정보는, 다음 웹사이트를 참조 바랍니다. <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>이 '마법사'는 칸타타가 제대로 동작하기 위한 사용자의 기본 설정을 돕습니다.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>칸타타로 맞이합니다</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>칸타타는 기능이 다양하고 사용하기 쉬운 Music Player Daemon (MPD) 클라이언트입니다. MPD는 유연하고, 강력한 음악 서버 프로그램입니다. MPD는 시스템 전체나 개인 사용자 기반으로 시작할 수 있습니다.<br/><br/>칸타타가 MPD를 연결하는 방식을 선택합니다:</p> Standard multi-user/server setup 표준 다수 사용자/서버 설정 <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>음원을 다른 사용자와 공유하고, MPD가 다른 기기에서 실행되거나, 개인설정을 하고, 다른 클라이언트 (예.MPDroid)에서 접속하려면, 이것을 선택합니다. MPD가 이미 설정이 되고 가동 중인 것을 확인해야 합니다.</i> Basic single user setup 기본 단일 사용자 설정 <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>음원을 다른 사용자와 공유하지 않고 칸타타가 MPD를 설정하고 제어하려면, 이것을 선택합니다. 이것은 칸타타만의 설정으로 다른 MPD 클라이언트(예.MPDroid)에서는 접속이 <b>안</b>됩니다.</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' MPD의 고급 설정을 하려면(예. 다중 오디오 출력, DSD 지원 등) <b>반드시</b> '표준'을 선택해야 합니다 For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. MPD에 대한 더 많은 정보는 웹사이트<a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a>를 참조합니다.<br/><br/>이 '마법사'는 칸타타가 제대로 작동하기 위한 기본 설정을 돕습니다. Connection details 연결 상세정보 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. 아래 설정은 칸타타에서 기본적으로 필요한 정보입니다. 관련 상세정보를 입력해 주시고, 연결해 보려면 '연결' 단추를 누릅니다. Host: 호스트: Password: 비밀번호: Music folder: 음악 폴더: Connect 연결 The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. '음악 폴더' 설정은 음반표지나 가사 등을 찾기 위해 사용됩니다. 만약 MPD가 원격 호스트에 있다면 HTTP URL로 설정할 수 있습니다. Music folder 음악 폴더 Please choose the folder containing your music collection. 음악 폴더를 선택합니다. Covers and Lyrics 음반표지와 가사 <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>인터넷으로부터 없는 음반표지와 가사를 내려받습니다.</p><p>이를 위하여, 음악폴더나 개인 캐시/설정 폴더에 내려받을 지를 확인합니다.</p> Save downloaded covers, artist, and composer images, in music folder 내려받은 음반, 연주자, 작곡가 이미지를 음악 폴더에 저장 Save downloaded lyrics in music folder 내려받은 가사를 음악 폴더에 저장 Save downloaded backdrops in music folder 내려받은 배경을 음악 폴더에 저장 If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. 음악폴더에 음반표지, 가사나 배경을 저장하는데 쓰기 권한이 없다면, 개인 캐시 폴더에 저장합니다. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. 두 단계 이상이면 배경, 연주자, 작곡가 이미지만 음악 폴더에 저장할 수 있습니다. 즉 '연주자/음반/곡'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. '음악 폴더'가 HTTP 주소로 지정되어, 외부 HTTP 서버에 파일을 올릴 수 없습니다. 따라서 위의 설정은 사용하지 않아야 합니다. Finished! 완료! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. 이제 칸타타가 설정되었습니다!<br/><br/>칸타타의 설정 대화창은 MPD 호스트 등을추가하는 것 이외에 외관을 개인화하는 데에 사용할 수 있습니다. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. 칸타타는 '음반연주자' 태그가 있으면 이를 이용해서 곡을 음반으로 묶고, 아니라면 '연주자' 태그를 이용합니다. 여러연주자 음반을 제대로 묶기 위해서는 <b>반드시</b> '음반연주자' 태그를 설정해야 합니다. 이때는 '여러연주자'를 사용하기를 추천합니다. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>경고:</b> 지금 '사용자' 그룹에 포함되어 있지 않습니다. 이 그룹의 사용자라면 음반표지와 가사를 저장하는 등의 기능을 더 잘 수행합니다. 만약 본인이나 관리자가 사용자를 이 그룹에 추가하였다면 다시 로그인을 해야 합니다. Not Connected 연결 안 됨 Connection Established 연결됨 Connection Failed 연결 안 됨 Cantata will now terminate 칸타타가 중지하지 않습니다 InputDialog Password 비밀번호 Please enter password: 비밀번호 입력: InterfaceSettings Sidebar 옆줄 Views 보기 Use the checkboxes below to configure which views will appear in the sidebar. 옆줄 보기를 설정하려면 아래 네모 칸을 사용합니다. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. 위에서 '연주목록'이 선택되지 않으면, 다른 보기의 옆에 보입니다. 위에서 '정보보기'가 선택되지 않으면, 곡 정보를 보는 도구 모음에 단추가 추가됩니다. Options 선택 Style: 모양: Position: 위치: Only show icons, no text 아이콘만 보임 Auto-hide 자동 숨김 Play Queue 연주순서 Initially collapse albums 처음에 음반 펼치지 않기 Automatically expand current album 지금 음반을 자동으로 펼치기 Scroll to current track 지금 곡으로 이동 Prompt before clearing 지우기 전에 물어보기 Separate action (and shortcut) for play queue search 연주목록에서 찾기 (또는 단축키) Background Image 배경 화면 None 없음 Current album cover 현재 음반표지 Custom image: 사용자 이미지: Blur: 흐림: 10px 10화소 Opacity: 불투명: 40% 40% Toolbar 도구 모음 Show stop button 정지 단추 보기 Show cover of current track 지금 곡 표지 보기 Show track rating 곡 평점 보기 External 외부 Enable MPRIS D-BUS interface MPRIS D-BUS 인터페이스 사용 Show popup messages when changing tracks 곡이 바뀌면 팝업 표시 Show icon in notification area 알림 영역에 아이콘 보기 Minimize to notification area when closed 닫으면 알림 영역으로 최소화 On Start-up 시작할 때 Show main window 창 보기 Hide main window 창 숨기기 Restore previous state 이전 상태로 Tweaks 바꾸기 Artist && Album Sorting 연주자와 음반 정렬 Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' 연주자와 음반을 정렬할 때 건너뛸 첫 단어를 (쉼표로 나누어) 입력합니다. 예) 'The'로 되어있으면 'The Beatles'는 'Beatles'로 정렬됩니다 Enter comma separated list of prefixes... 쉼표로 나누어진 접두사를 입력합니다... Composer Support 작곡가 지원 By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. 곡이나 음반을 묶기 위해서 기본적으로 '음반 연주자' 태그(없으면 '연주자' 태그)를 씁니다. 'Classical' 등 특정 장르는 '작곡가' 태그로 묶는 것이 좋을 수 있습니다. '작곡가' 태그를 사용할 장르를 쉼표로 나누어 입력합니다. Enter comma separated list of genres... 쉼표로 나누어진 장르를 입력합니다... Single Tracks 한 곡들 If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' 음원에 한 곡만을 가진 연주자가 많다면, 연주자 항목에 두기가 번거로울 수 있습니다. 해결 방법으로 이 곡들을 별도 폴더에 두고 이 폴더를 아래에 입력하면, '여러 연주자' 음반 연주자의 '한 곡들' 아래에 묶습니다 Folder that contains single track files... 한 곡 파일을 둔 폴더... CUE Files CUE 파일 A cue file is a metadata file which describes how the tracks of a CD are laid out. cue 파일은 CD 곡의 배치를 알려주는 메타데이터 파일입니다. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. 위 값을 바꾸면 데이터베이스 새로 읽어야 (그리고 칸타타를 다시 시작해야) 적용됩니다. General 일반 Fetch missing covers from Last.fm 표지가 없으면 Last.fm에서 가져오기 Show delete action in context menus 메뉴에 지우기 보기 Enforce single-click activation of items 한 번 클릭 사용 Changing the style setting will require a re-start of Cantata. 모양을 바꾸면 다시 시작해야 합니다. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>칸타타의 인터페이스를 아래와 같이 바꿉니다: <ul><li>연주와 컨트롤버튼은 33% 더 넓어집니다</li><li>보기는 '깜빡'거립니다</li><li>드래그하려면, 상단-좌측 모서리를 '터치'해야 합니다</li><li>스크롤바는 숨겨집니다</li><li>동작(예. '연주순서 추가')는 항상 보입니다 (마우스 아래에 항목이 있지 않아도)</li><li>회전 단추가 문자영역 옆에 +와 -를 가집니다</li></ul></p> Make interface more touch friendly 인터페이스를 터치 친화적으로 만들기 Show song information tooltips 곡 정보 툴팁 보기 Support retina displays 평점 표시 지원 Language: 언어: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. '한 번 클릭 사용' 설정을 바꾸면 다시 시작해야 합니다. Changing the language setting will require a re-start of Cantata. 언어 설정을 바꾸면 다시 시작해야 합니다. Changing the 'touch friendly' setting will require a re-start of Cantata. '터치 친화적' 설정을 바꾸면 다시 시작해야 합니다. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. 레티나 디스플레이 기능을 켜면 해당기기에서 아이콘이 보다 선명하게 보이지만, 해당기기가 아니면 덜 선명하게 보입니다. 이 설정을 바꾸면 다시 시작해야 합니다. Library 음원 Folders 폴더 Playlists 연주목록 Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts 인터넷 - 스트림, Jamendo, Magnatune, SoundCloud, 팟캐스트 Devices - UMS, MTP (e.g. Android), and AudioCDs 장치 - UMS, MTP (예, 안드로이드), 오디오CD Search (via MPD) 찾기 (MPD를 통해서) Info - Current song information (artist, album, and lyrics) 정보 - 현재 곡 정보 (연주자, 음반, 가사) Large 크게 Small 작게 Tab-bar 꼬리표 Left 왼쪽 Right 오른쪽 Top Bottom 아래 Images (*.png *.jpg) 이미지 (*.png *.jpg) 10px pixels 10화소 Notifications 알림 English (en) 영어 (en) System default 시스템 기본값 %1% value% %1% %1 px pixels %1 px ItemView Go Back 돌아가기 Updating... 업데이트 중... JamendoService The world's largest digital service for free music 무료 음악을 위한 최대 디지털 서비스 JamendoSettingsDialog Jamendo Settings Jamendo 설정 MP3 MP3 Ogg Ogg Streaming format: 스트림 형식: KeySequenceButton The key you just pressed is not supported by Qt. 누른 키가 Qt에서 지원되지 않습니다. Unsupported Key 지원하지 않는 키 KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. 단추를 누르고 사용할 단축키를 입력합니다. Meta Meta key 메타키 Ctrl Ctrl key Ctrl 키 Alt Alt key Alt 키 Shift Shift key Shift 키 Input What the user inputs now will be taken as the new shortcut 입력 None No shortcut defined 없음 Shortcut Conflict 단축키 중복 The "%1" shortcut is already in use, and cannot be configured. Please choose another one. "%1" 단축키가 이미 사용중이라서 설정할 수 없습니다. The "%1" shortcut is ambiguous with the shortcut for the following action: "%1" 다음 동작의 단축키가 명확하지 않습니다: Do you want to reassign this shortcut to the selected action? 이 단축키를 선택한 동작으로 다시 지정할까요? Reassign 다시 지정 LastFmEngine Read more on last.fm last.fm에서 더 읽기 LibraryDb Database error - please check Qt SQLite driver is installed 데이터베이스 오류 - Qt SQLite 드라이버가 설치되었는지 확인바랍니다 LibraryPage Show Artist Images 연주자 이미지 보기 Sort Albums 음반 정렬 Name 이름 Year 연도 Album, Artist, Year 음반, 연주자, 연도 Album, Year, Artist 음반, 연도, 연주자 Artist, Album, Year 연주자, 음반, 연도 Artist, Year, Album 연주자, 연도, 음반 Year, Album, Artist 연도, 음반, 연주자 Year, Artist, Album 연도, 연주자, 음반 Modified Date 바뀐 날짜 Group By 묶기 Genre 장르 Artist 연주자 Album 음반 Are you sure you wish to delete the selected songs? This cannot be undone. 선택된 곡을 지울까요? 되돌릴 수 없습니다. Delete Songs 곡 지우기 LyricSettings Choose the websites you want to use when searching for lyrics. 가사를 찾을 때 사용할 웹사이트를 선택합니다. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. 가사를 찾지 못하거나 잘못된 가사를 찾으면, 이 대화상자를 이용해서 새로운 검색을 입력하시기 바랍니다. 예로 지금 곡이 리메이크라면 원곡의 가사를 찾는 것이 도움이 될 수 있습니다. 만약 이 검색이 새로운 가사를 찾는다면, 원곡 이름이나 연주자와 여전히 연관이 있을 수 있습니다. Title: 제목: Artist: 연주자: Search For Lyrics 가사 찾기 MPDConnection Unknown 모름 Connection to %1 failed %1에 연결 안 됨 Connection to %1 failed - please check your proxy settings %1에 연결할 수 없음 - 프록시 설정을 확인해야 합니다 Connection to %1 failed - incorrect password %1에 연결 안 됨 - 틀린 암호 Connecting to %1 %1에 연결 Failed to send command to %1 - not connected %1에 명령어 전달 안 됨 - 연결 안 됨 Failed to load. Please check user "mpd" has read permission. 연결 안 됨. "mpd" 사용자가 읽기 권한이 있는지 확인해야 합니다. Failed to load. MPD can only play local files if connected via a local socket. 불러오기 안 됨. MPD는 로컬에 연결된 파일만 불러올 수 있습니다. MPD reported the following error: %1 MPD가 다음 오류를 보고함: %1 Failed to send command. Disconnected from %1 명령어 전달 안 됨. %1 연결 해제 Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b>에서 <b>%2</b>로 이름 바꾸기 안 됨 Failed to save <b>%1</b> <b>%1</b> 저장 안 됨 You cannot add parts of a cue sheet to a playlist! 큐시트의 일부를 연주목록에 추가할 수 없습니다! You cannot add a playlist to another playlist! 연주목록을 다른 연주목록에 추가할 수 없습니다! Failed to send '%1' to %2. Please check %2 is registered with MPD. '%1'을 %2로 보낼 수 없음. %2가 MPD에 등록되어 있는지 확인해야 합니다. Cannot store ratings, as the 'sticker' MPD command is not supported. MPD 'sticker' 명령어를 지원하지 않아, 평점을 저장할 수 없습니다. MagnatuneService None 없음 Streaming 스트림 MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com magnatune.com의 온라인 음악 MagnatuneSettingsDialog Magnatune Settings Magnatune 설정 Username: 사용자: Password: 비밀번호: Membership: 회원: Downloads: 내려받기: MainWindow [Dynamic] [활동] Exit Full Screen 전체화면 나가기 Configure Cantata... 칸타타 설정... Preferences 설정 Quit 나가기 About Cantata... 칸타타에 대하여... Show Window 창 보기 Server information... 서버정보... Refresh Database 데이터베이스 새로 읽기 Refresh 새로 읽기 Connect 연결 Collection 음원 Outputs 출력 Stop After Track 곡 다음 정지 Seek forward (%1 seconds) 앞으로 가기 (%1 초) Seek backward (%1 seconds) 뒤로 가기 (%1 초) Add To Stored Playlist 저장된 연주목록에 추가 Crop Others 나머지 지우기 Add Stream URL 스트림 URL 추가 Clear 지우기 Center On Current Track 지금 곡을 가운데로 Expanded Interface 넓게 보기 Show Current Song Information 지금 곡 정보보기 Full Screen 전체화면 Random 무작위 Repeat 반복 Single 단일 When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. '단일'이 선택되면, 지금 곡 다음에 연주를 멈춥니다. '반복'이 선택되면, 곡은 반복됩니다. Consume 써버리기 When consume is activated, a song is removed from the play queue after it has been played. 써버리기가 선택되면, 곡은 연주 후에 연주순서에서 없어집니다. Find in Play Queue 연주순서에서 찾기 Play Stream 스트림 연주 Locate In Library 음원에서 찾기 Play next 다음 곡 재생 Edit Track Information (Play Queue) 곡 정보 수정 (연주순서) Expand All 전체확장 Collapse All 전체축소 Cancel 취소 Play Queue 연주순서 Library 음원 Folders 폴더 Playlists 연주목록 Internet 인터넷 Devices 장치 Search 찾기 Info 정보 Show Menubar 메뉴 줄 보기 &Music 음악(&M) &Edit 수정(&E) &View 보기(&V) &Queue 순서(&Q) &Settings 설정(&S) &Help 도움말(&H) Set Rating 평점 설정 No Rating 평점 없음 Failed to locate any songs matching the dynamic playlist rules. 활동 연주목록 규정에 맞는 음악을 찾지 못했습니다. Connecting to %1 %1에 연결 Refresh MPD Database? MPD 데이터베이스를 새로 읽을까요? About Cantata 칸타타에 대하여 <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>MPD 클라이언트.<br/><br/>&copy; 2011-2017 Craig Drummond<br/><a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a>에 따라 배포 Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> <a href="http://lowblog.nl">QtMPC</a> 기반 - &copy; 2007-2010 The QtMPC 개발자<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> 찾아보기 배경 도움은 <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> 찾아보기 자료 도움은 <a href="http://www.wikipedia.org">위키피디아</a>와 <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> 나만의 음악 팬아트를 올려주실 곳은 <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. 팟캐스트를 내려받고 있습니다 지금 취소하면 내려받기를 그만둡니다. Abort download and quit 내려받기를 그만두고 취소 Please close other dialogs first. 다른 대화창을 먼저 닫으시기 바랍니다. Enabled: %1 사용 가능: %1 Disabled: %1 사용 불가능: %1 Server Information 서버정보 <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>서버</b></td></tr><tr><td align="right">프로토콜:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">가동시간:&nbsp;</td><td>%4</td></tr><tr><td align="right">연주:&nbsp;</td><td>%5</td></tr><tr><td align="right">처리:&nbsp;</td><td>%6</td></tr><tr><td align="right">태그:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>데이터베이스</b></td></tr><tr><td align="right">연주자:&nbsp;</td><td>%1</td></tr><tr><td align="right">음반:&nbsp;</td><td>%2</td></tr><tr><td align="right">곡:&nbsp;</td><td>%3</td></tr><tr><td align="right">지속시간:&nbsp;</td><td>%4</td></tr><tr><td align="right">업데이트:&nbsp;</td><td>%5</td></tr> Cantata (%1) 칸타타 (%1) MPD reported the following error: %1 MPD가 다음 오류를 보고함: %1 Cantata 칸타타 Playback stopped 연주중지 Remove all songs from play queue? 연주순서의 모든 곡을 지울까요? Priority 우선순위 Enter priority (0..255): 우선순위 입력 (0..255): Decrease priority for each subsequent track 다음 각 곡들의 우선순위를 내립니다 Playlist Name 연주목록 이름 Enter a name for the playlist: 연주목록 이름을 입력합니다: '%1' is used to store favorite streams, please choose another name. '%1'이 즐겨찾는 스트림 저장에 사용되므로, 다른 이름을 선택합니다. A playlist named '%1' already exists! Add to that playlist? 연주목록 이름 '%1'이 이미 있습니다! 연주목록에 추가할까요? Existing Playlist 이미 있는 연주목록 %n Track(s) %n 곡 %n Tracks (%1) %n 곡 (%2) MenuButton Menu 메뉴 MessageOverlay Cancel 취소 Mpris (Stream) (스트림) MtpConnection Connecting to device... 장치 연결 중... No devices found 장치가 없음 Connected to device 장치에 연결됨 Disconnected from device 장치와 분리됨 Updating folders... 폴더 업데이트 중... Updating files... 파일 업데이트 중... Updating tracks... 곡 업데이트 중... MtpDevice Not Connected 연결 안 됨 %1 free %1 남음 MusicBrainz Failed to open CD device CD 장치를 읽을 수 없음 Track %1 %1 곡 %1 (Disc %2) %1 (디스크 %2) No matches found in MusicBrainz MusicBrainz에서 찾을 수 없음 MusicLibraryModel Cue Sheet Cue 시트 Playlist 연주목록 %n Track(s) %n 곡 %n Artist(s) %n 연주자 %n Album(s) %n 음반 %n Tracks (%1) %n 곡 (%2) %1 by %2 Album by Artist %2의 %1 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>참고:</b> %1</i> NowPlayingWidget (Stream) (스트림) OSXStyle &Window 창(&W) Close 닫기 Minimize 최소화 Zoom 확대 OnlineDbService Downloading...%1% 내려받는 중...%1% Parsing music list.... 음악 항목 분석 중.... Failed to download 내려받을 수 없음 %n Artist(s) %n 연주자 OnlineDbWidget Group By 묶기 Genre 장르 Artist 연주자 Configure 설정 The music listing needs to be downloaded, this can consume over %1Mb of disk space 음악 항목을 내려받는 데에 %1Mb 디스크 용량이 필요합니다 Dowload music listing? 음악 항목을 내려받을까요? Download 내려받기 Re-download music listing? 음악 항목을 다시 내려받을까요? OnlineSearchService Searching... 찾기... OnlineSearchWidget No tracks found. 곡을 찾지 못함. %n Tracks (%1) %n 곡 (%2) OnlineSettings Use the checkboxes below to configure the list of active services. 서비스를 선택하려면 아래 항목을 사용합니다. Configure Service 서비스설정 OnlineView Song Information 곡 정보 OnlineXmlParser Failed to parse 분석 안 됨 OpmlBrowsePage Reload 다시 읽기 Failed to download directory listing 디렉터리 항목을 내려받을 수 없음 Failed to parse directory listing 디렉터리 항목을 분석할 수 없음 OtherSettings Background Image 배경 화면 None 없음 Artist image 연주자이미지 Custom image: 사용자 이미지: Blur: 흐림: 10px 10화소 Opacity: 불투명: 40% 40% Automatically switch to view after: 알아서 찾아보기로 바꿈: Do not auto-switch 알아서 바꾸지 않음 ms ms Dark background 어두운 기본배경 Darken background, and use white text, regardless of current color palette. 색상 팔레트와 상관없이, 어두운 배경과 흰 글자를 씁니다. Always collapse into a single pane 항상 하나의 창으로 줄이기 Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. 세 화면 모두 볼 수 있는 폭이더라도, '연주자', '음반'이나 '가사'만 봅니다. Only show basic wikipedia text 위키피디아 기본 본문만 보기 Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). 칸타타는 위키피디아의 이미지나 링크가 없이 단순화해서 보여줍니다. 이 단순화는 항상 100% 정확하지는 않아서, 기본 본문을 보여주게 됩니다. 전체 본문에서는 오류가 있을 수 있습니다. 또한 '캐시' 설정을 이용하여 저장된 기본 본문을 지워야 합니다. Images (*.png *.jpg) 이미지 (*.png *.jpg) 10px pixels 10화소 %1% value% %1% %1 px pixels %1 px PathRequester Select Folder 폴더 선택 Select File 파일 선택 PlayQueueModel Title 제목 Artist 연주자 Album 음반 # Track number # Length 길이 Disc 디스크 Year 연도 Original Year 원본 연도 Genre 장르 Priority 우선순위 Composer 작곡가 Performer 공연가 Rating 평점 Remove Duplicates 중복 지우기 Undo 되돌리기 Redo 다시 돌리기 Shuffle 뒤섞기 Tracks Albums 음반 Sort By 정렬하기 Album Artist 음반연주자 Track Title 곡 제목 Track Number 곡 번호 # (Track Number) # (곡 번호) PlayQueueView Remove 지우기 PlaybackSettings Playback 연주 Fa&deout on stop: 정지시 소리 줄임(&d): None 없음 ms ms Stop playback on exit 빠져나가면 연주 정지 Inhibit suspend whilst playing 연주 중에 절전기능 사용 않기 If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) 정지 단추를 누르고 있으면, 바로 정지할지 지금 곡 다음에 정지할지를 선택하는 메뉴가 보입니다. (정지 단추는 인터페이스/도구 모음에서 켤 수 있습니다) Output 출력 <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i><b>연결 안 됨!</b><br/>칸타타가 MPD에 연결되지 않았으므로 아래 목록은 수정되지 않습니다.</i> &Crossfade between tracks: 곡간 소리 줄임(&C): s Replay &gain: 리플레이 게인(&g): About replay gain 리플레이게인에 대하여 Use the checkboxes below to control the active outputs. 출력을 선택하려면 아래 항목을 켭니다. Track Album 음반 Auto 자동 <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i><b>%1에 연결</b><br/>아래 목록들은 지금 연결된 MPD 음원에 적용됩니다.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> 리플레이게인은 MP3나 Ogg Vorbis 같은 오디오 포맷의 소리 크기를 맞추기 위하여 2001년에 제안된 표준입니다. 곡이나 음반 단위로 작동하며 이제는 점점 더 많은 연주기가 지원합니다.<br/><br/>아래 설정이 사용될 수 있습니다:<ul><li><i>안 함</i> - 리플레이게인을 적용 안 함.</li><li><i>곡</i> - 곡의 리플레이게인 태그를 이용하여 음량을 조정함.</li><li><i>음반</i> - 음반의 리플레이게인 태그를 이용하여 음량을 조정함.</li><li><i>자동</i> - 무작위연주는 곡의 리플레이게인 태그를, 그 밖은 음반의 태그를 이용하여 음량을 조정함.</li></ul> PlaylistRule Type: 형태: Include songs that match the following: 아래와 맞는 음악 포함: Exclude songs that match the following: 아래와 맞는 음악 제외: Artist: 연주자: Artists similar to: 비슷한 연주자: Album Artist: 음반연주자: Composer: 작곡가: Album: 음반: Title: 제목: Genre 장르 From Year: 시작 연도: Any 모두 To Year: 마지막 연도: Comment: 설명: Filename / path: 파일명 / 경로: Exact match 정확한 맞춤 Only enter values for the tags you wish to be search on. 찾고자 하는 태그값만 입력합니다. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. 장르에서 별표(*)로 끝나는 문자열은 다양한 장르를 포함합니다. 예) 'rock*'은 'Hard Rock'과 'Rock and Roll'을 포함. PlaylistRuleDialog Dynamic Rule 활동 규정 Smart Rule 스마트 규정 Add 추가 <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>오류</b>: '시작 연도'는 '마지막 연도'보다 작아야 합니다</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>오류:</b> 날짜 범위가 너무 큽니다 (최대 %1년 이내여야 합니다)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>오류:</b> 파일명 / 경로만을 찾을 수 있는데 '정확한 맞춤'이 <b>not</b> 확인되어야 합니다.checked</i> PlaylistRules Name of Dynamic Rules 새로운 활동 규정 Add 추가 Edit 수정 Remove 지우기 Songs with ratings between: 평점 사이의 곡: - - Songs with duration between: 곡 길이: seconds Number of songs in play queue: 연주순서의 곡 수: Order songs: 곡 순서: About Rules 규정에 대하여 PlaylistRulesDialog Dynamic Rules 활동 규정 None 없음 No Limit 무제한 About dynamic rules 활동 규정에 대하여 <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with 10 entries. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>칸타타는 아래의 모든 규정을 이용하여 음원을 찾습니다. <i>포함</i> 규정은 사용될 일련의 곡을 만드는 데에 쓰입니다. <i>제외</i> 규정은 사용되지 않을 일련의 곡을 만드는 데에 쓰입니다. <i>포함</i> 규정이 없으면 모든 곡으로 (<i>제외</i>부터의 가로줄) 생각합니다.</p><p>예. '어떤이 또는 여러 연주자의 Rock 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이 장르=Rock</li><li>포함 음반연주자=여러연주자</li></ul> '다른 음반이 아닌 어떤이의 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이</li><li>제외 음반연주자=어떤이 음반=다른</li></ul>사용할 곡이 만들어진 다음, 칸타타는 무작위 곡을 골라 10곡의 연주순서를 만듭니다. 평점이 지정되면, 그 평점의 곡들만 사용됩니다. 곡 길이도 마찬가지입니다.</p> Smart Rules 스마트 규정 Ascending 오름차순 Descending 내림차순 Name of Smart Rules 스마트 규정 이름 Number of songs 곡 수 <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>칸타타는 아래의 모든 규정을 이용하여 음원을 찾습니다. <i>포함</i> 규정은 사용될 일련의 곡을 만드는 데에 쓰입니다. <i>제외</i> 규정은 사용되지 않을 일련의 곡을 만드는 데에 쓰입니다. <i>포함</i> 규정이 없으면 모든 곡으로 (<i>제외</i>부터의 가로줄) 생각합니다.</p><p>예. '어떤이 또는 여러 연주자의 Rock 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이 장르=Rock</li><li>포함 음반연주자=여러연주자</li></ul> '다른 음반이 아닌 어떤이의 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이</li><li>제외 음반연주자=어떤이 음반=다른</li></ul>사용할 곡이 만들어진 다음, 칸타타는 무작위 곡을 골라 정해진 곡 수(기본 10곡)만큼의 연주순서를 만듭니다. 평점이 지정되면, 그 평점의 곡들만 사용됩니다. 곡 길이도 마찬가지입니다.</p> About smart rules 스마트 규정에 대하여 <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>칸타타는 아래의 모든 규정을 이용하여 음원을 찾습니다. <i>포함</i> 규정은 사용될 일련의 곡을 만드는 데에 쓰입니다. <i>제외</i> 규정은 사용되지 않을 일련의 곡을 만드는 데에 쓰입니다. <i>포함</i> 규정이 없으면 모든 곡으로 (<i>제외</i>부터의 가로줄) 생각합니다.</p><p>예. '어떤이 또는 여러 연주자의 Rock 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이 장르=Rock</li><li>포함 음반연주자=여러연주자</li></ul> '다른 음반이 아닌 어떤이의 곡'을 찾는다면, 아래와 같습니다: <ul><li>포함 음반연주자=어떤이</li><li>제외 음반연주자=어떤이 음반=다른</li></ul>사용할 곡이 만들어진 다음, 칸타타는 원하는 곡 수를 연주순서에 추가합니다. 평점이 지정되면, 그 평점의 곡들만 사용됩니다. 곡 길이도 마찬가지입니다.</p> Failed to save %1 %1 저장 안 됨 A set of rules named '%1' already exists! Overwrite? 규정 이름인 '%1'는 이미 있음! 덮어쓸까요? Overwrite Rules 규정 덮어쓰기 Saving %1 %1로 저장함 PlaylistsModel New Playlist... 새로운 연주목록... Stored Playlists 저장된 연주목록 Standard playlists 기본 연주목록 %n Tracks (%1) %n 곡 (%2) Smart Playlist 고급 연주목록 Plurals %n Track(s) %n 곡 %n Tracks (%1) %n 곡 (%2) %n Album(s) %n 음반 %n Artist(s) %n 연주자 %n Stream(s) %n 스트림 %n Entry(s) %n 항목 %n Rule(s) %n 규정 %n Podcast(s) %n 팟캐스트 %n Episode(s) %n 에피소드 %n Update(s) available %n 업데이트 있음 PodcastPage RSS: RSS: Website: 웹사이트: Podcast details 팟캐스트 상세정보 Select a podcast to display its details 상세정보를 보여줄 팟캐스트를 선택합니다 PodcastSearchDialog Subscribe 구독 Enter URL URL 입력 Manual podcast URL 수동 팟캐스트 URL Search %1 %1 찾기 Search for podcasts on %1 %1에서 팟캐스트 찾기 Add Podcast Subscription 팟캐스트 구독 추가 Browse %1 %1 보기 Browse %1 podcasts %1 팟캐스트 보기 You are already subscribed to this podcast! 이미 이 팟캐스트를 구독하고 있습니다! Subscription added 구독 추가됨 PodcastSearchPage Enter search term... 검색단어입력... Search 찾기 Failed to fetch podcasts from %1 %1의 팟캐스트를 가져올 수 없음 There was a problem parsing the response from %1 %1의 응답을 분석할 수 없습니다 PodcastService Subscribe to RSS feeds RSS 구독 %n Podcast(s) %n 팟캐스트 %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) %n 에피소드 (Downloading: %1%) (내려받는 중: %1%) Failed to parse %1 %1 분석 안 됨 Cantata only supports audio podcasts! %1 contains only video podcasts. 오디오 팟캐스트만 지원합니다! %1은 비디오 팟캐스트만 있습니다. Failed to download %1 %1을 내려받을 수 없음 PodcastSettingsDialog Check for new episodes: 새로운 에피소드 확인: Download episodes to: 에피소드 내려받기: Download automatically: 자동으로 내려받기: Podcast Settings 팟캐스트 설정 Manually 수동 Every 15 minutes 15분마다 Every 30 minutes 30분마다 Every hour 1시간마다 Every 2 hours 2시간마다 Every 6 hours 6시간마다 Every 12 hours 12시간마다 Every day 매일 Every week 매주 Don't automatically download episodes 자동으로 에피소드를 내려받지 않기 Latest episode 최신 에피소드 Latest %1 episodes 최신 %1 에피소드 All episodes 모든 에피소드 PodcastUrlPage URL URL Enter podcast URL... 팟캐스트 URL 입력... Load 읽기 Enter podcast URL below, and press 'Load' 아래에 팟캐스트 URL을 입력하고, '읽기'를 누릅니다 Invalid URL! 잘못된 URL! Failed to fetch podcast! 팟캐스트를 가져올 수 없음! Failed to parse podcast. 팟캐스트 분석 안 됨. Cantata only supports audio podcasts! The URL entered contains only video podcasts. 오디오 팟캐스트만 지원합니다! 입력한 URL은 비디오 팟캐스트만 포함하고 있습니다. PodcastWidget Add Subscription 구독 추가 Remove Subscription 구독 삭제 Download Episodes 에피소드 내려받기 Delete Downloaded Episodes 내려받은 에피소드 지우기 Cancel Download 내려받기 취소 Mark Episodes As New 에피소드를 안 들은 것으로 하기 Mark Episodes As Listened 에피소드를 들은 것으로 하기 Show Unplayed Only 안 들은 것만 보기 Unsubscribe from '%1'? '%1'을 구독 취소할까요? Do you wish to download the selected podcast episodes? 선택된 팟캐스트 에피소드를 내려받을까요? Cancel podcast episode downloads (both current and any that are queued)? 팟캐스트 에피소드 내려받기를 취소할까요 (지금과 대기 모두)? Do you wish to the delete downloaded files of the selected podcast episodes? 선택된 팟캐스트 에피소드에서 내려받은 파일을 지울까요? Do you wish to mark the selected podcast episodes as new? 선택된 팟캐스트 에피소드를 안 들은 것으로 할까요? Do you wish to mark the selected podcast episodes as listened? 선택된 팟캐스트 에피소드를 읽은 것으로 할까요? Refresh all subscriptions? 전체 구독을 새로 읽을까요? Refresh 새로 읽기 Refresh All 전체 새로 읽기 Refresh all subscriptions, or only those selected? 전체나 선택된 구독만 새로 읽을까요? Refresh Selected 선택 새로 읽기 PowerManagement Cantata is playing a track 칸타타가 곡을 연주하고 있습니다 PreferencesDialog Collection 음원 Collection Settings 음원 설정 Playback 연주 Playback Settings 연주 설정 Downloaded Files 내려받은 파일 Downloaded Files Settings 내려받은 파일 설정 Interface 인터페이스 Interface Settings 인터페이스 설정 Info 정보 Info View Settings 정보보기 설정 Scrobbling 스크로블링 Scrobbling Settings 스크로블링 설정 Audio CD 오디오 CD Audio CD Settings 오디오 CD 설정 Proxy 프락시 Proxy Settings 프락시 설정 Shortcuts 단축키 Keyboard Shortcut Settings 키보드 단축키 설정 Cache 캐시 Cached Items 캐시항목 Custom Actions 사용자 메뉴 Cantata Preferences 칸타타 설정 Configure 설정 ProxySettings Mode: 상태: Type: 형태: HTTP Proxy HTTP 프락시 SOCKS Proxy SOCKS 프락시 Host: 호스트: Port: 포트: Username: 사용자: Password: 비밀번호: No proxy 프락시 없음 Use the system proxy settings 시스템 프락시 설정 사용 Manual proxy configuration 수동 프락시 설정 QObject Track listing 곡 목록 Read more on wikipedia 위키피디아에서 더 읽기 Open in browser 브라우저에서 열기 <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://ko.wikipedia.org/wiki/%EA%B3%A0%EA%B8%89_%EC%98%A4%EB%94%94%EC%98%A4_%EB%B6%80%ED%98%B8%ED%99%94</a> (AAC)는 디지털 오디오를 위한 특허 받은 손실 코덱입니다.<br>AAC는 일반적으로 비슷한 비트레이트에서 MP3보다 나은 음질을 들려줍니다. iPod나 다른 휴대용 음악 연주기에서는 좋은 선택입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br> 칸타타에 쓰인 <b>AAC</b> 인코더는 <a href=http://ko.wikipedia.org/wiki/%EA%B0%80%EB%B3%80_%EB%B9%84%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%8A%B8>가변 비트레이트(VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 <a href=http://www.ffmpeg.org/faq.html#SEC21>평균 비트레이트</a> 예측값일 뿐입니다. <br><b>150kb/s</b>는 휴대용 연주기의 음악감상에 좋은 선택입니다. <br/><b>120kb/s 이하</b>는 음악이 만족스럽지 않을 수 있습니다. <b>200kb/s</b>는 너무 높을 수 있습니다. Expected average bitrate for variable bitrate encoding 가변 비트레이트 인코딩에서의 평균 기댓값 Smaller file 더 작은 파일 Better sound quality 더 좋은 음악 품질 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://ko.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) 는 손실 데이터 압축을 이용하여 특허 받은 디지털 오디오 코덱입니다.<br>이러한 단점에도 불구하고, 소비자 오디오의 범용 저장 포맷으로 휴대용 음악 연주기에 널리 쓰입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>칸타타에 쓰인 <b>MP3</b> 인코더는 <a href=http://en.wikipedia.org/wiki/MP3#VBR>가변 비트레이트 (VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 평균 비트레이트일 뿐입니다.<br><b>160kb/s</b>은 휴대용 재생기에서 음악 듣기에 좋은 선택입니다.<br/><b>120kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>205kb/s 이상</b>은 너무 높을 수 있습니다. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://ko.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a>는 손실 데이터 압축을 이용하여 특허 받은 디지털 오디오 코덱입니다.<br>동일하거나 더 높은 품질에서 MP3보다 더 작은 파일을 만듭니다. Ogg Vorbis는 전반적으로, 특히 지원하는 휴대용 음악 연주기에는 아주 좋은 선택입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>Cantata에 쓰인 <b>Vorbis</b> 인코더는 <a href=http://ko.wikipedia.org/wiki/Vorbis>가변 비트레이트 (VBR)</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>Vorbis 인코더는 -1부터 10까지의 품질 범위를 가집니다.이 슬라이더에 쓰인 비트레이트는 Vorbis에서 제공된 개략적인 추측에 불과합니다.실은 새롭고 더 효율적인 Vorbis 버전의 실제 비트레이트는 더 낮습니다.<br><b>5</b>는 휴대용 연주기에서의 음악감상에 좋은 선택입니다.<br/><b>3이하</b>는 음악이 불만족스러울 수 있습니다.<b>8이상</b>은 너무 높을 수 있습니다. Quality rating 품질 순위 Opus 오푸스(Opus) <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://ko.wikipedia.org/wiki/%EC%98%A4%ED%91%B8%EC%8A%A4_%28%EC%98%A4%EB%94%94%EC%98%A4_%ED%8F%AC%EB%A7%B7%29>Opus</a>는 손실 데이터 압축을 이용하는 사용료가 없는 오디오 코덱입니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br>칸타타에 쓰인 <b>오푸스</b> 인코더는 <a href=http:http://ko.wikipedia.org/wiki/%EA%B0%80%EB%B3%80_%EB%B9%84%ED%8A%B8%EB%A0%88%EC%9D%B4%ED%8A%B8>가변 비트레이트</a> 설정을 지원하는데, 이는 오디오 정보의 복잡성에 따라 트랙의 비트레이트가 가변적임을 의미합니다. 복잡한 데이터는 단순한 것보다 높은 비트레이트로 인코딩이 되며, 이 방식은 트랙에서 전반적으로 높은 품질과 작은 파일을 만듭니다. <br>이런 이유로 이 슬라이더에서 측정된 비트레이트는 인코딩된 트랙의 평균 비트레이트일 뿐입니다.<br><b>128kb/s</b>은 휴대용 재생기에서 음악 듣기에 좋은 선택입니다.<br/><b>100kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>256kb/s 이상</b>은 너무 높을 수 있습니다. Bitrate 비트레이트 Apple Lossless 애플 무손실 <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_%EB%AC%B4%EC%86%90%EC%8B%A4>애플 무손실</a> (ALAC)은 디지털 음악의 무손실 압축을 위한 오디오 코덱입니다.<br>애플 제품이나 FLAC을 지원하지 않는 연주기에서만 추천합니다. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://ko.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC)은 무손실 디지털 음악을 위해서 공개된, 사용료가 없는 코덱입니다.<br>오디오 품질을 손상시키지 않고 음악을 저장하려면, FLAC은 훌륭한 선택입니다. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html> 압축률</a>은 <b>FLAC</b>으로의 인코딩과정에서, 압축 속도와 파일 크기의 균형을 나타내는 0에서 8까지의 정수값입니다.<br/> 압축률을 <b>0</b>으로 하면 압축 시간은 가장 짧지만 상대적으로 큰 파일 크기가 커지며<br/>반면에, 압축률을 <b>8</b>로 하면, 압축은 오래 걸리지만 가장 작은 파일을 만듭니다.<br/>FLAC은 무손실 코덱이므로 압축률에 상관없이 음악 출력품질은 동일합니다.<br/>또한, <b>5</b>이상의 값은 압축 시간을 늘이지만 파일 크기는 약간만 작아지므로 추천하지 않습니다. Compression level 압축률 Faster compression 더 빠른 압축 Windows Media Audio 윈도 미디어 오디오 (WMA) <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://ko.wikipedia.org/wiki/%EC%9C%88%EB%8F%84_%EB%AF%B8%EB%94%94%EC%96%B4_%EC%98%A4%EB%94%94%EC%98%A4>Windows Media Audio</a> (WMA)는 손실 압축을 위해 마이크로소프트에 의해 개발된 독점 코덱입니다.<br>Ogg Vorbis를 지원하지 않는 휴대용 음악 연주기에만 추천됩니다. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. 비트레이트는 오디오 트랙의 초당 데이터를 나타내는 양을 수치화한 것입니다.<br><b>WMA</b> 독점 포맷의 한계로 역설계가 어려워, 칸타타에 쓰인 WMA 인코더는 <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>고정 비트레이트 (CBR)</a> 설정입니다.<br>따라서 이 슬라이더의 비트레이트 측정값은 인코딩된 트랙의 상당히 정확한 예측치입니다.<br><b>136kb/s</b>는 휴대용 음악 연주기에서 좋은 선택입니다.<br/><b>112kb/s 이하</b>는 음악이 불만족스러울 수 있습니다.<b>182kb/s 이상</b>은 너무 높을 수 있습니다. Empty filename. 빈 파일명. Invalid filename. (%1) 무효 파일명 (%1) Failed to save %1. %1 저장 안됨. Failed to delete rules file. (%1) 규정 파일 지워지지 않음. (%1) Invalid command. (%1) 무효 명령. (%1) Could not remove active rules link. 활성화된 규정 연결을 지울 수 없음. Active rules is not a link. 활성화된 규정이 연결이 아님. Could not create active rules link. 활성화된 규정 연결을 만들 수 없음. Rules file, %1, does not exist. 규정 파일, %1,이 없음. Incorrect arguments supplied. 잘못된 변수가 주어짐. Unknown method called. 모르는 방법을 요청함. Unknown error 모르는 오류 Artist 연주자 SimilarArtists 비슷한 연주자 AlbumArtist 음반연주자 Composer 작곡가 Comment 설명 Album 음반 Title 제목 Genre 장르 Date 날짜 File 파일 Include 포함 Exclude 제외 (Exact) (정확) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover 현재 표지 CoverArt Archive 보관된 음반표지 Grouped Albums 앨범으로 묶음 Table 항목으로 따로 Parse in Library view, and show in Folders view 음원 보기에서 분석하고, 폴더 보기에 보여줌 Only show in Folders view 폴더 보기에만 보여줌 Do not list 보이지 않음 1 Track Singular 한 곡 %1 Tracks Plural (N!=1) %1 곡 1 Track (%1) Singular 한 곡 (%1) %1 Tracks (%2) Plural (N!=1) %1 곡 (%2) 1 Album Singular 한 음반 %1 Albums Plural (N!=1) %1 음반 1 Artist Singular 한 연주자 %1 Artists Plural (N!=1) %1 연주자 1 Stream Singular 한 스트림 %1 Streams Plural (N!=1) %1 스트림 1 Entry Singular 한 항목 %1 Entries Plural (N!=1) %1 항목 1 Rule Singular 한 규정 %1 Rules Plural (N!=1) %1 규정 1 Podcast Singular 한 팟캐스트 %1 Podcasts Plural (N!=1) %1 팟캐스트 1 Episode Singular 한 에피소드 %1 Episodes Plural (N!=1) %1 에피소드 1 Update available Singular 한 업데이트 있음 %1 Updates available Plural (N!=1) %1 업데이트 있음 Previous Track 이전 곡 Next Track 다음 곡 Play/Pause 연주/멈춤 Stop 정지 Stop After Current Track 지금 곡 다음 정지 Stop After Track 곡 다음 정지 Increase Volume 음량 올림 Decrease Volume 음량 내림 Save As 다른 이름으로 저장 Append 덧붙이기 Append To Play Queue 연주목록에 덧붙이기 Append And Play 덧붙이고 연주 Add And Play 더하고 연주 Append To Play Queue And Play 연주목록에 덧붙이고 연주 Insert After Current 지금 다음에 끼워 넣기 Append Random Album 무작위 음반 덧붙이기 Play Now (And Replace Play Queue) 지금 연주 (연주순서도 바꿈) Add With Priority 우선순위 추가 Set Priority 우선순위 설정 Highest Priority (255) 최고 우선순위 (255) High Priority (200) 높은 우선순위 (200) Medium Priority (125) 중간 우선순위 (125) Low Priority (50) 낮은 우선순위 (50) Default Priority (0) 기본 우선순위 (0) Custom Priority... 사용자 우선순위... Add To Playlist 연주목록 추가 Organize Files 파일구성 Edit Track Information 곡 정보 수정 ReplayGain 리플레이게인 Copy Songs To Device 장치로 곡 복사 Delete Songs 곡 지우기 Set Image 이미지 설정 Remove 지우기 Find 찾기 Add To Play Queue 연주순서 추가 Parse error loading cache file, please check your songs tags. 캐시 파일을 읽는 오류를 분석하고, 곡 태그를 확인해야 합니다. Other 기타 Default 기본값 "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks 한 곡들 Personal 개인 Unknown 모름 Various Artists 여러 연주자 Album artist 음반 연주자 Performer 공연가 Track number 곡 번호 Disc number 음반 번호 Year 연도 Orignal Year 원본 연도 Length 길이 <b>%1</b> on <b>%2</b> Song on Album <b>%2</b>의 <b>%1</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%3</b>에서 <b>%2</b>의 <b>%1</b> Invalid service 무효 서비스 Invalid method 무료 방법 Authentication failed 인증 실패 Invalid format 무효 포맷 Invalid parameters 무효 변수 Invalid resource specified 무효 자원 지정 Operation failed 기능 실패 Invalid session key 무효 세션 키 Invalid API key 무효 API 키 Service offline 서비스 오프라인 Last.fm is currently busy, please try again in a few minutes Last.fm이 지금 혼잡하므로, 잠시 후에 다시 해야 합니다 Rate-limit exceeded 평점-한도 초과 General 일반 Digitally Imported Digitally Imported Local and National Radio (ListenLive) 지역과 국가 라디오 (ListenLive) &OK 확인(&O) &Cancel 취소(&C) &Yes 예(&Y) &No 아니오(&N) &Discard 취소(&D) &Save 저장(&S) &Apply 적용(&A) &Close 닫기(&C) &Help 도움말(&H) &Overwrite 덮어쓰기(&O) &Reset 재설정(&R) &Continue 계속(&C) &Delete 지우기(&D) &Stop 정지(&S) &Remove 지우기(&R) &Previous 이전(&P) &Next 다음(&N) Close 닫기 Error 오류 Information 정보 Warning 주의 Question 질문 %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) 기본 나무 모양 (아이콘 없음) Simple Tree 단순 나무 모양 Detailed Tree 상세 나무 모양 List 목록 Grid 격자 %n Track(s) %n 곡 %n Tracks (%1) %n 곡 (%2) %n Album(s) %n 음반 %n Artist(s) %n 연주자 %n Stream(s) %n 스트림 %n Entry(s) %n 항목 %n Rule(s) %n 규정 %n Podcast(s) %n 팟캐스트 %n Episode(s) %n 에피소드 %n Update(s) available %n 업데이트 있음 RemoteDevicePropertiesDialog Device Properties 장치 속성 Connection 연결 Music Library 음악 음원 Add Device 장치 연결 A remote device named '%1' already exists! Please choose a different name. 원격 장치인 '%1'은 이미 있음! 다른 이름을 선택해야 합니다. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. 이 설정은 장치가 연결되지 않았을 때에만 수정할 수 있습니다. Type: 형태: Name: 이름: Options 선택 Host: 호스트: Port: 포트: User: 사용자: Domain: 도메인: Password: 비밀번호: Share: 공유: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' 여기에 비밀번호를 입력하면, 설정 파일에 <b>비 암호화</b>되어 저장됩니다. 공유에 접근하기 전에 비밀번호를 입력하려면, 비밀번호를 '-'로 설정합니다 Service name: 서비스 이름: Folder: 폴더: Extra Options: 기타 선택: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. sshfs의 작동 원리에 따라, 비밀번호 입력을 위해서는 적당한 ssh-askpass 응용프로그램 (ksshaskpass, ssh-askpass-gnome, 등.)이 필요합니다. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. 이 대화상자는 원격장치를 추가(예. Samba)하거나 로컬 마운트된 폴더에 연결할 때만 사용됩니다. 일반 미디어 연주기나 USB 장치는 연결되면 자동으로 표시됩니다. Samba Share 삼바 공유 Samba Share (Auto-discover host and port) 삼바 공유 (호스트와 포트 자동 검색) Secure Shell (sshfs) 보안 셸 (sshfs) Locally Mounted Folder 로컬 폴더 마운트 RemoteFsDevice Available 사용 가능 Not Available 사용 불가 Failed to resolve connection details for %1 %1의 상세정보에 연결 안 됨 Connecting... 연결 중... Password prompting does not work when cantata is started from the commandline. 칸타타가 명령어라인에서 실행되면 비밀번호 입력이 안됩니다. No suitable ssh-askpass application installed! This is required for entering passwords. 적당한 ssh-askpass 응용프로그램이 설치되지 않았습니다! 이는 패스워드 입력에 필요합니다. Mount point ("%1") is not empty! 마운트 포인트 ("%1")가 비어있지 않습니다! "sshfs" is not installed! "sshfs"가 설치되지 않았습니다! Disconnecting... 연결해제 중... "fusermount" is not installed! "fusermount"가 설치되지 않았습니다! Failed to connect to "%1" "%1"에 연결 안 됨 Failed to disconnect from "%1" "%1"로부터 연결 해제 안 됨 Updating tracks... 곡 업데이트 중... Not Connected 연결 안 됨 Capacity Unknown 용량을 모름 %1 free %1 남음 RgDialog ReplayGain 리플레이게인 Show All Tracks 전곡 보기 Show Untagged Tracks 태그가 없는 곡 보기 Remove From List 항목에서 지웁니다 Artist 연주자 Album 음반 Title 제목 Album Gain 음반 게인 Track Gain 곡 게인 Album Peak 음반 피크 Track Peak 곡 피크 Scan 검색 Update ReplayGain tags in tracks? 곡의 리플레이게인 태그를 업데이트할까요? Update Tags 태그 업데이트 Abort scanning of tracks? 곡 검색을 취소할까요? Abort 취소 Abort reading of existing tags? 기존 태그 읽기를 취소할까요? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> <b>모든</b> 곡을 검색할까요?<br/><br/><i>모든 곡이 리플레이게인 태그가 있음.</i> Do you wish to scan all tracks, or only tracks without existing tags? 모든 곡이나 태그가 없는 곡만 검색할까요? Untagged Tracks 태그가 없는 곡 All Tracks 모든 곡 Scanning tracks... 곡 검색 중... Reading existing tags... 태그를 읽는 중... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (태그 오류?) Failed to update the tags of the following tracks: 다음 곡들의 태그를 업데이트할 수 없음: Device has been removed! 장치가 제거되었습니다! Device is not connected. 장치가 연결되지 않았음. Device is busy? 장치가 사용 중입니까? %1 dB %1 dB Failed 실패 Original: %1 dB 원곡: %1 dB Original: %1 원곡: %1 Remove the selected tracks from the list? 항목의 선택된 곡을 지울까요? Remove Tracks 곡을 지움 RulesPlaylists - Rating: %1..%2 - 평점: %1..%2 Album Artist 음반연주자 Artist 연주자 Album 음반 Composer 작곡가 Date 날짜 Genre 장르 Rating 평점 File Age 파일 나이 Random 무작위 %n Rule(s) %n 규정 , Rating: %1..%2 , 평점: %1..%2 Ascending 오름차순 Descending 내림차순 Scrobbler %1 error: %2 %1 오류: %2 ScrobblingLove %1: Loved Current Track %1: 지금 곡 좋아함 %1: Love Current Track %1: 지금 곡 좋아요 ScrobblingSettings Scrobble using: 스크로블링 사용: Username: 사용자: Password: 비밀번호: Status: 상태: Login 로그인 Scrobble tracks 곡 스크로블링 Show 'Love' button '좋아요' 단추 보기 %1 (via MPD) scrobbler name (via MPD) %1 (MPD를 통해서) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. (%1 같은)'(MPD를 통해서)'를 선택해서 스크로블링을 하려면, 먼저 이를 명시하고 실행해야 합니다. 칸타타에서 곡을 '좋아요' 할 수만 있으며, 스크로블링을 가능하거나 불가능하게 할 수는 없습니다. Authenticating... 인증 중... Authenticated 인증됨 Not Authenticated 인증 안됨 ScrobblingStatus %1: Scrobble Tracks %1: 곡 스크로블 SearchModel # (Track Number) # (곡 번호) SearchPage Locate In Library 음원에서 찾기 Artist: 연주자: Composer: 작곡가: Performer: 공연가: Album: 음반: Title: 제목: Genre: 장르: Comment: 설명: Date: 날짜: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. '날짜'태그로 곡을 찾습니다.<br/><br/>대게 연도만 넣어도 충분합니다. Original Date: 원본 날짜: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. 곡 찾기 '원본 날짜' tag.<br/><br/>대부분 연도 입력만으로 충분합니다. Modified: 바뀜: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. 이후에 바뀐 파일을 찾기위해 날짜 (YYYY/MM/DD - e.g. 2015/01/31)를 넣습니다.<br/><br>아니면 이전에 바뀐 날짜 수를 넣습니다. File: 파일: Any: 모두: No tracks found. 곡을 찾지 못함. %n Tracks (%1) %n 곡 (%2) SearchWidget Search... 찾기... Close Search Bar 검색 줄 닫기 ServerSettings Collection: 음원: Name: 이름: Host: 호스트: Password: 비밀번호: Music folder: 음악 폴더: Cover filename: 표지 파일이름: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>내려받은 표지를 저장할 확장자없는 파일이름.<br/>비어있으면 'cover'가 사용됩니다.<br/><br/><i>%artist%은 지금 곡의 음반연주자로, %album%은 음반명으로 바뀝니다.</i></p> HTTP stream URL: HTTP 스트림 URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. '음악 폴더' 설정은 음반표지를 찾기 위해 사용됩니다. MPD가 다른 기기에 있고 표지가 HTTP를 통해야 한다면, HTTP URL로 설정할 수 있습니다. HTTP URL로 설정되지 않고 이 폴더에 쓰기 권한이 있다면, 내려받은 표지를 각각의 음반 폴더에 저장합니다. 이 폴더는 장치로(부터) 전송하는 음악파일을 찾는데에도 사용됩니다. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> '표지 파일이름'이 설정되지 않으면, 기본 <code>표지</code>를 사용합니다 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. 'HTTP 스트림 URL'은 MPD가 HTTP 스트림으로 출력하도록 설정되어있고, 칸타타로 그 스트림을 연주할 때에만 의미가 있습니다. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. '음악폴더' 설정을 바꾸면, 음악데이터베이스를 직접 업데이트해야 합니다. 이는 '연주자'나 '음반' 보기의 '데이터베이스 새로 읽기' 단추를 누르면 됩니다. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. 이 폴더는 태그 수정, 리플레이게인, 장치로(부터) 전송할 음원을 찾기위해 사용됩니다. This folder will also be used to locate music files for tag-editing, replay gain, etc. 이 폴더는 태그 수정, 리플레이게인 등을 위한 음원을 찾기위해 사용됩니다. Which type of collection do you wish to connect to? 어떤 형식의 음원에 연결할까요? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) 표준 - 다른 기기에서 이미 설정된 음원을 공유하거나, 다른 클라이언트(예.MPDroid)에서 접속할 수 있습니다 Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. 기본 - 음원을 공유하지 않고 칸타타가 MPD를 설정하고 제어합니다. 이것은 칸타타만의 설정으로 다른 MPD 클라이언트에서는 접속이 <b>안</b>됩니다. <i><b>NOTE:</b> %1</i> <i><b>참고:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' MPD의 고급 설정을 하려면(예. 다중 오디오 출력, DSD 지원 등) <b>반드시</b> '표준'을 선택해야 합니다 Add Collection 음원 추가 Standard 표준 Basic 기본 Delete '%1'? '%1'을 지울까요? Delete 지우기 New Collection %1 새로운 음원 %1 Default 기본값 ServiceStatusLabel Logged into %1 %1로 로그인됨 <b>NOT</b> logged into %1 %1로 로그인 <b>안</b>됨 ShortcutsModel Action 동작 Shortcut 단축키 ShortcutsSettingsWidget Search: 찾기: Shortcut for Selected Action 선택된 동작의 단축키 Default: 기본: None 없음 Custom: 개인: SinglePageWidget Refresh 새로 읽기 View 보기 SmartPlaylists Smart Playlists 스마트 연주목록 Rules based playlists 규정에 따른 연주목록 SmartPlaylistsPage Add 추가 Edit 수정 Remove 지우기 Are you sure you wish to remove the selected rules? This cannot be undone. 선택된 규정을 지울까요? 되돌릴 수 없습니다. Remove Smart Rules 스마트 규정 지우기 Failed to locate any matching songs 맞는 곡이 없습니다 SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. 곡 파일에 접근할 수 없음! 칸타타의 "음악 폴더"와, MPD의 "music_directory" 설정을 확인 바랍니다. Cannot access song files! Please check that the device is still attached. 곡 파일에 접근할 수 없음! 장치가 아직 연결되어 있는지 확인 바랍니다. SongView Lyrics 가사 Information 정보 Metadata 메타데이터 Scroll Lyrics 가사 올리기 Refresh Lyrics 가사 새로 읽기 Edit Lyrics 가사수정 Delete Lyrics File 가사파일 지우기 Refresh Track Information 곡 정보 새로 읽기 Cancel 취소 Track Reload lyrics? Reload from disk, or delete disk copy and download? 가사를 다시 읽을까요? 디스크에서 다시 읽거나, 아니면 디스크 복사본을 지우고 내려받을까요? Reload 다시 읽기 Reload From Disk 디스크에서 다시 읽기 Download 내려받기 Current playing song has changed, still perform search? 지금 연주되는 곡이 바뀌었는데도 여전히 찾을까요? Song Changed 곡 바뀜 Perform Search 찾기 Delete lyrics file? 가사를 지울까요? Delete File 파일 삭제 Artist 연주자 Album artist 음반 연주자 Composer 작곡가 Lyricist 작사가 Conductor 지휘자 Remixer 리믹서 Album 음반 Subtitle 부제 Track number 곡 번호 Disc number 음반 번호 Genre 장르 Date 날짜 Original date 원본 날짜 Comment 설명 Copyright 저작권 Label 음반사 Catalogue number 목록 번호 Title sort 제목 정렬 Artist sort 연주자 정렬 Album artist sort 음반 연주자 정렬 Album sort 음반 정렬 Encoded by 인코딩 Encoder 인코더 Mood 분위기 Media 매체 Bitrate 비트레이트 Sample rate 샘플레이트 Channels 채널 Tagging time 태그 시간 Performer (%1) 공연가 (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits 비트 Performer 공연가 Year 연도 Filename 파일이름 Fetching lyrics via %1 %1에서 가사 가져옴 SoundCloudService Search for tracks from soundcloud.com soundcloud.com에서 곡 찾기 SpaceLabel Calculating... 계산 중... Total space used: %1 전체사용공간: %1 SqlLibraryModel %n Artist(s) %n 연주자 %n Album(s) %n 음반 %n Tracks (%1) %n 곡 (%2) Cue Sheet Cue 시트 Playlist 연주목록 StoredPlaylistsPage Rename 이름 바꾸기 Remove Duplicates 중복 지우기 Initially Collapse Albums 처음에 음반 접기 Are you sure you wish to remove the selected playlists? This cannot be undone. 선택된 연주목록을 지울까요? 돌이킬 수 없습니다. Remove Playlists 연주목록 지움 Playlist Name 연주목록 이름 Enter a name for the playlist: 연주목록 이름을 입력합니다: A playlist named '%1' already exists! Overwrite? 연주목록 이름 '%1'은 이미 있습니다! 덮어쓸까요? Overwrite Playlist 연주목록을 덮어씀 Rename Playlist 연주목록 이름 바꾸기 Enter new name for playlist: 새로운 연주목록을 입력합니다: Cannot add songs from '%1' to '%2' '%1'에서 '%2'로 곡을 추가할 수 없음 StreamDialog Add stream to favourites 스트림을 즐겨찾기에 추가 Name: 이름: URL: URL: Add Stream 스트림 추가 Edit Stream 스트림 수정 <i><b>ERROR:</b> Invalid protocol</i> <i><b>오류:</b> 무효 프로토콜</i> StreamFetcher Loading %1 읽는 중 %1 StreamProviderListDialog Installed 설치됨 Update available 업데이트 있음 Check the providers you wish to install/update. 설치/업데이트하려는 서비스를 선택합니다. Install/Update Stream Providers 스트림 설치/업데이트 Downloading list... 항목을 내려받는 중... Failed to download list of stream providers! 스트림 항목 내려받기 실패함! Installing/updating %1 %1 설치/업데이트하는 중 Failed to install '%1' '%1' 설치 실패함 Failed to download '%1' '%1' 내려받기 실패함 Install/update the selected stream providers? 선택된 스트림 제공자를 설치/업데이트할까요? Install the selected stream providers? 선택된 스트림 제공자를 설치할까요? Update the selected stream providers? 선택된 스트림 제공자를 업데이트할까요? Install/Update 설치/업데이트 Abort installation/update? 설치/업데이트를 취소합니까? Abort 취소 %n Update(s) available %n 업데이트 있음 Downloading %1 %1 내려받는 중 Update all updateable providers 모두 업데이트 StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search 스트림 찾기 Search for radio streams 라디오 스트림 찾기 Enter string to search 찾을 문자를 입력합니다 Not Loaded 읽지 않음 Loading... 읽는 중... %n Entry(s) %n 항목 StreamSearchPage Added '%1'' to favorites '%1'을 즐겨찾기에 추가함 StreamsBrowsePage Import Streams Into Favorites 스트림을 즐겨찾기로 가져오기 Export Favorite Streams 즐겨찾기 스트림을 내보내기 Add New Stream To Favorites 새로운 스트림을 즐겨찾기에 추가 Edit 수정 Seatch For Streams 스트림 찾기 Configure 설정 Digitally Imported Service name Digitally Imported Import Streams 스트림 불러오기 XML Streams (*.xml *.xml.gz *.cantata) XML 스트림 (*.xml *.xml.gz *.cantata) Export Streams 스트림 내보내기 XML Streams (*.xml.gz) XML 스트림 (*.xml.gz) Failed to create '%1'! '%1'을 만들 수 없음! Stream '%1' already exists! 스트림 '%1'은 이미 있습니다! A stream named '%1' already exists! 스트림 이름 '%1'은 이미 있습니다! Bookmark added 책갈피 추가됨 Already bookmarked 책갈피 이미 있음 Already in favorites 즐겨찾기에 있음 Reload '%1' streams? 스트림 '%1'을 다시 읽을까요? Are you sure you wish to remove bookmark to '%1'? '%1'의 책갈피를 지울까요? Are you sure you wish to remove all '%1' bookmarks? '%1'의 모든 책갈피를 지울까요? Are you sure you wish to remove the %1 selected streams? 선택된 스트림 %1을 지울까요? Are you sure you wish to remove '%1'? '%1'을 지울까요? Added '%1'' to favorites '%1'을 즐겨찾기에 추가함 StreamsModel Bookmarks 책갈피 TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites 즐겨찾기 Bookmark Category 카테고리를 책갈피에 추가 Add Stream To Favorites 스트림을 즐겨찾기에 추가 Configure Digitally Imported Digitally Imported 설정 Reload 다시 읽기 Streams 스트림 Radio stations 라디오 방송 Not Loaded 읽지 않음 Loading... 읽는 중... %n Entry(s) %n 항목 StreamsSettings Use the checkboxes below to configure the list of active providers. 서비스를 선택하려면 아래 항목을 사용합니다. Built-in categories are shown in italic, and these cannot be removed. 기본 설치된 카테고리는 기울어져 보이는데, 이는 삭제할 수 없습니다. Configure Streams 스트림 설정 From File... 파일로부터... Download... 내려받기... Configure Provider 서비스 설정 Install 설치 Remove 지우기 Install Streams 스트림 설치 Cantata Streams (*.streams) 칸타타 스트림 (*.streams) A category named '%1' already exists! Overwrite? 카테고리 '%1' 은 이미 있습니다! 덮어쓸까요? Failed top open package file. 패키지 파일을 열 수 없음. Invalid file format! 파일 포맷 오류! Failed to create stream category folder! 카테고리 폴더를 만들 수 없음! Failed to save stream list! 스트림 항목을 저장할 수 없음! Are you sure you wish to remove '%1'? '%1'을 지울까요? Failed to remove streams folder! 스트림 폴더를 지울 수 없음! SyncCollectionWidget Search 찾기 Check Items 항목 확인 Uncheck Items 항목 확인 안 함 SyncDialog Library: 음원: Device: 장치: Loading all songs from library, please wait... 음원의 모든 곡을 읽는 중으로, 잠시 기다리세요... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>음원</code>은 음원의 곡들만 있고 장치의 곡들은 없습니다. 비슷하게 <code>장치</code>는 장치의 곡들만 있습니다.<br/><code>장치</code>에 복사하려는 <code>음원</code>의 곡을 선택하고, <code>음원</code>에 복사하려는<code>장치</code>의 곡을 선택합니다. 그런 다음 <code>동기화</code> 단추를 누릅니다. Synchronize 동기화 Device and library are in sync. 장치와 음원이 동기화됨. Loading all songs from library, please wait...%1%... 음원의 모든 곡을 읽는 중으로, 잠시 기다리세요...%1%... Local Music Library Properties 로컬 음악 음원 속성 Device has been removed! 장치가 제거되었습니다! Device has been changed? 장치가 바뀌었습니까? Device is busy? 장치가 사용 중입니까? TableView Stretch Columns To Fit Window 칸을 늘려 윈도에 맞추기 Left 왼쪽 Center 가운데 Right 오른쪽 Alignment 정렬 TagEditor Track: 곡: Title: 제목: Artist: 연주자: Album artist: 음반연주자: Composer: 작곡가: Album: 음반: Track number: 곡 번호: Disc number: 디스크 번호: Genre: 장르: Year: 연도: Rating: 평점: <i>(Various)</i> <i>(여러)</i> Comment: 설명: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') 다중 장르는 쉼표로 나뉘어야 합니다 (예. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. 평점은 외부 데이터베이스에 저장되며, 곡 파일에 저정되지 <b>않</b>습니다. Tags 태그 Tools 도구 Apply "Various Artists" Workaround "여러 연주자" 해결 적용 Revert "Various Artists" Workaround "여러 연주자" 해결 되돌리기 Set 'Album Artist' from 'Artist' '연주자'에서 '음반연주자' 설정 Capitalize 대문자로 Adjust Track Numbers 곡 번호 조정 Read Ratings from File 파일로부터 평점 읽기 Write Ratings to File 파일에 평점 쓰기 All tracks 전곡 Apply "Various Artists" workaround to <b>all</b> tracks? "여러 연주자" 해결을 <b>전</b>곡에 적용할까요? Apply "Various Artists" workaround? "여러 연주자" 해결을 적용할까요? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>'음반연주자'와 '연주자'를 "여러 연주자"로 설정하고, '제목'을 "곡 연주자 - 곡 제목"으로 바꿉니다</i> Revert "Various Artists" workaround on <b>all</b> tracks? "여러 연주자" 해결을 <b>전</b>곡에 되돌릴까요? Revert "Various Artists" workaround "여러 연주자" 해결을 되돌림 <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>'음반연주자'가 "곡 연주자 - 곡 제목" 형태의 '연주자'와 '제목'이 같다면, '연주자'는 '제목'으로부터 나오고 '제목' 자체는 제목으로 설정됩니다. 예로 <br/><br/>'제목'이 "유재하 - 내 마음에 비친 내 모습"이고 '연주자'가 "유재하"로 설정되면, '제목'은 "내 마음에 비친 내 모습"이 됩니다</i> Revert 되돌림 Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 만약 음반연주자가 비어있다면, '연주자'에서 '음반연주자'를 설정하는 것을 <b>전</b>곡에 적용할까요? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? 만약 음반연주자가 비어있다면, '연주자'에서 '음반연주자'를 설정할까요? Album Artist from Artist 연주자에서 음반연주자 Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? <b>전</b>곡의 문자열 (예. '제목', '연주자' 등) 첫글자를 대문자로 할까요? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? 문자열 (예. '제목', '연주자' 등) 첫글자를 대문자로 할까요? Adjust the value of each track number by: 각 곡 순서를 값대로 늘여갑니다: Adjust track number by: 곡 번호 조정: Read ratings for all tracks from the music files? 음악 파일 모든 곡의 평점을 읽을까요? Read rating from music file? 음악 파일의 평점을 읽을까요? Ratings 평점 Read Ratings 평점 읽기 Read Rating 평점 읽기 Read, and updated, ratings from the following tracks: 아래 곡의 평점을 읽고 업데이트 함: Not all Song ratings have been read from MPD! MPD로부터 모든 곡의 평점을 읽지 않았음! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. 곡 평점은 파일이 아니라, MPD의 'sticker' 데이터베이스에 저정됩니다.실제 파일에 저장하기 위해서, 칸타타는 MPD로부터 평점을 먼저 읽어야 합니다. Song rating has not been read from MPD! MPD로부터 곡 평점을 읽지 않았음! Write ratings for all tracks to the music files? 모든 곡의 평점을 음악 파일에 쓸까요? Write rating to music file? 평점을 음악 파일에 쓸까요? Write Ratings 평점 쓰기 Write Rating 평점 쓰기 Failed to write ratings of the following tracks: 아래 파일의 평점을 쓸 수 없음: Failed to write rating to music file! 음악 파일에 평점을 쓸 수 없음! All tracks [modified] 전곡 [바뀜] %1 [modified] %1 [바뀜]ㄱ %1 (Corrupt tags?) filename (Corrupt tags?) %1 (태그 오류?) Failed to update the tags of the following tracks: 다음 곡들의 태그를 업데이트할 수 없음: Would you also like to rename your song files, so as to match your tags? 태그에 맞추기 위해서, 곡들의 이름을 바꾸겠습니까? Rename Files 파일이름 바꾸기 Rename 이름 바꾸기 Device has been removed! 장치가 제거되었습니다! Device is not connected. 장치가 연결되지 않았음. Device is busy? 장치가 사용 중입니까? TagSpinBox (Various) (여러) ThinSplitter Reset Spacing 간격 새로 맞추기 TitleWidget Click to go back 돌아가려면 누름 Add All To Play Queue 모두 연주목록에 추가 Add All And Replace Play Queue 모두 추가하고 연주목록 바꾸기 ToggleList Available: 사용 가능: Selected: 선택됨: TrackOrganiser Filenames 파일이름 Filename scheme: 파일이름 구성: VFAT safe 안전한 VFAT Use only ASCII characters ASCII 문자만 표시 Replace spaces with underscores 빈칸을 밑줄로 바꿈 Append 'The' to artist names 연주자 이름에 'The'를 붙입니다 Original Name 원래 이름 New Name 새 이름 Ratings will be lost if a file is renamed. 평점은 파일이름이 바뀌면 사라집니다. Organize Files 파일구성 Rename 이름 바꾸기 Remove From List 항목에서 지웁니다 Abort renaming of files? 파일 이름 바꾸기를 취소할까요? Abort 취소 Source file does not exist! 원본 파일이 없음! Skip 건너뜀 Auto Skip 자동 건너뜀 Destination file already exists! 대상 파일이 이미 있음! Failed to create destination folder! 대상 폴더를 만들 수 없음! Failed to rename '%1' to '%2' 파일이름을 '%1'에서 '%2'로 바꿀 수 없습니다 Remove the selected tracks from the list? 항목의 선택된 곡을 지울까요? Remove Tracks 곡을 지움 Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. 곡 평점은 파일이 아니라, MPD의 'sticker' 데이터베이스에 저정됩니다. 파일 (또는 저장된 폴더) 이름을 바꾸면, 곡의 평점은 지워집니다. Device has been removed! 장치가 제거되었습니다! Device is not connected. 장치가 연결되지 않았음. Device is busy? 장치가 사용 중입니까? TrayItem Cantata 칸타타 Now playing 지금 연주 중 UltimateLyricsProvider (Polish Translations) (폴란드어 번역) (Portuguese Translations) (포르투갈어 번역) UmsDevice Not Scanned 검색 안 됨 Not Connected 연결 안 됨 %1 free %1 남음 ValueSlider (recommended) (추천) View Cancel 취소 VolumeSlider Mute 조용히 Unmute 조용히 안 함 Volume %1% (Muted) 음량 %1% (조용히 함) Volume %1% 음량 %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | 연주자|밴드|가수|보컬리스트|음악가 album|score|soundtrack Search pattern for an album, separated by | 음반|평점|사운드트랙 WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. 연주자와 음반정보를 찾을 때 사용할 언어를 선택합니다. Reload 다시 읽기 cantata-2.2.0/translations/cantata_pl.ts000066400000000000000000026025571316350454000203470ustar00rootroot00000000000000 Refresh Album Information Odśwież informacje o albumie Album Album Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Utwory Refresh Artist Information Odśwież informacje o artyście Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Artysta Albums Albumy Web Links Linki sieciowe Similar Artists Podobni Artyści Lyrics Providers Dostawcy tekstów Wikipedia Languages Język Wikipedii Other Inne Reset Spacing Resetuj odstępy &Artist &Artysta Al&bum Al&bum &Track U&twór Read more on last.fm Czytaj więcej na last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Jeśli Cantata nie może znaleźć tekstów, albo znalazła nieprawidłowe, to można użyć tego okna dialogowego do wprowadzenia nowych parametrów wyszukiwania. Na przykład, obecny utwór może być coverem - jeśli tak, to wyszukiwanie tekstu według oryginalnego artysty może pomóc. Jeśli to wyszukiwanie się powiedzie, to znaleziony tekst zostanie przyporządkowany w Cantacie oryginalnemu utworowi i artyście. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Tytuł: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Artysta: Search For Lyrics Szukaj tekstów Choose the websites you want to use when searching for lyrics. Proszę wybrać strony z tekstami. Song Information Informacje o utworze Images (*.png *.jpg) Obrazy (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px Lyrics Teksty Information Informacja Metadata Metadane Scroll Lyrics Przewijaj teksty Refresh Lyrics Odśwież teksty Edit Lyrics Edytuj teksty Delete Lyrics File Usuń plik z tekstami Refresh Track Information Odśwież informacje o utworze Cancel Anuluj Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Utwór Reload lyrics? Reload from disk, or delete disk copy and download? Załadować teksty ponownie? Załadować z dysku, czy usunąć z dysku i pobrać? Reload Odśwież Reload From Disk Załaduj z dysku Download Pobierz Current playing song has changed, still perform search? Obecnie odtwarzany utwór uległ zmianie. Czy nadal wykonać wyszukiwanie? Song Changed Zmieniono utwór Perform Search Szukaj Delete lyrics file? Usunąć plik z tekstami? Delete File Usuń plik Album artist Artysta albumu Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Kompozytor Lyricist Twórca tekstu Conductor Dyrygent Remixer Twórca remixu Subtitle Podtytuł Track number Numer utworu Disc number Numer płyty Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Gatunek Date Data Original date Oryginalna data Comment Komentarz Copyright Prawa autorskie Label Wytwórnia muzyczna Catalogue number Numer katalogowy Title sort Tytuł (sortowanie) Artist sort Artysta (sortowanie) Album artist sort Artysta albumu (sortowanie) Album sort Album (sortowanie) Encoded by Enkodowane przez Encoder Enkoder Mood Nastruj Media Nośnik Bitrate Bitrate Sample rate Częstość próbkowania Channels Kanały Tagging time Czas tagowania Performer (%1) Wykonawca (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bity Performer Wykonawca Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Rok Filename Nazwa pliku Fetching lyrics via %1 Pobieranie tekstów z %1 (Polish Translations) (Polskie tłumaczenia) (Portuguese Translations) (Portugalskie tłumaczenia) Track listing Listing utworów Read more on wikipedia Czytaj więcej na Wikipedii Open in browser Otwórz w przeglądarce artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artysta|zespół|piosenkarz|wokalista|muzyk album|score|soundtrack Search pattern for an album, separated by | album|ścieżka dźwiękowa|kompozycja Choose the wikipedia languages you want to use when searching for artist and album information. Proszę wybrać języki Wikipedii, które mają być użyte przy wyszukiwaniu informacji o artyście i albumie. Cantata is playing a track Cantata odtwarza utwór <b>INVALID</b> <b>NIEWŁAŚCIWY</b> <i>(When different)</i> <i>(Gdy różne)</i> Artists:%1, Albums:%2, Songs:%3 Artyści:%1, Albumy:%2, Utwory:%3 %1 free %1 wolnego miejsca Local Music Library Lokalna biblioteka muzyki Audio CD Płyta audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Nie ma wystarczająco miejsca na docelowym urządzeniu. Zaznaczone utwory zajmują %1, jednak wolnego miejsca zostało jedynie %2. Utwory musiałyby zostać transkodowane do mniejszego rozmiary aby je skopiować z powodzeniem. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Nie ma wystarczająco miejsca w lokalizacji docelowej. Zaznaczone utwory zajmują %1, jednak wolnego miejsca zostało jedynie %2. Copy Songs To Library Kopiuj utwory do biblioteki Copy Songs To Device Skopiuj utwory do urządzenia Copy Songs Skopiuj utwory Delete Songs Usuń utwory You have not configured the destination device. Continue with the default settings? Urządzenie docelowe nie zostało skonfigurowane. Kontynuować z ustawieniami domyślnymi? Not Configured Nie skonfigurowano Use Defaults Użyj domyślnych You have not configured the source device. Continue with the default settings? Urządzenie źródłowe nie zostało skonfigurowane. Kontynuować z ustawieniami domyślnymi? Are you sure you wish to stop? Czy na pewno zatrzymać? Stop Stop Device has been removed! Urządzenie zostało usunięte! Device is not connected! Urządzenie nie jest podłączone! Device is busy? Urządzenie jest zajęte? Device has been changed? Urządzenie zostało zmienione? Clearing unused folders Czyszczenie nieużywanych katalogów Calculate ReplayGain for ripped tracks? Obliczyć ReplayGain zgranych utworów? ReplayGain ReplayGain Calculate Oblicz The destination filename already exists! Istnieje plik o nazwie docelowej! Song already exists! Utwór już istnieje! Song does not exist! Utwór nie istnieje! Failed to create destination folder!<br/>Please check you have sufficient permissions. Tworzenie katalogu docelowego nie powiodło się!<br/>Proszę upewnić się, że ustawione są odpowiednie prawa dostępu. Source file no longer exists? Plik źródłowy już nie istnieje? Failed to copy. Kopiowanie nie powiodło się. Failed to delete. Usuwanie nie powiodło się. Not connected to device. Brak połączenia z urządzeniem. Selected codec is not available. Wybrany kodek nie jest dostępny. Transcoding failed. Konwertowanie nie powiodło się. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Stworzenie pliku tymczasowego nie powiodło się.<br/>(Wymagane przy konwertowaniu do urządzeń MTP.) Failed to read source file. Odczyt pliku źródłowego nie powiódł się. Failed to write to destination file. Zapis do pliku docelowego nie powiódł się! No space left on device. Brak miejsca na urządzeniu. Failed to update metadata. Uaktualnienie metadanych nie powiodło się. Failed to download track. Pobieranie utworu nie powiodło się. Failed to lock device. Zablokowanie urządzenia nie powiodło się. Local Music Library Properties Ustawienia lokalnej biblioteki muzyki Error Błąd Skip Pomiń Auto Skip Automatycznie pomiń Retry Spróbuj ponownie Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Album: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Utwór: Source file: Plik źródłowy: Destination file: Plik docelowy: File: Plik: Calculating... Obliczanie... %1 (Estimated) time (Estimated) %1 (Szacunkowo) Time remaining: Pozostało: Saving cache Zapisywanie pamięci podręcznej Apply "Various Artists" Workaround Zastosuj obejście dla 'Various Artists' Revert "Various Artists" Workaround Cofnij obejście dla 'Various Artists' Capitalize Duża pierwsza litera Adjust Track Numbers Dostosuj numery utworów Tools Narzędzia Apply "Various Artists" workaround? Zastosować obejście dla 'Various Artists'? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" To ustawi tagi 'Artysta albumu' oraz 'Artysta' na "Various Artists" oraz tag 'Tytuł' na "Artysta utworu - Tytuł utworu" Revert "Various Artists" workaround? Przywrócić wartości tagów sprzed obejścia "Various Artists"? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Jeżeli 'Artysta albumu' jest taki sam jak 'Artysta' oraz 'Tytuł' jest formatu "Artysta utworu - Tytuł utworu", to wartość tagu 'Artysta' zostanie wydzielona z tagu 'Tytuł', natomiast nowa wartość tagu 'Tytuł' ustawiona będzie na 'Tytuł utworu', np. Jeśli 'Tytuł' to "Wibble - Wobble", to 'Artysta' zostanie ustawiony na "Wibble" a 'Tytuł' na "Wobble" Revert Odwróć Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Ustawić dużą pierwszą literę tagów 'Tytuł', 'Artysta', 'Artysta albumu' oraz 'Album'? Adjust track number by: Zmień numery utworów o: Reading disc Odczyt płyty CDDB CDDB MusicBrainz MusicBrainz Data Track Ścieżka danych Failed to open CD device Otwarcie urządzenia CD nie powiodło się Track %1 Utwór %1 Failed to create CDDB connection Nawiązanie połączenia z CDDB nie powiodło się Failed to contact CDDB server, please check CDDB and network settings Nawiązanie połączenia z serwerem CDDB nie powiodło się, proszę sprawdzić ustawienia CDDB i sieci No matches found in CDDB Nie znaleziono psujących wyników w CDDB CDDB error: %1 Błąd CDDB: %1 Multiple matches were found. Please choose the relevant one from below: Znaleziono wiele pasujących wyników. Proszę wybrać jeden z poniższych: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Tytuł Disc Selection Wybór płyty %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 Płyta %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Uaktualnianie (%1)... Updating (%1%)... Uaktualnianie (%1%)... Device Properties Ustawienia urządzenia Don't copy covers Nie kopiuj okładek Embed cover within each file Osadź okładkę w każdym pliku No maximum size Brak rozmiaru maksymalnego 400 pixels 400 pixeli 300 pixels 300 pixeli 200 pixels 200 pixeli 100 pixels 100 pixeli <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>W trakcie kopiowania utworów do urządzenia, gdy tag 'ArtystaAlbumu' jest ustawiony na 'Various Artists', Cantata ustawi tag 'Artysta' we wszystkich utworach na 'Various Artists', zaś tag 'Tytuł' na 'ArtystaUtworu - TytułUtworu'.<hr/> W trakcie kopiowania z urządzenia, Cantata sprawdzi, czy oba tagi 'ArtystaAlbumu' oraz 'Artysta' są ustawione na 'Various Artists'. Jeśli tak, to program podejmie próbę przywrócenia rzeczywistej nazwy artysty z tagu 'Tytuł' oraz usunie nazwę artysty z tagu 'Tytuł'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Wybranie tej opcji spowoduje, że Cantata stworzy pamięć podręczną dla biblioteki muzyki wybranego urządzenia. Pozwoli to na przyspieszenie kolejnych skanowań biblioteki (ponieważ zamiast czytać tagi z plików, używane będą informacje z pamięci podręcznej.)<hr/><b>UWAGA:</b> Jeśli używana jest również inna aplikacja w celu zarządzania biblioteką muzyki na urządzeniu, to pamięć podręczna będzie nieaktualna. W celu jej ponownego utworzenia, należy użyć ikony 'odśwież', znajdującej się na liście urządzeń. Spowoduje to usunięcie pamięci podręcznej oraz ponowne przeskanowanie zawartości urządzenia.</p> Do not transcode Nie konwertuj Transcode to %1 Konwertuj do "%1" %1 (%2 free) name (size free) %1 (%2 wolne) Copy To Library Skopiuj do biblioteki Synchronise Synchronizuj Forget Device Zapomnij o urządzeniu Add Device Dodaj urządzenie Lookup album and track details? Czy wyszukać szczegóły albumu i utworów? Refresh Odśwież Via CDDB Za pośrednictwem CDDB Via MusicBrainz Za pośrednictwem MusicBrainz Which type of refresh do you wish to perform? Jaki typ odświeżenia ma zostać przeprowadzony? Partial - Only new songs are scanned (quick) Częściowe - tylko nowe utwory są przeskanowane (szybkie) Full - All songs are rescanned (slow) Pełne - wszystkie utwory są skanowane (wolne) Partial Częściowy Full Pełny Are you sure you wish to delete the selected songs? This cannot be undone. Czy na pewno usunąć zaznaczone utwory? Ta operacja nie może być cofnięta. Are you sure you wish to forget '%1'? Czy na pewno zapomnieć '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Czy na pewno wysunąć płytę audio '%1 - %2'? Eject Wysuń Are you sure you wish to disconnect '%1'? Czy na pewno rozłączyć się z '%1'? Disconnect Rozłącz Please close other dialogs first. Proszę najpierw zamknąć inne okna dialogowe. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://pl.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) to opatentowany algorytm stratnej kompresji danych dźwiękowych.<br>AAC generalnie osiąga lepszą jakość dźwięku niż MP3 przy podobnych rozmiarach pliku. Jest to dobry wybór dla iPodów oraz niektórych innych przenośnych odtwarzaczy muzycznych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Nie jestem pewien, czy "przesadzone" nie jest zbyt kolokwialne. Było "nadmiarowe". Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>AAC</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> - wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem <a href=http://www.ffmpeg.org/faq.html#SEC21>średniego bitrate'u</a> wynikowego utworu.<br><b>150kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>120kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>200kb/s</b> jest prawdopodobnie przesadzone. Expected average bitrate for variable bitrate encoding Szacowany średni bitrate przy kodowaniu ze zmiennym bitrate Smaller file Mniejszy plik Better sound quality Lepsza jakość dźwięku <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://pl.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) jest opatentowanym cyfrowym kodekiem audio używającym stratnej kompresji danych.<br>Pomimo swoich słabych stron, jest bardzo popularnym formatem do przechowywania muzyki oraz jest obsługiwany przez zdecydowaną większość przenośnych odtwarzaczy muzycznych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. nadmiarowe → przesadzone patrz też komentarz do devices/encoders.cpp:62 Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>MP3</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem średniego bitrate'u wynikowego utworu.<br><b>160kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>120kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>205kb/s</b> jest prawdopodobnie przesadzone. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> jest stratnym kodekiem audio wydanym na licencji open-source nie wymagającym licencji patentowych.<br>Generuje mniejsze pliki niż format MP3 przy porównywalnych lub lepszych jakościach. Ogg Vorbis jest ogólnie doskonałym wyborem, szczególnie dla przenośnych odtwarzaczy obsługujących ten format. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. nadmiarowe → przesadzone patrz też komentarz do devices/encoders.cpp:62 Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>Vorbis</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a>- wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Enkoder Vorbis używa wskaźnika jakości ze skali o zakresie od -1 do 10 w celu oceny oczekiwanej jakości. Przedstawiona wartość bitrate jest tylko zgrubnym oszacowaniem (dostarczonym przez twórców kodeka Vorbis) średniej wartości bitrate'u wynikowego pliku przy zadanej jakości. W związku z tym, nowsze i bardziej efektywne wersje kodeka Vorbis tworzą pliki o jeszcze mniejszej wartości bitrate.<br><b>5</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>3</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>8</b> jest prawdopodobnie przesadzone. Quality rating Wskaźnik jakości Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://pl.wikipedia.org/wiki/Opus_(format)>Opus</a> jest wolnym od patentów kodekiem audio używającym stratnej kompresji danych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. nadmiarowe → przesadzone patrz też komentarz do devices/encoders.cpp:62 Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>Opus</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem średniego bitrate'u wynikowego utworu.<br><b>128kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych. <br/>Wartości poniżej <b>100kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>256kb/s</b> jest prawdopodobnie przesadzone. Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) jest kodekiem audio do bezstratnej kompresji muzyki cyfrowej.<br>Polecany jedynie dla odtwarzaczy muzycznych firmy Apple oraz odtwarzaczy nie obsługujących formatu FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) jest bezstratnym kodekiem audio wydanym na licencji open-source nie wymagającym licencji patentowych.<br>Kodek ten jest polecany do przechowywania muzyki bez straty jakości dźwięku. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Poziom kompresji</a> jest liczbą całkowitą z przedziału od 0 do 8 reprezentującą kompromis pomiędzy rozmiarem pliku a prędkością kompresji przy użyciu formatu <b>FLAC</b>.<br/> Ustawienie poziomu kompresji na <b>0</b> daje najkrótsze czasy kompresji jednak generowane pliki są stosunkowo duże.<br/>Z drugiej strony, poziom kompresji równy <b>8</b> powoduje, że kompresja jest dosyć powolna, jednak generowane pliki są najmniejsze.<br/>Należy pamiętać, że format FLAC jest z definicji bezstratny, więc jakość dźwięku wyjściowego jest identyczna z oryginałem niezależnie od ustawionego poziomu kompresji.<br/>Ponadto, poziomy powyżej <b>5</b> dramatycznie podnoszą czas kompresji dając jedynie niewiele mniejsze pliki wyjściowe i są niezalecane. Compression level Poziom kompresji Faster compression Szybsza kompresja Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://pl.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) jest stworzonym przez firmę Microsoft zamkniętym kodekiem audio służącym do stratnej kompresji dźwięku.<br>Zalecany jedynie dla odtwarzaczy muzycznych nie obsługujących formatu Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. nadmiarowe → przesadzone patrz też komentarz do devices/encoders.cpp:62 Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>W związku z ograniczeniami zamkniętego formatu <b>WMA</b> oraz z trudnościami związanymi z analizą działania zamkniętego enkodera, używany przez program Cantata enkoder WMA używa opcji <a https://pl.wikipedia.org/wiki/Stała_przepływność>stały bitrate (CBR)</a>.<br>Z tego powodu wyświetlana wartość bitrate jest dosyć dokładnym oszacowaniem bitrate'u pliku wynikowego.<br><b>136kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>112kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>182kb/s</b> jest prawdopodobnie przesadzone. Filename Scheme Schemat nazwy pliku Various Artists Example album artist Various Artists Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Następujące zmienne zostaną zastąpione ich znaczeniami dla każdego utworu. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Zmienna</em></th><th><em>Przycisk</em></th><th><em>Opis</em></th></tr> Updating... Uaktualnianie.... Reading cache Odczytywanie pamięci podręcznej %1 %2% Message percent %1 %2% Connecting to device... Łączenie się z urządzeniem... No devices found Nie znaleziono urządzeń Connected to device Połączono z urządzeniem Disconnected from device Rozłączono urządzenie Updating folders... Wysyłanie katalogów... Updating files... Uaktualniania plików... Updating tracks... Uaktualnianie utworów... Not Connected Nie połączono %1 (Disc %2) %1 (Płyta %2) No matches found in MusicBrainz Nie znaleziono pasujących wyników w MusicBrainz Connection Połączenie Music Library Biblioteka muzyki A remote device named '%1' already exists! Please choose a different name. Urządzenie zdalne o nazwie '%1' już istnieje! Proszę wybrać inną nazwę. Samba Share Udziały Samby Samba Share (Auto-discover host and port) Udziały Samby (Automatycznie wykryj hosta i porty) Secure Shell (sshfs) Bezpieczna powłoka (sshfs) Locally Mounted Folder Katalog zamontowany lokalnie Available Dostępny Not Available Niedostępny Failed to resolve connection details for %1 Nie powiodło się pobranie szczegółów połączenia dla %1 Connecting... Łączenie... Password prompting does not work when cantata is started from the commandline. Pytanie o hasło nie działa, jeśli Cantata uruchomiana została z wiersza poleceń. No suitable ssh-askpass application installed! This is required for entering passwords. Nie znaleziono odpowiedniej aplikacji ssh-askpass! Jest ona wymagana do wprowadzania haseł. Mount point ("%1") is not empty! Punkt montowania ("%1") nie jest pusty! "sshfs" is not installed! "sshfs" nie jest zainstalowane! Disconnecting... Rozłączanie... "fusermount" is not installed! "fusermount" nie jest zainstalowane! Failed to connect to "%1" Nie powiodło się łączenie z "%1" Failed to disconnect from "%1" Nie powiodło się rozłączanie z "%1" Capacity Unknown Pojemność nieznana Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Szukaj Check Items Zaznacz elementy Uncheck Items Odznacz elementy Library: Biblioteka: Device: Urządzenie Loading all songs from library, please wait... Ładowanie wszystkich utworów z biblioteki, proszę czekać... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Biblioteka</code> wyświetla tylko utwory, które zawarte są w bibliotece ale nie na urządzeniu. Analogicznie <code>Urządzenie</code> wyświetla utwory, które są na urządzeniu.<br/>Należy zaznaczyć utwory z <code>Biblioteki</code>, które mają zostać skopiowane na <code>Urządzenie</code>, oraz zaznaczyć utwory na <code>Urządzeniu</code>, które mają zostać skopiowane do <code>Biblioteki</code>. następnie należy wcisnąć przycisk <code>Synchronizuj</code>. Synchronize Synchronizuj Device and library are in sync. Urządzenie i biblioteka są zsynchronizowane. Loading all songs from library, please wait...%1%... Ładowanie utworów z bilioteki, proszę czekać...%1%... Not Scanned Nie przeskanowano (recommended) (zalecane) Empty filename. Pusta nazwa pliku. Invalid filename. (%1) Nieprawidłowa nazwa pliku. Failed to save %1. Zapisywanie nie powiodło się %1. Failed to delete rules file. (%1) Usunięcie pliku z regułami nie powiodło się. (%1) Invalid command. (%1) Nieprawidłowa komenda. (%1) Could not remove active rules link. Nie można usunąć powiązania dla aktywnej reguły. Active rules is not a link. Aktywna reguła nie jest powiązaniem. Could not create active rules link. Nie można stworzyć powiązania aktywnej reguły. Rules file, %1, does not exist. Plik reguł, %1, nie istnieje. Incorrect arguments supplied. Podano nieprawidłowe argumenty. Unknown method called. Wywołano nieznaną metodę. Unknown error Nieznany błąd Start Dynamic Playlist Uruchom playlistę dynamiczną Stop Dynamic Mode Zatrzymaj tryb dynamiczny Dynamic Playlists Dynamiczne playlisty Dynamically generated playlists Dynamicznie generowane playlisty - Rating: %1..%2 - Ocena: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Należy w systemie zainstalować narzędzie "perl" aby tryb dynamiczny Cantaty mógł działać. Failed to locate rules file - %1 Odnalezienie pliku z regułami nie powiodło się - %1 Failed to remove previous rules file - %1 Usunięcie poprzedniego pliku z regułami nie powiodło się - %1 Failed to install rules file - %1 -> %2 Instalacja pliku z regułami nie powiodła się - %1 -> %2 Dynamizer has been terminated. Dynamizer został wyłączony. Saving rule Zapisywanie reguły Deleting rule Usuwanie reguły Awaiting response for previous command. (%1) Oczekiwanie na odpowiedź dla poprzedniej komendy. (%1) Failed to save %1. (%2) Zapisywanie nie powiodło się %1. (%2) Failed to control dynamizer state. (%1) Kontrola stanu dynamizera nie powiodła się. (%1) Failed to set the current dynamic rules. (%1) Ustawianie dynamicznych reguł nie powiodło się. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Dodaj Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Edytuj Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Usuń Remote dynamizer is not running. Zdalny dynamizer nie działa. Are you sure you wish to remove the selected rules? This cannot be undone. Czy na pewno usunąć zaznaczone reguły? Ta operacja nie może być cofnięta. Remove Dynamic Rules Usuń dynamiczne reguły Dynamic Rule Dynamiczna reguła <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>BŁĄD</b>: 'Od roku' powinno mieć mniejszą wartość niż 'Do roku'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>BŁĄD:</b> Zakres dat jest zbyt duży (może wynosić maksymalnie %1 lat)</i> SimilarArtists PodobniArtyści AlbumArtist ArtystaAlbumu Include Zawiera Exclude Nie zawiera (Exact) (Dokładnie) Dynamic Rules Dynamiczne reguły None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Brak About dynamic rules Informacje o dynamicznych regułach Failed to save %1 Nie powiodło się zapisywanie %1 A set of rules named '%1' already exists! Overwrite? Zestaw reguł o nazwie '%1' już istnieje! Nadpisać? Overwrite Rules Nadpisz reguły Saving %1 Zapisywanie %1 Deleting... Usuwanie... Name Nazwa Item Count Liczba elementów Space Used Użyta przestrzeń Total space used: %1 Całkowita użyta przestrzeń: %1 Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata przechowuje w pamięci podręcznej różne informacje (okładki, teksty itp.). Poniżej przedstawiono podsumowanie użycia pamięci podręcznej programu Cantata. Covers Okładki Scaled Covers Przeskalowane okładki Backdrops Tło Artist Information Informacje o artyście Album Information Informacje o albumie Track Information Informacje o utworze Stream Listings Listy strumieni Podcast Directories Katalogi podcastów Scrobble Tracks Scrobbluj utwory Delete All Usuń wszystko Delete all '%1' items? Czy usunąć wszystkie '%1' elementów? Delete Cache Items Usuń elementy pamięci podręcznej Delete items from all selected categories? Usunąć elementy ze wszystkich zaznaczonych kategorii? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Obecna okładka CoverArt Archive Archiwum okładek Image Obraz Downloading... Pobieranie... Image (%1 x %2 %3%) Image (width x height zoom%) Obraz (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Istnieje już obraz dla danego artysty, a plik nie może być nadpisany. A cover already exists for this album, and the file is not writeable. Istnieje już okładka dla danego albumu, a plik nie może być nadpisany. '%1' Artist Image '%1' Obraz artysty '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Okładka albumu Failed to set cover! Could not download to temporary file! Ustawienie okładki nie powiodło się! Nie można pobrać do tymczasowego pliku! Failed to download image! Pobieranie obrazu nie powiodło się! Load Local Cover Załaduj okładkę lokalną File is already in list! Plik istnieje już na liście! Failed to read image! Odczyt obrazu nie powiódł się! Display Wyświetl Failed to set cover! Could not make copy! Ustawienie okładki nie powiodło się! Nie można stworzyć kopii! Failed to set cover! Could not backup original! Ustawienie okładki nie powiodło się! Nie można stworzyć kopii zapasowej oryginału! Failed to set cover! Could not copy file to '%1'! Ustawienie okładki nie powiodło się! Nie można skopiować pliku do '%1'! Searching... Szukanie... Custom Actions Własne akcje Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Nazwa: Command: Polecenie: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. W linii komend powyżej, %f zostanie zastąpione przez listę plików a %d przez listę katalogów. Jeżeli żadne z nich nie zostaną podane, lista plików zostanie dodana na koniec komendy. c-format Add New Command Dodaj nowe polecenie Edit Command Edytuj polecenie To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Aby program Cantata wykonywał zewnętrzne polecenia (np. w celu edycji tagów poprzez inną aplikację), należy poniżej dodać wpis dla tego polecenia. Jeżeli chociaż jedno polecenie jest zdefiniowane, do menu kontekstowego dla Biblioteki, Katalogów i Playlist zostaje dodane menu 'Własne akcje'. Command Polecenie Remove the selected commands? Usunąć zaznaczone polecenia? Open In File Manager w pliku doplphin.desktop jest "menedżer", więc zmieniam dla spójności Otwórz w menedżerze plików Connection Established Nawiązano połączenie Connection Failed Połączenie nie powiodło się Grouped Albums Grupuj Albumy Table Tabela Parse in Library view, and show in Folders view Przetwarzaj w widoku Biblioteki i pokazuj w widoku Katalogów Only show in Folders view Pokazuj tylko w widoku katalogów Do not list Nie pokazuj na liście Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Kolejka odtwarzania Library Biblioteka Folders Katalogi Playlists Playlisty Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet - strumienie, Jamendo, Maganatune, SoundCloud, oraz podkasty Devices - UMS, MTP (e.g. Android), and AudioCDs Urządzenia - UMS, MTP (np. Android), i płyty AudioCDs Search (via MPD) Szukaj (przez MPD) Info - Current song information (artist, album, and lyrics) Informacje - szczegóły dotyczące obecnego utworu (artysta, album i teksty) Large Duże Small Małe Tab-bar Pasek kart Left Z lewej Right Z prawej Top U góry Bottom U dołu Notifications Powiadomienia System default Domyślne systemowe Show Artist Images Pokazuj obrazy artysty Sort Albums Sortuj albumy Album, Artist, Year Album, artysta, rok Album, Year, Artist Album, rok, artysta Artist, Album, Year Artysta, album, rok Artist, Year, Album Artysta, rok, album Year, Album, Artist Rok, album, artysta Year, Artist, Album Rok, artysta, album Modified Date Data modyfikacji Group By Grupuj według Configure Cantata... Konfiguruj Cantatę... Preferences Ustawienia Quit Zamknij About Cantata... Qt-only O programie Cantata... Show Window Pokaż okno Server information... Informacja serwera... Refresh Database Odśwież bazę danych Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Połącz Collection Kolekcja Outputs Wyjścia Stop After Track Zatrzymaj po utworze Add To Stored Playlist Dodaj do zapisanej playlisty Crop Others Przytnij pozostałe Add Stream URL Dodaj URL strumienia Clear Wyczyść Center On Current Track Wycentruj na obecnym utworze Expanded Interface Interfejs rozszerzony Show Current Song Information Pokaż informacje o obecnym utworze Full Screen Pełen ekran Random Losowo Repeat Powtarzaj Single Tryb pojedynczy When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Gdy tryb 'Pojedynczy' jest aktywny, odtwarzanie jest zatrzymywane po obecnym utworze; utwór jest zapętlany, jeśli dodatkowo jest włączony tryb 'Powtarzaj'. Consume Tryb konsumowania When consume is activated, a song is removed from the play queue after it has been played. Gdy tryb konsumowania jest aktywny, obecny utwór jest usuwany z kolejki po jego zakończeniu. Find in Play Queue Znajdź w kolejce odtwarzania Play Stream Odtwarzaj strumień Locate In Library Znajdź w bibliotece Expand All Rozwiń wszystkie Collapse All Zwiń wszystkie Internet Internet Devices Urządzenia Info Informacje Show Menubar Pokaż pasek menu &Music &Muzyka &Edit &Edytuj &View &Widok &Queue &Kolejka &Settings &Ustawienia &Help &Pomoc Set Rating Ustaw ocenę No Rating Brak oceny Failed to locate any songs matching the dynamic playlist rules. Nie udało się znaleźć żadnych utworów pasujących do reguł dynamicznej playlisty. Connecting to %1 Łączenie z %1 Refresh MPD Database? Odświeżyć bazę danych MPD? About Cantata Qt-only O programie Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Qt-only <b>Cantata %1</b><br/><br/>klient MPD.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Program wydany na licencji <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Na podstawie <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 Autorzy QtMPC<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Tło widoku kontekstowego za uprzejmością <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Metadane widoku kontekstowego za uprzejmością <a href="http://www.wikipedia.org">Wikipedii</a> i <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Proszę rozpatrzeć przesłanie własnych obrazów na <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Obecnie trwa pobieranie podcastu Wyjście z programu spowoduje przerwanie pobierania. Abort download and quit Przerwij pobieranie i zakończ Enabled: %1 Włączone: %1 Disabled: %1 Wyłączone: %1 Server Information Informacje serwera <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Serwer</b></td></tr><tr><td align="right">Protokół:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Czas pracy:&nbsp;</td><td>%4</td></tr><tr><td align="right">Odtwarzanie:&nbsp;</td><td>%5</td></tr><tr><td align="right">Obsługa URL:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tagi:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Baza danych</b></td></tr><tr><td align="right">Artyści:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albumy:&nbsp;</td><td>%2</td></tr><tr><td align="right">Utwory:&nbsp;</td><td>%3</td></tr><tr><td align="right">Czas trwania:&nbsp;</td><td>%4</td></tr><tr><td align="right">Aktualizacja:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD zgłosił następujący błąd: %1 Cantata Cantata Playback stopped Odtwarzanie zatrzymane Remove all songs from play queue? Usunąć wszystkie utwory z kolejki odtwarzania? Priority Priorytet Enter priority (0..255): Wpisz priorytet (0..255): Playlist Name Nazwa playlisty Enter a name for the playlist: Proszę wpisać nazwę dla playlisty: '%1' is used to store favorite streams, please choose another name. '%1' jest używane do przechowywania ulubionych strumieni, proszę wybrać inną nazwę. A playlist named '%1' already exists! Add to that playlist? Playlista o nazwie '%1' już istnieje! Dodać do tej playlisty? Existing Playlist Istniejąca playlista Auto Automatycznie <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Połączono z %1<br/>Poniższe ustawienia dotyczą aktualnie połączonej kolekcji MPD.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Brak połączenia.<br/>Poniższe ustawienia nie mogą być modyfikowane, ponieważ Cantata nie jest połączona z MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> ReplayGain jest propozycją standardu opublikowaną w 2001 mającą na celu znormalizowanie odczuwanej głośności komputerowych formatów dźwięku takich jak MP3 albo Ogg Vorbis. ReplayGain operuje na utworach i całych albumach i jest obsługiwany na rosnącej liczbie odtwarzaczy.<br/><br/>Następujące ustawienia ReplayGain mogą być użyte:<ul><li><i>Brak</i> - Informacja ReplayGain nie jest używana.</li><li><i>Utwór</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain utworu.</li><li><i>Album</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain albumu.</li><li><i>Automatycznie</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain utworu jeśli włączone jest odtwarzanie losowe, w przeciwnym wypadku używany jest tag ReplayGain albumu.</li></ul> Rename Zmień nazwę Remove Duplicates Usuń duplikaty Initially Collapse Albums Wstępnie zwiń albumy Are you sure you wish to remove the selected playlists? This cannot be undone. Czy na pewno usunąć zaznaczone playlisty? Ta operacja nie może być cofnięta. Remove Playlists Usuń playlisty A playlist named '%1' already exists! Overwrite? Playlista o nazwie '%1' już istnieje! Nadpisać? Overwrite Playlist Nadpisz playlisty Rename Playlist Zmiana nazwy playlisty Enter new name for playlist: Proszę wpisać nową nazwę dla playlisty: Cannot add songs from '%1' to '%2' Nie można dodać utworów z '%1' do '%2' 1 Track 1 Utwór %1 Utwory %1 Utworów %1 Tracks 1 Track (%2) 1 Utwór (%2) %1 Utwory (%2) %1 Utworów (%2) %1 Tracks (%2) 1 Album 1 Album %1 Albumy %1 Albumów %1 Albums 1 Artist 1 Artysta %1 Artystów %1 Artystów %1 Artists 1 Stream 1 strumień %1 strumienie %1 strumieni %1 Streams 1 Entry 1 Element %1 Elementy %1 Elementów %1 Entries 1 Rule 1 reguła %1 reguły %1 reguł %1 Rules 1 Podcast 1 Podcast %1 Podcasty %1 Podcastów %1 Podcasts 1 Episode 1 Odcinek %1 Odcinki %1 Odcinków %1 Episodes 1 Update available 1 aktualizacja dostępna %1 aktualizacje dostępne %1 aktualizacji dostępnych %1 Updates available Collection Settings Ustawienia kolekcji Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Odtwarzanie Playback Settings Ustawienia odtwarzania Downloaded Files Pobrane pliki Downloaded Files Settings Ustawienia pobranych plików Interface Interfejs Interface Settings Ustawienia interfejsu Info View Settings Ustawienia widoku informacyjnego Scrobbling Scrobbling Scrobbling Settings Ustawienia scrobblowania Audio CD Settings Ustawienia płyt audio Proxy Proxy Proxy Settings Qt-only Ustawienia Proxy Shortcuts Qt-only Skróty Keyboard Shortcut Settings Qt-only Ustawienia skrótów klawiszowych Cache Pamięć podręczna Cached Items Elementy w pamięci podręcznej Cantata Preferences Ustawienia Cantaty Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Konfiguruj Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Kompozytor: Performer: Wykonawca: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Gatunek: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Komentarz: Date: Data: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Wyszukiwanie utworów będzie brało pod uwagę tag 'Data'.<br/><br/>Zazwyczaj podanie samego roku powinno wystarczyć. Modified: Zmodyfikowano: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Aby wyszukać pliki zmienione od daty należy wprowadzić datę w formacie YYYY/MM/DD - np. 2015/01/31.<br/><br>Wprowadzenie liczby spowoduje wyszukanie plików zmienionych od podanej liczby dni. Any: Dowolne: No tracks found. Nie znaleziono utworów. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Host: Which type of collection do you wish to connect to? Do jakiego typu kolekcji program ma się połączyć? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Standardowy - kolekcja muzyki może być współdzielona, znajduje się na innej maszynie, jest już skonfigurowa albo ma być używana z innymi klientami (np. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Podstawowy - kolekcja muzyki nie jest współdzielona a program Cantata skonfiguruje i będzie kontrolować program MPD. Taka konfiguracja będzie dostępna wyłącznie dla programu Cantata i <b>nie</b> będzie można jej używać z innymi klientami MPD. If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' i18n: file: gui/initialsettingswizard.ui:236 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel_2) Jeżeli mają być używane zaawansowane ustawienia MPD (np. wiele wyjść audio, pełne wsparcie DSD, itp.) wtedy <b>trzeba</b> wybrać opcję 'Standardowy' <i><b>NOTE:</b> %1</i> <i><b>UWAGA:</b> %1</i> Add Collection Dodaj kolekcję Standard Standardowa Basic Podstawowa Delete '%1'? Usunąć '%1'? Delete Usuń New Collection %1 Nowa kolekcja %1 Default Domyślne Previous Track Poprzedni utwór Next Track Następny utwór Play/Pause Odtwarzaj/Wstrzymaj Stop After Current Track Zatrzymaj po obecnym utworze Increase Volume Zwiększ głośność Decrease Volume Zmniejsz głośność Save As Zapisz jako Append Dodaj na koniec Append To Play Queue Dodaj na koniec kolejki odtwarzania Append And Play Dodaj na koniec i odtwarzaj Add And Play Dodaj i odtwarzaj Append To Play Queue And Play Dodaj do kolejki odtwarzania i odtwarzaj Insert After Current Wstaw za obecnym Append Random Album Dodaj losowy album Play Now (And Replace Play Queue) Odtwarzaj teraz (i zastąp kolejkę odtwarzania) Add With Priority Dodaj z priorytetem Set Priority Ustaw priorytet Highest Priority (255) Najwyższy priorytet (255) High Priority (200) Wysoki priorytet (200) Medium Priority (125) Średni priorytet (125) Low Priority (50) Niski priorytet (50) Default Priority (0) Domyślny priorytet (0) Custom Priority... Inny priorytet... Add To Playlist Dodaj do playlisty Organize Files Organizuj pliki Edit Track Information Edytuj informacje o utworze Set Image Ustaw obraz Find Szukaj Add To Play Queue Dodaj do kolejki odtwarzania Now playing Teraz odtwarzane Play Odtwarzaj Pause Wstrzymaj Cue Sheet Plik .cue Playlist Playlista Configure Device Konfiguruj urządzenie Refresh Device Odśwież urządzenie Connect Device Połącz z urządzeniem Disconnect Device Rozłącz urządzenie Edit CD Details Edytuj szczegóły CD No Devices Attached Brak podłączonych urządzeń Not logged in Nie zalogowano Logged in Zalogowano No subscriptions Brak subskrypcji You do not have an active subscription Nie ma aktywnych subskrypcji Logged in (expiry:%1) Zalogowano (wygaśnięcie: %1) Session expired Sesja wygasła Parse error loading cache file, please check your songs tags. Błąd przetwarzania podczas ładowania plików pamięci podręcznej, zaleca się sprawdzenie tagów utworów. %1 by %2 Album by Artist %1 w wykonaniu %2 New Playlist... Nowa playlista... Stored Playlists Przechowywane playlisty Standard playlists Standardowe playlisty Smart Playlist Inteligentna lista odtwarzania # Track number # Length Długość Disc Płyta Rating Ocena Undo Cofnij Redo Ponów Shuffle Wymieszaj Sort By Sortuj przy użyciu Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Artysta albumu Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Tytuł utworu # (Track Number) # (Numer utworu) TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Szukaj strumienia Search for radio streams Szukaj strumieni radiowych Enter string to search Wprowadź termin do wyszukiwania Not Loaded Nie załadowano Loading... Ładowanie... Bookmarks Zakładki IceCast IceCast Favorites Favourites Bookmark Category Kategoria zakładek Add Stream To Favorites Dodaj strumień do ulubionych Configure Digitally Imported Skonfiguruj Digitally Imported Streams Strumienie Radio stations Stacje radiowe Unknown Nieznany "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Connection to %1 failed Połączenie z %1 nie powiodło się Connection to %1 failed - please check your proxy settings Połączenie z %1 nie powiodło się - proszę sprawdzić ustawienia proxy Connection to %1 failed - incorrect password Połączenie z %1 nie powiodło się - nieprawidłowe hasło Failed to send command to %1 - not connected Nie powiodło się wysyłanie komendy do %1 - nie połączono Failed to load. Please check user "mpd" has read permission. Załadowanie nie powiodło się. Proszę upewnić się, że użytkownik "mpd" ma prawa odczytu. Failed to load. MPD can only play local files if connected via a local socket. Załadowanie nie powiodło się. MPD może odtwarzać lokalne pliki tylko gdy jest połączony poprzez lokalne gniazdo. Failed to send command. Disconnected from %1 Wysyłanie komendy nie powiodło się. Rozłączono z %1 Failed to rename <b>%1</b> to <b>%2</b> Zmiana nazwy z <b>%1</b> na <b>%2</b> nie powiodła się Failed to save <b>%1</b> Zapisanie <b>%1</b> nie powiodło się You cannot add parts of a cue sheet to a playlist! Nie można dodać części pliku cue do playlisty! You cannot add a playlist to another playlist! Nie można dodać playlisty do innej playlisty! Failed to send '%1' to %2. Please check %2 is registered with MPD. Wysłanie "%1" do %2 nie powiodło się. Należy sprawdzić, czy %2 jest zarejestrowany w MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. Nie można przechowywać ocen ponieważ komenda MPD 'sticker' nie jest obsługiwana. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Pojedyncze utwory Personal Osobiste Various Artists Various Artists <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> na <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> w wykonaniu <b>%2</b> na <b>%3</b> No proxy Brak Proxy Use the system proxy settings Użyj systemowych ustawień proxy Manual proxy configuration Ręczne ustawienia proxy The world's largest digital service for free music Największszy na świecie cyfrowy serwis darmowej muzyki Jamendo Settings Ustawienia Jamendo MP3 MP3 Ogg Ogg Streaming format: Format strumienia: Streaming Strumieniowanie MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Online music from magnatune.com Muzyka online z magnatune.com Magnatune Settings Ustawienia Magnatune Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Użytkownik: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Hasło: Membership: Członkostwo: Downloads: Pobieranie: Failed to parse Parsowanie nie powiodło się Downloading...%1% Pobieranie...%1% Parsing music list.... Przetwarzanie listy muzyki... Failed to download Pobieranie nie powiodło się The music listing needs to be downloaded, this can consume over %1Mb of disk space Listing muzyki musi zostać pobrany, to może zająć ponad %1Mb miejsca na dysku Dowload music listing? Pobrać listing muzyki? Re-download music listing? Pobrać ponownie listing muzyki? RSS: RSS: Website: Strona: Podcast details Szczegóły podcastu Select a podcast to display its details Zaznacz podcast aby wyświetlić jego szczegóły Enter search term... Wprowadź termin do wyszukiwania... Failed to fetch podcasts from %1 Pobieranie podcastów z %1 nie powiodło się There was a problem parsing the response from %1 Wystąpił problem podczas parsowania odpowiedzi z %1 Failed to download directory listing Pobranie listingu katalogów nie powiodło się Failed to parse directory listing Parsowanie listingu katalogów nie powiodło się URL URL Enter podcast URL... Podaj adres URL podcastu... Load Załaduj Enter podcast URL below, and press 'Load' Wprowadź poniżej URL podcastu i wciśnij przycisk "Załaduj" Invalid URL! Nieprawidłowy URL! Failed to fetch podcast! Pobieranie podcastu nie powiodło się! Failed to parse podcast. Parsowanie podcastu nie powiodło się. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata obsługuje jedynie podcasty audio! Podany URL zawiera wyłącznie podcasty wideo. Subscribe Subskrybuj Enter URL Wprowadź URL Manual podcast URL Ręczny URL podcastu Search %1 Szukaj %1 Search for podcasts on %1 Szukaj podcastów na %1 Add Podcast Subscription Dodaj subskrypcję podcastu Browse %1 Przeglądaj %1 Browse %1 podcasts Przeglądaj %1 podcastów You are already subscribed to this podcast! Subskrypcja do danego podcastu już istnieje! Subscription added Dodano subskrypcję Subscribe to RSS feeds Zasubskrybuj kanał RSS %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Pobieranie: %1%) Failed to parse %1 Parsowanie %1 nie powiodło się Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata obsługuje jedynie podcasty audio! %1 zawiera wyłącznie podcasty wideo. Failed to download %1 Pobieranie %1 nie powiodło się Check for new episodes: Sprawdź w poszukiwaniu nowych odcinków: Download episodes to: Pobierz odcinki do: Download automatically: Pobieraj automatycznie: Podcast Settings Ustawienia podcastów Manually Ręcznie Every 15 minutes Co 15 minut Every 30 minutes Co 30 minut Every hour Co godzinę Every 2 hours Co 2 godziny Every 6 hours Co 6 godzin Every 12 hours Co 12 godzin Every day Codziennie Every week Co tydzień Don't automatically download episodes Nie pobieraj automatycznie odcinków Latest episode Najnowszy odcinek Latest %1 episodes Najnowsze %1 odcinków All episodes Wszystkie odcinki Add Subscription Dodaj subskrypcję Remove Subscription Usuń subskrypcję Download Episodes Pobierz odcinki Delete Downloaded Episodes Usuń pobrane odcinki Cancel Download Anuluj pobieranie Mark Episodes As New Oznacz odcinek jako nowy Mark Episodes As Listened Oznacz odcinek jako odsłuchany Unsubscribe from '%1'? Wypisać się z '%1'? Do you wish to download the selected podcast episodes? Czy pobrać zaznaczone odcinki podcastu? Cancel podcast episode downloads (both current and any that are queued)? Anulować pobieranie odcinków podcastu (zarówno obecnych jak i zakolejnowanych)? Do you wish to the delete downloaded files of the selected podcast episodes? Czy usunąć pobrane pliki zaznaczonych odcinków podcastu? Do you wish to mark the selected podcast episodes as new? Czy oznaczyć zaznaczone odcinki podcastu jako nowe? Do you wish to mark the selected podcast episodes as listened? Czy oznaczyć zaznaczone odcinki podcastu jako wysłuchane? Refresh all subscriptions? Odświeżyć wszystkie subskrypcje? Refresh All Odśwież wszystkie Refresh all subscriptions, or only those selected? Odświeżyć wszystkie subskrypcje, czy tylko zaznaczone? Refresh Selected Odśwież zaznaczone Search for tracks from soundcloud.com Szukaj utworów na soundcloud.com Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Obraz tła Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Obraz artysty Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Własny obraz Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Rozmycie: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Przezroczystość: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) Automatycznie zmień na widok po: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) Nie zmieniaj automatycznie ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) ms Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Ciemnie tło Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Przyciemnij tło oraz użyj białego tekstu, niezależnie od obecnej palety kolorów. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Zawsze zwiń do pojedynczego panelu Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) Pokazuj wyłącznie "Artystę", "Album" lub "Tekst", nawet jeśli jest wystarczająco miejsca na pokazanie wszystkich trzech. Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Pokazuj tylko podstawowy tekst z Wikipedii Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) Cantata pokazuje jedynie skrócone wersje stron wikipedii (bez obrazków, linków itp.). Skracanie stron nie zawsze jest poprawne w 100%, dlatego domyślnie Cantata pokazuje jedynie wstęp. Przy wybraniu opcji pokazania pełnego artykułu może dojść do błędów parsowania. W takim wypadku konieczne będzie również usunięcie artykułów obecnie przechowywanych w pamięci podręcznej (używając strony 'Pamięć podręczna'). no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Dostępny: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Wybrany: Calculating size of files to be copied, please wait... i18n: file: devices/actiondialog.ui:86 i18n: ectx: property (text), widget (QLabel, fileS) Obliczanie rozmiaru plików do skopiowania, proszę czekać... Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Kopiuj utwory z: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Wymaga konfiguracji) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Kopiuj utwory do: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Format docelowy: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Nadpisz utwory To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Do skopiowania: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Szczegóły albumu Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Rok: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Płyta: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Pojedynczy artysta Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Pobieranie informacji albumu i utworów Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Najpierw wyszukaj poprzez: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) Host CDDB: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) Port CDDB: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Sprawdź informacje jak tylko płyta CD zostanie włożona Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Zgrywanie audio Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Tryb full paranoia mode (najlepsza jakość) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Nigdy nie pomijaj przy błędach odczytu These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) Te ustawienia są poprawne i można je edytować jedynie gdy urządzenie jest podłączone. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Katalog z muzyką: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Kopiuj okładki albumów jako: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Maksymalny rozmiar okładek: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Domyślny wolumin: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) Obejście dla 'Various Artists' Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Automatycznie skanuj muzykę po podłączeniu Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Używaj pamięci podręcznej Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Nazwy plików Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Schemat nazwy pliku: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) Bezpieczne dla VFAT Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Używaj jedynie znaków ASCII Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Zastąp spacje znakami podkreślenia Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) Dodaj 'The' to nazw artystów If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' i18n: file: devices/devicepropertieswidget.ui:195 i18n: ectx: property (toolTip), widget (QCheckBox, ignoreThe) Jeżeli nazwa artysty rozpoczyna się od 'The', to dodaj predrostek do nazwy katalogu, np. 'The Beatles' zmieni się w 'Beatles, The' Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Konwertowanie Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Konwertuj tylko gdy plik wejściowy jest w innym formacie Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Przykład: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) O schematach nazwy pliku The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) Artysta albumu. Dla większości albumów będzie to taka sama wartość jak <i>artysta utworu.</i> Dla kompilacji często przyjmie wartość <i>Various Artists.</i> The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) Nazwa albumu. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Tytuł albumu The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) Kompozytor. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) Artysta każdego utworu. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Artysta utworu The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) Tytuł utworu (bez <i>artysty utworu</i>). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) Tytuł utworu (z <i>artystą utworu</i>, jeśli różny od <i>artysty albumu</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Tytuł utworu (+Artysta) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Numer utworu. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Numer utworu The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) Numer płyty w wydaniach kilkupłytowych. Kompilacje często składają się z wielu płyt. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) Numer CD The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) Rok wydania albumu. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) Gatunek albumu. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) Te ustawienia można edytować jedynie gdy urządzenie nie jest podłączone. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Typ: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Opcje Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Port: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Użytkownik: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Domena: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Udział: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) Jeżeli hasło zostanie wpisane tutaj, to będzie ono przechowywane w postaci <b>niezaszyfrowanej</b> w pliku konfiguracyjnym Cantaty. Aby program Cantata za każdym razem pytał o hasło podczas dostępu do udziału należy hasło ustawić na '-' Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Nazwa serwisu: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Katalog: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Opcje dodatkowe: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) W związku z mechanizmem działania sshfs, odpowiednia aplikacja typu ssh-askpass (ksshaskpass, ssh-askpass-gnome, itp.) konieczna jest do wpisania hasła. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) To okno dialogowe jest używane jedynie do dodawania zdalnych urządzeń (np. za pomocą protokołu Samba) albo w celu dostępu do lokalnie zamontowanych katalogów. W przypadku normalnych odtwarzaczy mediów podłączonych przez USB Cantata automatycznie wyświetli urządzenie gdy będzie ono podłączone. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Nazwa dynamicznych reguł - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) sekund About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) O regułach Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Zawieraj utwory posiadające w tagach: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Nie zawieraj utworów posiadających w tagach: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Artyści podobni do: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Artysta albumu: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) Od roku: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Dowolny To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) Do roku: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Dokładne dopasowanie Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) Należy podać tylko wartości tagów, które mają być kryteriami wyszukiwania. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) Dla gatunku, zakończenie tekstu przy użyciu gwiazdki dopasuje wiele gatunków, np. 'rock*' dopasuje zarówno 'Hard Rock' jak i 'Rock and Roll'. Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Dodaj plik lokalny Save downloaded covers, artist, and composer images, in music folder i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/filesettings.ui:32 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) i18n: file: gui/initialsettingswizard.ui:675 i18n: ectx: property (text), widget (QCheckBox, storeCoversInMpdDir) Zapisz pobrane okładki oraz obrazy artystów i kompozytorów w katalogu muzyki Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Zapisz pobrane teksty w katalogu muzyki Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Zapisz pobrane tła w katalogu muzyki If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) Jeżeli wybrano opcję, aby program Cantata przechowywał okładki, teksty oraz tła w katalogu muzyki, a użytkownik nie ma praw do zapisu w tym katalogu, to Cantata wróci do opcji zapisu tych plików w osobistym katalogu pamięci podręcznej. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/filesettings.ui:60 i18n: ectx: property (text), widget (NoteLabel, persNote_2) i18n: file: gui/initialsettingswizard.ui:703 i18n: ectx: property (text), widget (NoteLabel, persNote_2) Program Cantata może zapisaćtła, obrazy artysów i kompozytorów w hierarchii katalogów tylko jeśli ma ona głębokość 2 poziomów, tzn. 'Artysta/Album/Utwory'. Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Pierwsze uruchomienie programu Cantata Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Witamy w programie Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>Cantata jest przyjaznym dla użytkownika i bogatym w funkcje klientem Music Player Daemon (MPD). MPD jest elastyczną i potężną aplikacją serwerową do odtwarzania muzyki.</p><p>W celu uzyskania informacji na temat MPD prosimy o odwiedzenie strony <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Ten 'kreator konfiguracji' pomoże przy podstawowych ustawieniach potrzebnych do poprawnego działania programu Cantata.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Witaj w programie Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> i18n: file: gui/initialsettingswizard.ui:134 i18n: ectx: property (text), widget (QLabel, label_8) <p>Cantata jest bogatym w funkcje i przyjaznym dla użytkownika klientem programu Music Player Daemon (MPD). MPD to elastyczna i funkcjonalna aplikacja serwerowa do odtwarzania muzyki. MPD może być uruchamiany zarówno dla całego systemu, lub dla poszczególnych użytkowników.<br/><br/>Proszę wybrać, jak Cantata ma początkowo połączyć się (lub uruchamiać) MPD:</p> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Standardowa wielo-użytkownikowa/serwerowa konfiguracja <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> i18n: file: gui/initialsettingswizard.ui:172 i18n: ectx: property (text), widget (BuddyLabel, label_10) <i>Proszę wybrać tę opcję, jeśli kolekcja muzyki jest współdzielona, znajduje się na innej maszynie, jest już skonfigurowa albo ma być używana z innymi klientami (np. MPDroid). Wybranie tej opcji spowoduje, że program Cantata nie będzie mógł kontrolować uruchamiania i wyłączania serwera MPD. Dlatego należy się upewnić, że MPD jest już skonfigurowany i uruchomiony.</i> Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Podstawowa konfiguracja dla pojedynczego użytkownika <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> i18n: file: gui/initialsettingswizard.ui:217 i18n: ectx: property (text), widget (BuddyLabel, label_9) <i>Proszę wybrać tę opcję jeśli kolekcja muzyki nie jest współdzielona a konfiguracją i kontrolą MPD ma zająć się Cantata. Takie ustawienie będzie dostępna wyłącznie dla programu Cantata i <b>nie</b> będzie można jej używać z innymi klientami MPD (np. MPDroid)</i> For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. i18n: file: gui/initialsettingswizard.ui:259 i18n: ectx: property (text), widget (QLabel, label_11) Aby znaleźć więcej informacji należy odwiedzić stronę MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>Ten 'kreator konfiguracji' pomoże przy podstawowych ustawieniach potrzebnych do poprawnego działania programu Cantata. Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Szczegóły połączenia The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Poniższe ustawienia są podstawowymi ustawieniami wymaganymi przez program Cantata. Proszę wpisać odpowiednie dane i użyć przycisku 'Połącz' aby przetestować połączenie. The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) Ustawienie 'Katalog z muzyką' jest używane to wyszukiwania okładek, tekstów itp. Jeśli używana instancja MPD jest na zdalnym komputerze, to ten adres może być ustawiony na URL HTTP. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Katalog z muzyką Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Proszę wybrać katalog zawierający kolekcję z muzyką. Covers and Lyrics i18n: file: gui/initialsettingswizard.ui:620 i18n: ectx: property (text), widget (QLabel, label_6f) Okładki i teksty <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata pobierze brakujące okładki i teksty z internetu.</p><p>Dla każdego z nich należy wybrać, czy Cantata ma przechowywać pobrane pliki w katalogu z muzyką, czy w osobistym katalogu ustawień i pamięci podręcznej.</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) Ustawienie 'Katalog z muzyką' wskazuje na adres HTTP, a program Cantata obecnie nie obsługuje wysyłania plików na zewnętrzne serwery HTTP. Dlatego powyższe ustawienie powinno pozostać odznaczone. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Zakończono! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata została skonfigurowana!<br/><br/>Okno konfiguracji Cantaty może być użyte do dostosowania wyglądu programu, jak również w celu dodania hostów MPD itp. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) Cantata grupuje utwory w albumy przy użyciu tagu 'ArtystaAlbumu', jeśli jest on ustawiony. W przeciwnym wypadku wykorzystywany jest tag 'Artysta'. Jeśli w kolekcji znajdują się albumy posiadające wiele artystów, to tag 'ArtystaAlbumu' <b>musi</b> w nich być ustawiony aby grupowanie działało poprawnie. W takim przypadku sugeruje się użycie 'Various Artists'. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>UWAGA:</b> Nie jesteś obecnie użytkownikiem należącym do grupy "users". Cantata będzie działać znacznie lepiej (zapisywanie okładek albumów, tekstów, etc. z odpowiednimi uprawnieniami), jeśli zostaniesz dodany do tej grupy. Po dodaniu się do grupy należy się wylogować i zalogować ponownie, aby te ustawienia zadziałały. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Panel boczny Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Widok Use the checkboxes below to configure which views will appear in the sidebar. i18n: file: gui/interfacesettings.ui:48 i18n: ectx: property (text), widget (QLabel, label_2) Aby skonfigurować widoki dostępne na pasku bocznym należy użyć poniższych pól wyboru. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. i18n: file: gui/interfacesettings.ui:61 i18n: ectx: property (text), widget (NoteLabel, sbPlayQueueLabel) Jeżeli 'Lista odtwarzania' nie została zaznaczona powyżej, to będzie ona wyświetlana z boku wszystkich widoków. Jeżeli 'Informacja' nie została zaznaczona, to do paska narzędzi dodany zostanie przycisk pozwalający na dostęp do informacji o utworze. Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Styl: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Pozycja: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Pokazuj tylko ikony, bez tekstu Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Automatycznie ukryj Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Zwiń albumy przy uruchomieniu Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Automatycznie rozwijaj obecny album Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Przewiń do obecnego utworu Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) Pytaj przed czyszczeniem Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) Osobna akcja (i skrót) dla wyszukiwania w kolejce Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Okładka obecnego albumu Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) Pasek narzędzi Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) Pokazuj przycisk stop Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) Pokazuj okładkę obecnego utworu Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) Pokazuj ocenę utworu External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Zewnętrzne Enable MPRIS D-BUS interface i18n: file: gui/interfacesettings.ui:404 i18n: ectx: property (text), widget (QCheckBox, enableMpris) Włącz interfejs MPRIS D-BUS Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Pokazuj wyskakujące okienka przy zmianie utworu Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Pokarz ikonę w tacce systemowej Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Minimalizuj do tacki systemowej przy zamykaniu On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) Przy uruchomieniu Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Pokaż główne okno Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) Ukryj główne okno Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Przywróć poprzedni stan Tweaks i18n: file: gui/interfacesettings.ui:482 i18n: ectx: attribute (title), widget (QWidget, tab_4z) Usprawnienia Artist && Album Sorting i18n: file: gui/interfacesettings.ui:488 i18n: ectx: property (title), widget (QGroupBox, groupBox_2p) Sortowanie Artysty i Albumu Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' i18n: file: gui/interfacesettings.ui:494 i18n: ectx: property (text), widget (QLabel, labelp) Poniżej można wprowadzić listę (oddzielaną przecinkami) predrostków, które mają być ignorowane podczas sortowania artystów i albumów, np. jeśli do listy wpisane zostanie słowo 'The', to artysta 'The Beatles' podczas sortowania będzie traktowany jako 'Beatles' Enter comma separated list of prefixes... i18n: file: gui/interfacesettings.ui:504 i18n: ectx: property (placeholderText), widget (LineEdit, ignorePrefixes) Proszę wprowadzić oddzielaną przecinkami listę przedrostków... Composer Support i18n: file: gui/interfacesettings.ui:514 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Obsługa tagu 'Kompozytor' By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. i18n: file: gui/interfacesettings.ui:520 i18n: ectx: property (text), widget (QLabel, label) Domyślnie, Cantata używa tagu 'Artysta Albumu' (albo 'Artysta' jeśli utwór nie posiada tagu 'Artysta Albumu') do grupowania utworów i albumów. Dla niektórych gatunków, np. 'Muzyka klasyczna', użycie tagu 'Kompozytor' (jeśli został ustawiony) może być preferowane. Poniżej można wprowadzić listę (oddzielaną przecinkami) gatunków, dla których Cantata ma używać tagu 'Kompozytor'. Enter comma separated list of genres... i18n: file: gui/interfacesettings.ui:530 i18n: ectx: property (placeholderText), widget (LineEdit, composerGenres) Proszę wprowadzić oddzielaną przecinkami listę gatunków... If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' i18n: file: gui/interfacesettings.ui:546 i18n: ectx: property (text), widget (QLabel, label_3) Jeżeli w kolekcji znajduje się duża liczba artystów z pojedynczym utworem, to może być uciążliwe, aby każdy posiadał własny wpis w bibliotece w liście artystów. Jako obejście tego problemu, jeżeli wszystkie te utwory zostaną umieszczone w osobnym katalogu, którego adres zostanie podany poniżej, to program Cantata pogrupuje te utwory w albumie o nazwie 'Pojedyncze utwory' jako artysta 'Various Artists' Folder that contains single track files... i18n: file: gui/interfacesettings.ui:556 i18n: ectx: property (placeholderText), widget (LineEdit, singleTracksFolders) Katalog zawierający pojedyncze utwory... CUE Files i18n: file: gui/interfacesettings.ui:566 i18n: ectx: property (title), widget (QGroupBox, groupBox_3xx) Pliki CUE A cue file is a metadata file which describes how the tracks of a CD are laid out. i18n: file: gui/interfacesettings.ui:572 i18n: ectx: property (text), widget (QLabel, label_3x) Plic cue zawiera metadane opisujące układ utworów na płycie CD. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. i18n: file: gui/interfacesettings.ui:588 i18n: ectx: property (text), widget (NoteLabel, tweaksLabel) Zmiana którejkolwiek z powyższych opcji wymaga odświeżenia bazy danych (oraz prawdopodobnie ponownego uruchomienia programu Cantata) aby zmiany zostały wprowadzone. General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Ogólne Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) Pobierz brakujące okładki z Last.fm Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Pokazuj akcję usuwania w menu kontekstowym Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Wymuś aktywację elementów przy pojedynczym kliknięciu <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>To ustawienie wpłynie na interfejs w następujący sposób: <ul><li>Przyciski odtwarzania i kontroli będą 33% szersze</li><li>Widoki będą przesuwane przez przeciągnięcie</li><li>Aby przeciągnąć elementy trzeba kliknąć/dotknąć lewy górny róg</li><li>Paski przewijania będą szerokie tylko na kilka pikseli</li><li>Akcje (np. "Dodaj do kolejki odtwarzania) będą zawsze widoczne (nie tylko po najechaniu na nie kursorem)</li><li>Wejścia numeryczne będą miały przyciski + i − obok pola tekstowego</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) Interfejs dotykowy Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) Pokazuj dymki z informacją utworu Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) Obsługa wyświetlaczy retina Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Język: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) Zmiana ustawienia 'Wymuś aktywację elementów przy pojedynczym kliknięciu' wymaga restartu Cantaty. Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) Zmiana ustawienia języka wymaga restartu Cantaty. Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) Zmiana ustawienia 'Interfejs dotykowy' wymaga restartu Cantaty. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) Włączenie obsługi wyświetlaczy retina spowoduje użycie bardziej wyrazistych ikon na wyświetlaczach retina, ale może doprowadzić do mniej wyrazistego wyświetlania ikon na wyświetlaczach nie-retina. Zmiana tego ustawienia wymaga restartu Cantaty. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Dynamiczne] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Wyjdź z pełnego ekranu Fa&deout on stop: i18n: file: gui/playbacksettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, label_6b) Wycisz przy zatrzymaniu: Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Zatrzymaj odtwarzanie przy wyjściu Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) Powstrzymaj przejście w stan wstrzymania podczas odtwarzania If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Jeśli przycisk stop zostanie wciśnięty i przytrzymany, to wyświetlone zostanie menu pozwalające na wybór, czy odtwarzanie ma zostać zatrzymane teraz, czy po obecnym utworze. (Wyświetlanie przycisku stop możliwe jest przez użycie odpowiedniej opcji w sekcji Interfejs/Pasek narzędzi) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Wyjście &Crossfade between tracks: i18n: file: gui/playbacksettings.ui:112 i18n: ectx: property (text), widget (BuddyLabel, crossfadingLabel) &Przenikanie między utworami: s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s Replay &gain: i18n: file: gui/playbacksettings.ui:135 i18n: ectx: property (text), widget (BuddyLabel, replayGainLabel) Replay&Gain: About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) O ReplayGain Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Użyj poniższych pól wyboru aby kontrolować aktywne wyjścia. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Kolekcja: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Nazwa pliku okładki: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Nazwa pliku (bez rozszerzenia) do zapisu okładek.<br/>Przy pozostawieniu tego pola pustego, nazwa 'cover' zostanie użyta.<br/><br/><i>%artist% zostanie zastąpiony przez nazwę artysty albumu obecnego utworu, natomiast %album% zostanie zastąpiony przez nazwę albumu.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) URL strumienia HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. i18n: file: gui/serversettings.ui:171 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) Ustawienie 'Katalog z muzyką' jest używane to wyszukiwania okładek. Jeśli używana instancja MPD jest na zdalnym komputerze, a okładki dostępne są przez HTTP, to ten adres może być ustawiony na URL HTTP. Jeżeli nie jest używany URL HTTP i użytkownik ma prawa zapisu do podanego katalogu (oraz jego podkatalogów), to program Cantata będzie zapisywał wszystkie pobrane okładki do odpowiednich katalogów albumów. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) i18n: file: gui/serversettings.ui:178 i18n: ectx: property (text), widget (NoteLabel, coverNameNoteLabel) i18n: file: gui/serversettings.ui:265 i18n: ectx: property (text), widget (NoteLabel, basicCoverNameNoteLabel) Jeżeli nie podano ustawienia 'Nazwa pliku okładki', to Cantata użyje domyślnej wartości <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) 'URL strumienia HTTP' używany jest jedynie jeśli MPD skonfigurowany jest z wyjściem do strumienia HTTP a Cantata ma odtwarzać ten strumień. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) Jeśli ustawienie 'Katalog z muzyką' zostanie zmienione, należy ręcznie uaktualnić bazę danych muzyki. Można to zrobić używając akcji 'Odśwież bazę danych' w widokach 'Artyści' lub 'Albumy'. Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Tryb: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP Proxy SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS Proxy Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Użyj poniższych pól wyboru aby skonfigurować listę aktywnych serwisów. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Konfiguruj serwis Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Scrobbluj przy użyciu: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Stan: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Login Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Scrobbluj utwory Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Pokazuj przycisk "polub" You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Można słuchać muzyki za darmo bez posiadania konta, jednak użytkownicy posiadający konta premium mogą słuchać muzyki o lepszej jakości bez reklam. W celu założenia konta premium należy odwiedzić stronę <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Konto premium Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Typ strumienia: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Wygaśnięcie sesji: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Te ustawienia odnoszą się do Digitally Imported, JazzRadio.com, RockRadio.com oraz Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) Po wpisaniu szczegółów konta, element statusu 'DI' pojawi się poniżej listy strumieni. Pokazuje on, czy użytkownik jest zalogowany. Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Użyj poniższych pól wyboru aby skonfigurować listę aktywnych dostawców. Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) Wbudowane kategorie wyświetlane są kursywą i nie mogą zostać usunięte. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Szukaj: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Skrót dla zaznaczonej akcji Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) Domyślny: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Własny: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Artysta albumu: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Numer utworu: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Numer płyty: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) Ocena: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(Various)</i> Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') i18n: file: tags/tageditor.ui:210 i18n: ectx: property (text), widget (PlainNoteLabel, label_7x) Podanie kilku gatunków powinno być oddzielane przecinkiem (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) Oceny przechowywane są w zewnętrznej bazie danych i <b>nie</b> w pliku utworu. Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Obecna nazwa New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Nowa nazwa Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) Oceny zostaną utracone jeśli nazwa pliku zostanie zmieniona. Your names NAME OF TRANSLATORS Piotr Wicijowski Your emails EMAIL OF TRANSLATORS piotr[dot]wicijowski[at]gmail[dot]com Show All Tracks Pokaż wszystkie utwory Show Untagged Tracks Pokaż utwory bez tagów Remove From List Usuń z listy Album Gain Gain albumu Track Gain Gain utworu Album Peak Peak albumu Track Peak Peak utworu Scan Skanuj Update ReplayGain tags in tracks? Uaktualnić tagi ReplayGain w utworach? Update Tags Uaktualnij tagi Abort scanning of tracks? Przerwać skanowanie plików? Abort Przerwij Abort reading of existing tags? Przerwać czytanie istniejących tagów? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Skanować <b>wszystkie</b> utwory?<br/><br/><i>Wszystkie utwory mają już istniejące tagi ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Czy przeskanować wszystkie utwory, czy tylko te bez istniejących tagów? Untagged Tracks Utwory bez tagów All Tracks Wszystkie utwory Scanning tracks... Skanowanie utworów... Reading existing tags... Czytanie istniejących tagów... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Uszkodzone tagi?) Failed to update the tags of the following tracks: Uaktualnianie tagów następujących utworów nie powiodło się: Device is not connected. Urządzenie nie jest podłączone. %1 dB %1 dB Failed Nie powiodło się Original: %1 dB Oryginał: %1 dB Original: %1 Oryginał: %1 Remove the selected tracks from the list? Usunąć zaznaczone utwory z listy? Remove Tracks Usuń utwory Invalid service Nieprawidłowy serwis Invalid method Nieprawidłowa metoda Authentication failed Uwierzytelnianie nie powiodło się Invalid format Nieprawidłowy format Invalid parameters Nieprawidłowe parametry Invalid resource specified Podano nieprawidłowy zasób Operation failed Operacja nie powiodła się Invalid session key Nieprawidłowy klucz sesji Invalid API key Nieprawidłowy klucz API Service offline Serwis jest offline Last.fm is currently busy, please try again in a few minutes Last.fm jest obecnie zajęty, proszę spróbować za kilka minut Rate-limit exceeded Przekroczono limit prób %1 error: %2 %1 błąd: %2 %1: Loved Current Track %1: Polubiono obecny utwór %1: Love Current Track %1: Polub obecny utwór %1 (via MPD) scrobbler name (via MPD) %1 (przez MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Jeśli użyty jest scrobbler oznaczony jako '(przy użyciu MPD)' (na przykład %1), to musi on być już uruchomiony i działający. Cantata może jedynie 'Polubić' utwory przy jego pomocy, ale nie może wyłączyć lub włączyć scrobblowania. Authenticating... Uwierzytelnianie... Authenticated Uwierzytelniono Not Authenticated Nie uwierzytelniono %1: Scrobble Tracks %1: Scrobbluj utwory Digitally Imported Settings Ustawienia Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Logout Wyloguj URL: URL: Add Stream Dodaj strumień Edit Stream Edytuj strumień <i><b>ERROR:</b> Invalid protocol</i> <i><b>BŁAD:</b> Niewłaściwy protokół</i> Loading %1 Ładowanie %1 Installed Zainstalowano Update available Aktualizacja dostępna Check the providers you wish to install/update. Proszę sprawdzić dostawców, którzy mają zostać zainstalowani/zaktualizowani. Install/Update Stream Providers Instaluj/aktualizuj dostawców strumieni. Downloading list... Pobieranie listy... Digitally Imported Digitally Imported Local and National Radio (ListenLive) Lokalne i krajowe radio (ListenLive) Failed to download list of stream providers! Pobieranie listy dostawców strumieni nie powiodło się! Installing/updating %1 Instalowanie/aktualizowanie %1 Failed to install '%1' Instalacja '%1' nie powiodła się Failed to download '%1' Pobieranie '%1' nie powiodło się Install/update the selected stream providers? Zainstalować/zaktualizować zaznaczonych dostawców strumieni? Install the selected stream providers? Zainstalować zaznaczonych dostawców strumieni? Update the selected stream providers? Zaktualizować zaznaczonych dostawców strumieni? Install/Update Instaluj/aktualizuj Abort installation/update? Przerwać instalację/aktualizację? Downloading %1 Pobieranie %1 Update all updateable providers Zaktualizuj wszystkich dostawców Import Streams Into Favorites Importuj strumienie do ulubionych Export Favorite Streams Eksportuj ulubione strumienie Add New Stream To Favorites Dodaj nowy strumień do ulubionych Seatch For Streams Szukaj strumieni Digitally Imported Service name Digitally Imported Import Streams Importuj strumienie XML Streams (*.xml *.xml.gz *.cantata) Strumienie XML (*.xml *.xml.gz *.cantata) Export Streams Eksportuj strumienie XML Streams (*.xml.gz) Strumienie XML (*.xml.gz) Failed to create '%1'! Tworzenie '%1' nie powiodło się! Stream '%1' already exists! Strumień '%1' już istnieje! A stream named '%1' already exists! Strumień o nazwie '%1' już istnieje! Bookmark added Dodano zakładkę Already bookmarked Już w zakładkach Already in favorites Już w ulubionych Reload '%1' streams? Wczytać ponownie '%1' strumieni? Are you sure you wish to remove bookmark to '%1'? Czy na pewno usunąć zakładkę do '%1'? Are you sure you wish to remove all '%1' bookmarks? Czy na pewno usunąć '%1' zakładek? Are you sure you wish to remove the %1 selected streams? czy tutaj również nie powinna być liczba mnoga? 1 zaznaczony strumień 2-4 zaznaczone strumienie 5+ zaznaczonych strumieni (jak teraz) Czy jesteś pewien, że chcesz usunąć %1 zaznaczonych strumieni? Are you sure you wish to remove '%1'? Czy na pewno usunąć '%1'? Added '%1'' to favorites Dodano "%1" do ulubionych Configure Streams Konfiguruj strumienie From File... Z pliku... Download... Pobierz... Configure Provider Konfiguruj dostawcę Install Instaluj Install Streams Instaluj strumienie Cantata Streams (*.streams) Strumienie Cantata (*.streams) A category named '%1' already exists! Overwrite? Kategoria o nazwie '%1' już istnieje! Nadpisać? Failed top open package file. Otwieranie pliku pakietu nie powiodło się. Invalid file format! Niepoprawny format pliku! Failed to create stream category folder! Tworzenie katalogu kategorii strumieni nie powiodło się! Failed to save stream list! Zapisanie listy strumieni nie powiodło się! Failed to remove streams folder! Usunięcie katalogu strumieni nie powiodło się! &OK &Ok &Cancel &Anuluj &Yes &Tak &No &Nie &Discard &Odrzuć &Save Zapi&sz &Apply &Zastosuj &Close Zam&knij &Overwrite Nad&pisz &Reset &Resetuj &Continue &Kontynuuj &Delete &Skasuj &Stop &Stop &Remove &Usuń &Previous &Poprzedni &Next &Następny Configure... Konfiguruj... Password Hasło Please enter password: Proszę podać hasło: Close Zamknij Warning Ostrzeżenie Question Pytanie &Window &Okno Minimize Minimalizuj Zoom Przybliż Select Folder Wybierz katalog Select File Wybierz plik %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags Tagi Set 'Album Artist' from 'Artist' Ustaw tag 'Artysta Albumu' na podstawie tagu 'Artysta' Read Ratings from File Wczytaj oceny z pliku Write Ratings to File Zapisz oceny do pliku All tracks Wszystkie utwory Apply "Various Artists" workaround to <b>all</b> tracks? Czy zastosować obejście dla 'Various Artists' we <b>wszystkich</b> utworach? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Spowoduje to ustawienie tagów 'ArtystaAlbumu' oraz 'Artysta' na wartość "Various Artists", zaś tagu 'Tytuł' na "ArtystaUtworu - TytułUtworu"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Czy cofnąć obejście dla 'Various Artists' we <b>wszystkich</b> utworach? Revert "Various Artists" workaround Cofnij obejście dla 'Various Artists' <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>W przypadku, gdy tag 'ArtystaAlbumu' jest taki sam jak 'Artysta' oraz tag 'Tytuł' jest formatu "ArtystaUtworu - TytułUtworu", to tag 'Artysta' zostanie pobrany z tagu 'Tytuł' a sam 'Tytuł' zostanie przywrócony do wartości "TytułUtworu". Dla przykładu: <br/><br/> Jeśli 'Tytuł' to "Wibble - Wobble", wtedy 'Artysta' zostanie ustawiony na "Wibble" natomiast 'Title' będzie miał wartość "Wobble"</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Czy ustawić tag 'Artystę Albumu' na podstawie tagu 'Artysta' (jeśli 'Artysta Albumu' jest pusty) dla <b>wszystkich</b> utworów? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Czy ustawić tag 'Artystę Albumu' na podstawie tagu 'Artysta' (jeśli 'Artysta Albumu' jest pusty)? Album Artist from Artist Artysta Albumu na podstawie Artysty Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Ustawić dużą pierwszą literę pól tekstowych (np. 'Tytuł', 'Artysta', itp.) <b>wszystkich</b> utworów? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Ustawić dużą pierwszą literę pól tekstowych (np. 'Tytuł', 'Artysta', itp.)? Adjust the value of each track number by: Zwiększ wartość numeru każdego utworu o: Read ratings for all tracks from the music files? Odczytać oceny wszystkich utworów z plików muzycznych? Read rating from music file? Odczytać ocenę z pliku muzycznego? Ratings Oceny Read Ratings Odczytaj oceny Read Rating Odczytaj ocenę Read, and updated, ratings from the following tracks: Odczytaj i uaktualnij oceny z następujących utworów: Not all Song ratings have been read from MPD! Nie wszystkie utwory zostały odczytane z MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Oceny utworów nie są przechowywane w plikach utworów, ale w bazie danych 'sticker' serwera MPD. W celu zapisania ocen do plików, Cantata musi odczytać je najpierw z MPD. Song rating has not been read from MPD! Oceny utworów nie zostały odczytane z MPD! Write ratings for all tracks to the music files? Zapisać oceny dla wszystkich utworów do plików muzycznych? Write rating to music file? Zapisać ocenę do pliku muzycznego? Write Ratings Zapisz oceny Write Rating Zapisz ocenę Failed to write ratings of the following tracks: Zapisywanie ocen nie powiodło się dla następujących utworów: Failed to write rating to music file! Zapisywanie oceny do pliku muzycznego nie powiodło się! All tracks [modified] Wszystkie utwory [zmodyfikowano] %1 [modified] %1 [zmodyfikowano] Would you also like to rename your song files, so as to match your tags? Czy również zmienić nazwy plików tak, aby pasowały do tagów? Rename Files Zmień nazwy plików Abort renaming of files? Przerwać zmianę nazw plików? Source file does not exist! Plik źródłowy nie istnieje! Destination file already exists! Plik docelowy już istnieje! Failed to create destination folder! Tworzenie katalogu docelowego nie powiodło się! Failed to rename '%1' to '%2' Zmiana nazwy pliku z '%1' na '%2' nie powiodła się Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Oceny utworów nie są przechowywane w plikach utworów, ale w bazie danych 'sticker' serwera MPD. Jeśli nazwa pliku zostanie zmieniona (albo nazwa folderu, który zawiera ten plik), to ocena związana z utworem zostanie utracona. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Kompozytor:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Wykonawca:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artysta:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Rok:</b></td><td>%3</td></tr> Filter On Genre Filtruj według gatunku All Genres Wszystkie gatunki Go Back Idź wstecz Menu Menu (Stream) (Strumień) Search... Szukaj... Close Search Bar Zamknij pasek wyszukiwania Logged into %1 Zalogowano do %1 <b>NOT</b> logged into %1 <b>NIE</b> zalogowano do %1 Basic Tree (No Icons) Drzewo podstawowe (bez ikon) Simple Tree Proste drzewo Detailed Tree Szczegółowe drzewo List Lista Grid Siatka View Widok Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Dostęp do plików utworów nie powiódł się! Proszę sprawdzić poprawność ustawień "Katalog z muzyką" w programie Cantata oraz "music_directory" w MPD. Cannot access song files! Please check that the device is still attached. Dostęp do plików utworów nie powiódł się! Proszę sprawdzić czy urządzenie nadal jest podłączone. Stretch Columns To Fit Window Rozciągnij kolumny aby dopasować do okna Center Środek Alignment Wyrównanie (Various) (różne) Click to go back Kliknij aby pójść wstecz Add All To Play Queue Dodaj wszystko do kolejki odtwarzania Add All And Replace Play Queue Dodaj wszystko i zamień kolejkę odtwarzania Mute Wycisz Unmute Wyłącz wyciszenie Volume %1% (Muted) Głośność %1% (wyciszono) Volume %1% Głośność %1% 1 Track Singular Utwory: 1 %1 Tracks Plural (N!=1) Utwory: %1 1 Track (%1) Singular Utwory: 1 (%1) %1 Tracks (%2) Plural (N!=1) Utwory: %1 (%2) 1 Album Singular Albumy: 1 %1 Albums Plural (N!=1) Albumy: %1 1 Artist Singular Artyści: 1 %1 Artists Plural (N!=1) Artyści: %1 1 Stream Singular Strumienie: 1 %1 Streams Plural (N!=1) Strumienie: %1 1 Entry Singular Elementy: 1 %1 Entries Plural (N!=1) Elementy: %1 1 Rule Singular Reguły: 1 %1 Rules Plural (N!=1) Reguły: %1 1 Podcast Singular 1 Podcast %1 Podcasts Plural (N!=1) Podcasty: %1 1 Episode Singular 1 Odcinek %1 Episodes Plural (N!=1) Odcinki: %1 1 Update available Singular 1 aktualizacja dostępna %1 Updates available Plural (N!=1) Dostępne aktualizacje: %1 ActionDialog Calculating size of files to be copied, please wait... Obliczanie rozmiaru plików do skopiowania, proszę czekać... Copy songs from: Kopiuj utwory z: Configure Konfiguruj (Needs configuring) (Wymaga konfiguracji) Copy songs to: Kopiuj utwory do: Destination format: Format docelowy: Overwrite songs Nadpisz utwory To copy: Do skopiowania: <b>INVALID</b> <b>NIEWŁAŚCIWY</b> <i>(When different)</i> <i>(Gdy różne)</i> Artists:%1, Albums:%2, Songs:%3 Artyści:%1, Albumy:%2, Utwory:%3 %1 free %1 wolnego miejsca Local Music Library Lokalna biblioteka muzyki Audio CD Płyta audio There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. Nie ma wystarczająco miejsca na docelowym urządzeniu. Zaznaczone utwory zajmują %1, jednak wolnego miejsca zostało jedynie %2. Utwory musiałyby zostać transkodowane do mniejszego rozmiary aby je skopiować z powodzeniem. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Nie ma wystarczająco miejsca w lokalizacji docelowej. Zaznaczone utwory zajmują %1, jednak wolnego miejsca zostało jedynie %2. Copy Songs To Library Kopiuj utwory do biblioteki Copy Songs To Device Skopiuj utwory do urządzenia Copy Songs Skopiuj utwory Delete Songs Usuń utwory You have not configured the destination device. Continue with the default settings? Urządzenie docelowe nie zostało skonfigurowane. Kontynuować z ustawieniami domyślnymi? Not Configured Nie skonfigurowano Use Defaults Użyj domyślnych You have not configured the source device. Continue with the default settings? Urządzenie źródłowe nie zostało skonfigurowane. Kontynuować z ustawieniami domyślnymi? Are you sure you wish to stop? Czy na pewno zatrzymać? Stop Zatrzymaj Device has been removed! Urządzenie zostało usunięte! Device is not connected! Urządzenie nie jest podłączone! Device is busy? Urządzenie jest zajęte? Device has been changed? Urządzenie zostało zmienione? Clearing unused folders Czyszczenie nieużywanych katalogów Calculate ReplayGain for ripped tracks? Obliczyć ReplayGain zgranych utworów? ReplayGain ReplayGain Calculate Oblicz The destination filename already exists! Istnieje plik o nazwie docelowej! Song already exists! Utwór już istnieje! Song does not exist! Utwór nie istnieje! Failed to create destination folder!<br/>Please check you have sufficient permissions. Tworzenie katalogu docelowego nie powiodło się!<br/>Proszę upewnić się, że ustawione są odpowiednie prawa dostępu. Source file no longer exists? Plik źródłowy już nie istnieje? Failed to copy. Kopiowanie nie powiodło się. Failed to delete. Usuwanie nie powiodło się. Not connected to device. Brak połączenia z urządzeniem. Selected codec is not available. Wybrany kodek nie jest dostępny. Transcoding failed. Konwertowanie nie powiodło się. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Stworzenie pliku tymczasowego nie powiodło się.<br/>(Wymagane przy konwertowaniu do urządzeń MTP.) Failed to read source file. Odczyt pliku źródłowego nie powiódł się. Failed to write to destination file. Zapis do pliku docelowego nie powiódł się. No space left on device. Brak miejsca na urządzeniu. Failed to update metadata. Uaktualnienie metadanych nie powiodło się. Failed to download track. Pobieranie utworu nie powiodło się. Failed to lock device. Zablokowanie urządzenia nie powiodło się. Local Music Library Properties Ustawienia lokalnej biblioteki muzyki Error Błąd Skip Pomiń Auto Skip Automatycznie pomiń Retry Spróbuj ponownie Artist: Artysta: Album: Album: Track: Utwór: Source file: Plik źródłowy: Destination file: Plik docelowy: File: Plik: Saving cache Zapisywanie pamięci podręcznej Calculating... Obliczanie... Time remaining: Pozostało: AlbumDetails Album Details Szczegóły albumu Artist: Artysta: Composer: Kompozytor: Title: Tytuł: Genre: Gatunek: Year: Rok: Disc: Płyta: Single artist Pojedynczy artysta Tracks Utwory Track Utwór Artist Artysta Title Tytuł AlbumDetailsDialog Audio CD Płyta audio Apply "Various Artists" Workaround Zastosuj obejście dla 'Various Artists' Revert "Various Artists" Workaround Przywróć obejście dla 'Various Artists' Capitalize Duża pierwsza litera Adjust Track Numbers Dostosuj numery utworów Tools Narzędzia Apply "Various Artists" workaround? Zastosować obejście dla 'Various Artists'? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" To ustawi tagi 'Artysta albumu' oraz 'Artysta' na "Various Artists" oraz tag 'Tytuł' na "Artysta utworu - Tytuł utworu" Revert "Various Artists" workaround? Przywrócić wartości tagów sprzed obejścia "Various Artists"? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Jeżeli 'Artysta albumu' jest taki sam jak 'Artysta' oraz 'Tytuł' jest formatu "Artysta utworu - Tytuł utworu", to wartość tagu 'Artysta' zostanie wydzielona z tagu 'Tytuł', natomiast nowa wartość tagu 'Tytuł' ustawiona będzie na 'Tytuł utworu', np. Jeśli 'Tytuł' to "Wibble - Wobble", to 'Artysta' zostanie ustawiony na "Wibble" a 'Tytuł' na "Wobble" Revert Przywróć Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Ustawić dużą pierwszą literę tagów 'Tytuł', 'Artysta', 'Artysta albumu' oraz 'Album'? Adjust track number by: Zmień numery utworów o: AlbumView Refresh Album Information Odśwież informacje o albumie Album Album Tracks Utwory ArtistView Refresh Artist Information Odśwież informacje o artyście Artist Artysta Albums Albumy Web Links Linki sieciowe Similar Artists Podobni Artyści AudioCdDevice Reading disc Odczyt płyty %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) AudioCdSettings Album and Track Information Retrieval Pobieranie informacji albumu i utworów Initially look up via: Najpierw wyszukaj poprzez: CDDB Host: Host CDDB: CDDB Port: Port CDDB: Lookup information as soon as CD is inserted Sprawdź informacje jak tylko płyta CD zostanie włożona Audio Extraction Zgrywanie audio Full paranoia mode (best quality) Tryb full paranoia mode (najlepsza jakość) Never skip on read error Nigdy nie pomijaj przy błędach odczytu CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Plik .cue Playlist Playlista CacheItem Deleting... Usuwanie... Calculating... Obliczanie... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Cantata przechowuje w pamięci podręcznej różne informacje (okładki, teksty itp.). Poniżej przedstawiono podsumowanie użycia pamięci podręcznej programu Cantata. Covers Okładki Scaled Covers Przeskalowane okładki Backdrops Tła Lyrics Teksty Artist Information Informacje o artyście Album Information Informacje o albumie Track Information Informacje o utworze Stream Listings Listy strumieni Podcast Directories Katalogi podcastów Wikipedia Languages Języki Wikipedii Scrobble Tracks Scrobbluj utwory Delete All Usuń wszystko Delete all '%1' items? Czy usunąć wszystkie '%1' elementów? Delete Cache Items Usuń elementy pamięci podręcznej Delete items from all selected categories? Usunąć elementy ze wszystkich zaznaczonych kategorii? CacheTree Name Nazwa Item Count Liczba elementów Space Used Użyta przestrzeń CddbInterface Data Track Ścieżka danych Failed to open CD device Otwarcie urządzenia CD nie powiodło się Track %1 Utwór %1 Failed to create CDDB connection Nawiązanie połączenia z CDDB nie powiodło się Failed to contact CDDB server, please check CDDB and network settings Nawiązanie połączenia z serwerem CDDB nie powiodło się, proszę sprawdzić ustawienia CDDB i sieci No matches found in CDDB Nie znaleziono psujących wyników w CDDB CDDB error: %1 Błąd CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Znaleziono wiele pasujących wyników. Proszę wybrać jeden z poniższych: Artist Artysta Title Tytuł Disc Selection Wybór płyty %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 płyta %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Dostawcy tekstów Wikipedia Languages Języki Wikipedii Other Inne ContextWidget &Artist &Artysta Al&bum Al&bum &Track U&twór CoverDialog Search Szukaj Add a local file Dodaj plik lokalny Configure Konfiguruj This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. Opcja ta może być użyta tylko do zmiany pliku używanego jako okładka, nie zmieni to okładek osadzonych w plikach utworów. CoverArt Archive Archiwum okładek An image already exists for this artist, and the file is not writeable. Istnieje już obraz dla danego artysty, a plik nie może być nadpisany. A cover already exists for this album, and the file is not writeable. Istnieje już okładka dla danego albumu, a plik nie może być nadpisany. '%1' Artist Image '%1' Obraz artysty '%1 - %2' Album Cover 'Artist - Album' Album Cover '%1 - %2' Okładka albumu Failed to set cover! Could not download to temporary file! Ustawienie okładki nie powiodło się! Nie można pobrać do tymczasowego pliku! Failed to download image! Pobieranie obrazu nie powiodło się! Load Local Cover Załaduj okładkę lokalną Images (*.png *.jpg) Obrazy (*.png *.jpg) File is already in list! Plik istnieje już na liście! Failed to read image! Odczyt obrazu nie powiódł się! Display Wyświetl Remove Usuń Failed to set cover! Could not make copy! Ustawienie okładki nie powiodło się! Nie można stworzyć kopii! Failed to set cover! Could not backup original! Ustawienie okładki nie powiodło się! Nie można stworzyć kopii zapasowej oryginału! Failed to set cover! Could not copy file to '%1'! Ustawienie okładki nie powiodło się! Nie można skopiować pliku do '%1'! Searching... Szukanie... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Kompozytor:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Wykonawca:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Artysta:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Rok:</b></td><td>%3</td></tr> CoverPreview Image Obraz Downloading... Pobieranie... Image (%1 x %2 %3%) Image (width x height zoom%) Obraz (%1 x %2 %3%) CustomActionDialog Name: Nazwa: Command: Polecenie: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. W linii komend powyżej, %f zostanie zastąpione przez listę plików a %d przez listę katalogów. Jeżeli żadne z nich nie zostaną podane, lista plików zostanie dodana na koniec komendy. Add New Command Dodaj nowe polecenie Edit Command Edytuj polecenie CustomActions Custom Actions Własne akcje CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Aby program Cantata wykonywał zewnętrzne polecenia (np. w celu edycji tagów poprzez inną aplikację), należy poniżej dodać wpis dla tego polecenia. Jeżeli chociaż jedno polecenie jest zdefiniowane, do menu kontekstowego dla Biblioteki, Katalogów i Playlist zostaje dodane menu 'Własne akcje'. Add Dodaj Edit Edytuj Remove Usuń Name Nazwa Command Polecenie Remove the selected commands? Usunąć zaznaczone polecenia? Device Updating (%1)... Uaktualnianie (%1)... Updating (%1%)... Uaktualnianie (%1%)... DevicePropertiesDialog Device Properties Ustawienia urządzenia DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Te ustawienia są poprawne i można je edytować jedynie gdy urządzenie jest podłączone. Name: Nazwa: Music folder: Katalog z muzyką: Copy album covers as: Kopiuj okładki albumów jako: Maximum cover size: Maksymalny rozmiar okładek: Default volume: Domyślny wolumin: 'Various Artists' workaround Obejście dla 'Various Artists' Automatically scan music when attached Automatycznie skanuj muzykę po podłączeniu Use cache Używaj pamięci podręcznej Filenames Nazwy plików Filename scheme: Schemat nazwy pliku: VFAT safe Bezpieczne dla VFAT Use only ASCII characters Używaj jedynie znaków ASCII Replace spaces with underscores Zastąp spacje znakami podkreślenia Append 'The' to artist names Dodaj 'The' to nazw artystów If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Jeżeli nazwa artysty rozpoczyna się od 'The', to dodaj predrostek do nazwy katalogu, np. 'The Beatles' zmieni się w 'Beatles, The' Transcoding Konwertowanie Only transcode if source file is of a different format Konwertuj tylko gdy plik wejściowy jest w innym formacie Only transcode if source is FLAC/WAV Transkoduj tylko jeżeli źródło jest w formacie FLAC/WAV Don't copy covers Nie kopiuj okładek Embed cover within each file Osadź okładkę w każdym pliku No maximum size Brak rozmiaru maksymalnego 400 pixels 400 pixeli 300 pixels 300 pixeli 200 pixels 200 pixeli 100 pixels 100 pixeli <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>W trakcie kopiowania utworów do urządzenia, gdy tag 'ArtystaAlbumu' jest ustawiony na 'Various Artists', Cantata ustawi tag 'Artysta' we wszystkich utworach na 'Various Artists', zaś tag 'Tytuł' na 'ArtystaUtworu - TytułUtworu'.<hr/> W trakcie kopiowania z urządzenia, Cantata sprawdzi, czy oba tagi 'ArtystaAlbumu' oraz 'Artysta' są ustawione na 'Various Artists'. Jeśli tak, to program podejmie próbę przywrócenia rzeczywistej nazwy artysty z tagu 'Tytuł' oraz usunie nazwę artysty z tagu 'Tytuł'.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Wybranie tej opcji spowoduje, że Cantata stworzy pamięć podręczną dla biblioteki muzyki wybranego urządzenia. Pozwoli to na przyspieszenie kolejnych skanowań biblioteki (ponieważ zamiast czytać tagi z plików, używane będą informacje z pamięci podręcznej.)<hr/><b>UWAGA:</b> Jeśli używana jest również inna aplikacja w celu zarządzania biblioteką muzyki na urządzeniu, to pamięć podręczna będzie nieaktualna. W celu jej ponownego utworzenia, należy użyć ikony 'odśwież', znajdującej się na liście urządzeń. Spowoduje to usunięcie pamięci podręcznej oraz ponowne przeskanowanie zawartości urządzenia.</p> Do not transcode Nie konwertuj Encoder Enkoder Transcode to %1 Konwertuj do %1 %1 (%2 free) name (size free) %1 (%2 wolne) DevicesModel Configure Device Konfiguruj urządzenie Refresh Device Odśwież urządzenie Connect Device Połącz z urządzeniem Disconnect Device Rozłącz urządzenie Edit CD Details Edytuj szczegóły CD Not Connected Nie połączono No Devices Attached Brak podłączonych urządzeń DevicesPage Copy To Library Skopiuj do biblioteki Synchronise Synchronizuj Forget Device Zapomnij o urządzeniu Add Device Dodaj urządzenie Lookup album and track details? Czy wyszukać szczegóły albumu i utworów? Refresh Odśwież Via CDDB Za pośrednictwem CDDB Via MusicBrainz Za pośrednictwem MusicBrainz Which type of refresh do you wish to perform? Jaki typ odświeżenia ma zostać przeprowadzony? Partial - Only new songs are scanned (quick) Częściowe - tylko nowe utwory są przeskanowane (szybkie) Full - All songs are rescanned (slow) Pełne - wszystkie utwory są skanowane (wolne) Partial Częściowe Full Pełne Are you sure you wish to delete the selected songs? This cannot be undone. Czy na pewno usunąć zaznaczone utwory? Ta operacja nie może być cofnięta. Delete Songs Usuń utwory Are you sure you wish to forget '%1'? Czy na pewno zapomnieć '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Czy na pewno wysunąć płytę audio CD '%1 - %2'? Eject Wysuń Are you sure you wish to disconnect '%1'? Czy na pewno rozłączyć się z '%1'? Disconnect Rozłącz Please close other dialogs first. Proszę najpierw zamknąć inne okna dialogowe. DigitallyImported Not logged in Nie zalogowano Logged in Zalogowano Unknown error Nieznany błąd No subscriptions Brak subskrypcji You do not have an active subscription Nie ma aktywnych subskrypcji Logged in (expiry:%1) Zalogowano (wygaśnięcie: %1) Session expired Sesja wygasła DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Można słuchać muzyki za darmo bez posiadania konta, jednak użytkownicy posiadający konta premium mogą słuchać muzyki o lepszej jakości bez reklam. W celu założenia konta premium należy odwiedzić stronę <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>. Premium Account Konto premium Username: Użytkownik: Password: Hasło: Stream type: Typ strumienia: Status: Stan: Login Login Session expiry: Wygaśnięcie sesji: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Te ustawienia odnoszą się do Digitally Imported, JazzRadio.com, RockRadio.com oraz Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Po wpisaniu szczegółów konta, element statusu 'DI' pojawi się poniżej listy strumieni. Pokazuje on, czy użytkownik jest zalogowany. Digitally Imported Settings Ustawienia Digitally Imported MP3 256k MP3 256k AAC 64k AAC 64k AAC 128k AAC 128k Not Authenticated Nie uwierzytelniono Authenticating... Uwierzytelnianie... Authenticated Uwierzytelniono Logout Wyloguj DockMenu - Rating: %1..%2 - Ocena: %1..%2 %n Rule(s) 1 Reguła %n Reguły %n Reguł Play Odtwarzaj Pause Wstrzymaj DynamicPlaylists Start Dynamic Playlist Uruchom playlistę dynamiczną Stop Dynamic Mode Zatrzymaj tryb dynamiczny Dynamic Playlists Dynamiczne playlisty Dynamically generated playlists Dynamicznie generowane playlisty You need to install "perl" on your system in order for Cantata's dynamic mode to function. Należy w systemie zainstalować narzędzie "perl" aby tryb dynamiczny Cantaty mógł działać. Failed to locate rules file - %1 Odnalezienie pliku z regułami nie powiodło się - %1 Failed to remove previous rules file - %1 Usunięcie poprzedniego pliku z regułami nie powiodło się - %1 Failed to install rules file - %1 -> %2 Instalacja pliku z regułami nie powiodła się - %1 -> %2 Dynamizer has been terminated. Dynamizer został wyłączony. Awaiting response for previous command. (%1) Oczekiwanie na odpowiedź dla poprzedniej komendy. (%1) Saving rule Zapisywanie reguły Deleting rule Usuwanie reguły Failed to save %1. (%2) Zapisywanie nie powiodło się %1. (%2) Failed to delete rules file. (%1) Usunięcie pliku z regułami nie powiodło się. (%1) Failed to control dynamizer state. (%1) Kontrola stanu dynamizera nie powiodła się. (%1) Failed to set the current dynamic rules. (%1) Ustawianie dynamicznych reguł nie powiodło się. (%1) DynamicPlaylistsPage Add Dodaj Edit Edytuj Remove Usuń Remote dynamizer is not running. Zdalny dynamizer nie działa. Are you sure you wish to remove the selected rules? This cannot be undone. Czy na pewno usunąć zaznaczone reguły? Ta operacja nie może być cofnięta. Remove Dynamic Rules Usuń dynamiczne reguły FancyTabWidget Configure... Konfiguruj... FileSettings Save downloaded covers, artist, and composer images, in music folder Zapisz pobrane okładki oraz obrazy artystów i kompozytorów w katalogu muzyki Save downloaded lyrics in music folder Zapisz pobrane teksty w katalogu muzyki Save downloaded backdrops in music folder Zapisz pobrane tła w katalogu muzyki If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Jeżeli wybrano opcję, aby program Cantata przechowywał okładki, teksty oraz tła w katalogu muzyki, a użytkownik nie ma praw do zapisu w tym katalogu, to Cantata wróci do opcji zapisu tych plików w osobistym katalogu pamięci podręcznej. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Program Cantata może zapisaćtła, obrazy artysów i kompozytorów w hierarchii katalogów tylko jeśli ma ona głębokość 2 poziomów, tzn. 'Artysta/Album/Utwory'. FilenameSchemeDialog Example: Przykład: About filename schemes O schematach nazwy pliku The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Artysta albumu. Dla większości albumów będzie to taka sama wartość jak <i>artysta utworu.</i> Dla kompilacji często przyjmie wartość <i>Various Artists.</i> Album Artist Artysta albumu The name of the album. Nazwa albumu. Album Title Tytuł albumu The composer. Kompozytor. Composer Kompozytor The artist of each track. Artysta każdego utworu. Track Artist Artysta utworu The track title (without <i>Track Artist</i>). Tytuł utworu (bez <i>artysty utworu</i>). Track Title Tytuł utworu The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Tytuł utworu (z <i>artystą utworu</i>, jeśli różny od <i>artysty albumu</i>). Track Title (+Artist) Tytuł utworu (+Artysta) The track number. Numer utworu. Track # Numer utworu The album number of a multi-album album. Often compilations consist of several albums. Numer płyty w wydaniach kilkupłytowych. Kompilacje często składają się z wielu płyt. CD # Numer CD The year of the album's release. Rok wydania albumu. Year Rok The genre of the album. Gatunek albumu. Genre Gatunek Filename Scheme Schemat nazwy pliku Various Artists Example album artist Various Artists Wibble Example artist Wibble Vivaldi Example composer Vivaldi Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Dance The following variables will be replaced with their corresponding meaning for each track name. Następujące zmienne zostaną zastąpione ich znaczeniami dla każdego utworu. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Zmienna</em></th><th><em>Przycisk</em></th><th><em>Opis</em></th></tr> FolderPage Open In File Manager Otwórz w menedżerze plików Are you sure you wish to delete the selected songs? This cannot be undone. Czy na pewno usunąć zaznaczone utwory? Ta operacja nie może być cofnięta. Delete Songs Usuń utwory FsDevice Updating... Uaktualnianie.... Reading cache Odczytywanie pamięci podręcznej Saving cache Zapisywanie pamięci podręcznej %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Filtruj według gatunku All Genres Wszystkie gatunki GroupedViewDelegate Audio CD Płyta audio Streams Strumienie %n Track(s) %n Utwór %n Utwory %n Utworów InitialSettingsWizard Cantata First Run Pierwsze uruchomienie programu Cantata Welcome to Cantata Witamy w programie Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata jest przyjaznym dla użytkownika i bogatym w funkcje klientem Music Player Daemon (MPD). MPD jest elastyczną i potężną aplikacją serwerową do odtwarzania muzyki.</p><p>W celu uzyskania informacji na temat MPD prosimy o odwiedzenie strony <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Ten 'kreator konfiguracji' pomoże przy podstawowych ustawieniach potrzebnych do poprawnego działania programu Cantata.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Witaj w programie Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> <p>Cantata jest bogatym w funkcje i przyjaznym dla użytkownika klientem programu Music Player Daemon (MPD). MPD to elastyczna i funkcjonalna aplikacja serwerowa do odtwarzania muzyki. MPD może być uruchamiany zarówno dla całego systemu, lub dla poszczególnych użytkowników.<br/><br/>Proszę wybrać, jak Cantata ma początkowo połączyć się (lub uruchamiać) MPD:</p> Standard multi-user/server setup Standardowa wielo-użytkownikowa/serwerowa konfiguracja <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> <i>Proszę wybrać tę opcję, jeśli kolekcja muzyki jest współdzielona, znajduje się na innej maszynie, jest już skonfigurowa albo ma być używana z innymi klientami (np. MPDroid). Wybranie tej opcji spowoduje, że program Cantata nie będzie mógł kontrolować uruchamiania i wyłączania serwera MPD. Dlatego należy się upewnić, że MPD jest już skonfigurowany i uruchomiony.</i> Basic single user setup Podstawowa konfiguracja dla pojedynczego użytkownika <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> <i>Proszę wybrać tę opcję jeśli kolekcja muzyki nie jest współdzielona a konfiguracją i kontrolą MPD ma zająć się Cantata. Takie ustawienie będzie dostępna wyłącznie dla programu Cantata i <b>nie</b> będzie można jej używać z innymi klientami MPD (np. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Jeżeli mają być używane zaawansowane ustawienia MPD (np. wiele wyjść audio, pełne wsparcie DSD, itp.) wtedy <b>trzeba</b> wybrać opcję 'Standardowy' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Aby znaleźć więcej informacji należy odwiedzić stronę MPD <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>Ten 'kreator konfiguracji' pomoże przy podstawowych ustawieniach potrzebnych do poprawnego działania programu Cantata. Connection details Szczegóły połączenia The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Poniższe ustawienia są podstawowymi ustawieniami wymaganymi przez program Cantata. Proszę wpisać odpowiednie dane i użyć przycisku 'Połącz' aby przetestować połączenie. Host: Host: Password: Hasło: Music folder: Katalog z muzyką: Connect Połącz The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Ustawienie 'Katalog z muzyką' jest używane to wyszukiwania okładek, tekstów itp. Jeśli używana instancja MPD jest na zdalnym komputerze, to ten adres może być ustawiony na URL HTTP. Music folder Katalog z muzyką Please choose the folder containing your music collection. Proszę wybrać katalog zawierający kolekcję z muzyką. Covers and Lyrics Okładki i teksty <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata pobierze brakujące okładki i teksty z internetu.</p><p>Dla każdego z nich należy wybrać, czy Cantata ma przechowywać pobrane pliki w katalogu z muzyką, czy w osobistym katalogu ustawień i pamięci podręcznej.</p> Save downloaded covers, artist, and composer images, in music folder Zapisz pobrane okładki oraz obrazy artystów i kompozytorów w katalogu muzyki Save downloaded lyrics in music folder Zapisz pobrane teksty w katalogu muzyki Save downloaded backdrops in music folder Zapisz pobrane tła w katalogu muzyki If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Jeżeli wybrano opcję, aby program Cantata przechowywał okładki, teksty oraz tła w katalogu muzyki, a użytkownik nie ma praw do zapisu w tym katalogu, to Cantata wróci do opcji zapisu tych plików w osobistym katalogu pamięci podręcznej. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. Program Cantata może zapisać tła, obrazy artysów i kompozytorów w hierarchii katalogów tylko jeśli ma ona głębokość 2 poziomów, tzn. 'Artysta/Album/Utwory'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Ustawienie 'Katalog z muzyką' wskazuje na adres HTTP, a program Cantata obecnie nie obsługuje wysyłania plików na zewnętrzne serwery HTTP. Dlatego powyższe ustawienie powinno pozostać odznaczone. Finished! Zakończono! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata została skonfigurowana!<br/><br/>Okno konfiguracji Cantaty może być użyte do dostosowania wyglądu programu, jak również w celu dodania hostów MPD itp. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantata grupuje utwory w albumy przy użyciu tagu 'ArtystaAlbumu', jeśli jest on ustawiony. W przeciwnym wypadku wykorzystywany jest tag 'Artysta'. Jeśli w kolekcji znajdują się albumy posiadające wiele artystów, to tag 'ArtystaAlbumu' <b>musi</b> w nich być ustawiony aby grupowanie działało poprawnie. W takim przypadku sugeruje się użycie 'Various Artists'. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>UWAGA:</b> Nie jesteś obecnie użytkownikiem należącym do grupy "users". Cantata będzie działać znacznie lepiej (zapisywanie okładek albumów, tekstów, etc. z odpowiednimi uprawnieniami), jeśli zostaniesz dodany do tej grupy. Po dodaniu się do grupy należy się wylogować i zalogować ponownie, aby te ustawienia zadziałały. Not Connected Nie połączono Connection Established Nawiązano połączenie Connection Failed Połączenie nie powiodło się Cantata will now terminate Program Cantata zostanie teraz zamknięty InputDialog Password Hasło Please enter password: Proszę podać hasło: InterfaceSettings Sidebar Panel boczny Views Widok Use the checkboxes below to configure which views will appear in the sidebar. Aby skonfigurować widoki dostępne na pasku bocznym należy użyć poniższych pól wyboru. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Jeżeli 'Lista odtwarzania' nie została zaznaczona powyżej, to będzie ona wyświetlana z boku wszystkich widoków. Jeżeli 'Informacja' nie została zaznaczona, to do paska narzędzi dodany zostanie przycisk pozwalający na dostęp do informacji o utworze. Options Opcje Style: Styl: Position: Pozycja: Only show icons, no text Pokazuj tylko ikony, bez tekstu Auto-hide Automatycznie ukryj Play Queue Kolejka odtwarzania Initially collapse albums Zwiń albumy przy uruchomieniu Automatically expand current album Automatycznie rozwijaj obecny album Scroll to current track Przewiń do obecnego utworu Prompt before clearing Pytaj przed czyszczeniem Separate action (and shortcut) for play queue search Osobna akcja (i skrót) dla wyszukiwania w kolejce Background Image Obraz tła None Brak Current album cover Okładka obecnego albumu Custom image: Własny obraz: Blur: Rozmycie: 10px 10px Opacity: Przezroczystość: 40% 40% Toolbar Pasek narzędzi Show stop button Pokazuj przycisk stop Show cover of current track Pokazuj okładkę obecnego utworu Show track rating Pokazuj ocenę utworu External Zewnętrzne Enable MPRIS D-BUS interface Włącz interfejs MPRIS D-BUS Show popup messages when changing tracks Pokazuj wyskakujące okienka przy zmianie utworu Show icon in notification area Pokarz ikonę w tacce systemowej Minimize to notification area when closed Minimalizuj do tacki systemowej przy zamykaniu On Start-up Przy uruchomieniu Show main window Pokaż główne okno Hide main window Ukryj główne okno Restore previous state Przywróć poprzedni stan Tweaks Usprawnienia Artist && Album Sorting Sortowanie Artysty i Albumu Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Poniżej można wprowadzić listę (oddzielaną przecinkami) predrostków, które mają być ignorowane podczas sortowania artystów i albumów, np. jeśli do listy wpisane zostanie słowo 'The', to artysta 'The Beatles' podczas sortowania będzie traktowany jako 'Beatles' Enter comma separated list of prefixes... Proszę wprowadzić oddzielaną przecinkami listę przedrostków... Composer Support Obsługa tagu 'Kompozytor' By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Domyślnie, Cantata używa tagu 'Artysta Albumu' (albo 'Artysta' jeśli utwór nie posiada tagu 'Artysta Albumu') do grupowania utworów i albumów. Dla niektórych gatunków, np. 'Muzyka klasyczna', użycie tagu 'Kompozytor' (jeśli został ustawiony) może być preferowane. Poniżej można wprowadzić listę (oddzielaną przecinkami) gatunków, dla których Cantata ma używać tagu 'Kompozytor'. Enter comma separated list of genres... Proszę wprowadzić oddzielaną przecinkami listę gatunków... Single Tracks Pojedyncze utwory If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Jeżeli w kolekcji znajduje się duża liczba artystów z pojedynczym utworem, to może być uciążliwe, aby każdy posiadał własny wpis w bibliotece w liście artystów. Jako obejście tego problemu, jeżeli wszystkie te utwory zostaną umieszczone w osobnym katalogu, którego adres zostanie podany poniżej, to program Cantata pogrupuje te utwory w albumie o nazwie 'Pojedyncze utwory' jako artysta 'Various Artists' Folder that contains single track files... Katalog zawierający pojedyncze utwory... CUE Files Pliki CUE A cue file is a metadata file which describes how the tracks of a CD are laid out. Plic cue zawiera metadane opisujące układ utworów na płycie CD. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. Zmiana którejkolwiek z powyższych opcji wymaga odświeżenia bazy danych (oraz prawdopodobnie ponownego uruchomienia programu Cantata) aby zmiany zostały wprowadzone. General Ogólne Fetch missing covers from Last.fm Pobierz brakujące okładki z Last.fm Show delete action in context menus Pokazuj akcję usuwania w menu kontekstowym Enforce single-click activation of items Wymuś aktywację elementów przy pojedynczym kliknięciu Changing the style setting will require a re-start of Cantata. Zmiana ustawienia stylu wymaga restartu programu Cantata. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>To ustawienie wpłynie na interfejs w następujący sposób: <ul><li>Przyciski odtwarzania i kontroli będą 33% szersze</li><li>Widoki będą przesuwane przez przeciągnięcie</li><li>Aby przeciągnąć elementy trzeba kliknąć/dotknąć lewy górny róg</li><li>Paski przewijania będą szerokie tylko na kilka pikseli</li><li>Akcje (np. "Dodaj do kolejki odtwarzania) będą zawsze widoczne (nie tylko po najechaniu na nie kursorem)</li><li>Wejścia numeryczne będą miały przyciski + i − obok pola tekstowego</li></ul></p> Make interface more touch friendly Interfejs dotykowy Show song information tooltips Pokazuj dymki z informacją utworu Support retina displays Obsługa wyświetlaczy retina Language: Język: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Zmiana ustawienia 'Wymuś aktywację elementów przy pojedynczym kliknięciu' wymaga restartu Cantaty. Changing the language setting will require a re-start of Cantata. Zmiana ustawienia języka wymaga restartu Cantaty. Changing the 'touch friendly' setting will require a re-start of Cantata. Zmiana ustawienia 'Interfejs dotykowy' wymaga restartu Cantaty. Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. Włączenie obsługi wyświetlaczy retina spowoduje użycie bardziej wyrazistych ikon na wyświetlaczach retina, ale może doprowadzić do mniej wyrazistego wyświetlania ikon na wyświetlaczach nie-retina. Zmiana tego ustawienia wymaga restartu Cantaty. Library Biblioteka Folders Katalogi Playlists Playlisty Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Internet - strumienie, Jamendo, Maganatune, SoundCloud, oraz podkasty Devices - UMS, MTP (e.g. Android), and AudioCDs Urządzenia - UMS, MTP (np. Android), i płyty AudioCDs Search (via MPD) Szukaj (przez MPD) Info - Current song information (artist, album, and lyrics) Informacje - szczegóły dotyczące obecnego utworu (artysta, album i teksty) Large Duże Small Małe Tab-bar Pasek kart Left Z lewej Right Z prawej Top U góry Bottom U dołu Images (*.png *.jpg) Obrazy (*.png *.jpg) 10px pixels 10px Notifications Powiadomienia English (en) English (en) System default Domyślne systemowe %1% value% %1% %1 px pixels %1 px ItemView Go Back Idź wstecz Updating... Uaktualnianie.... JamendoService The world's largest digital service for free music Największszy na świecie cyfrowy serwis darmowej muzyki JamendoSettingsDialog Jamendo Settings Ustawienia Jamendo MP3 MP3 Ogg Ogg Streaming format: Format strumienia: KeySequenceButton The key you just pressed is not supported by Qt. Wciśnięty klawisz nie jest wspierany przez Qt. Unsupported Key Niewspierany klawisz KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Proszę wcisnąć przycisk, następnie użyć skrótu klawiszowego który ma zostać przyporządkowany. Na przykład dla skrótu Ctrl+a należy przytrzymać klawisz Ctrl i wcisnąć klawisz a. Meta Meta key Meta Ctrl Ctrl key Ctrl Alt Alt key Alt Shift Shift key Shift Input What the user inputs now will be taken as the new shortcut Wejście None No shortcut defined Brak Shortcut Conflict Konflikt skrótów The "%1" shortcut is already in use, and cannot be configured. Please choose another one. Skrót "%1" jest już używany i nie może zostać skonfigurowany. Proszę wybrać inny skrót. The "%1" shortcut is ambiguous with the shortcut for the following action: Skrót "%1" jest wieloznaczny ze skrótem dla następującej akcji: Do you want to reassign this shortcut to the selected action? Czy przypisać ten skrót do zaznaczonej akcji? Reassign Przypisz LastFmEngine Read more on last.fm Czytaj więcej na last.fm LibraryDb Database error - please check Qt SQLite driver is installed Błąd bazy danych - proszę sprawdzić czy sterownik Qt SQLite jest zainstalowany LibraryPage Show Artist Images Pokazuj obrazy artysty Sort Albums Sortuj albumy Name Nazwa Year Rok Album, Artist, Year Album, artysta, rok Album, Year, Artist Album, rok, artysta Artist, Album, Year Artysta, album, rok Artist, Year, Album Artysta, rok, album Year, Album, Artist Rok, album, artysta Year, Artist, Album Rok, artysta, album Modified Date Data modyfikacji Group By Grupuj według Genre Gatunek Artist Artysta Album Album Are you sure you wish to delete the selected songs? This cannot be undone. Czy na pewno usunąć zaznaczone utwory? Ta operacja nie może być cofnięta. Delete Songs Usuń utwory LyricSettings Choose the websites you want to use when searching for lyrics. Proszę wybrać strony z tekstami. LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Jeśli Cantata nie może znaleźć tekstów, albo znalazła nieprawidłowe, to można użyć tego okna dialogowego do wprowadzenia nowych parametrów wyszukiwania. Na przykład, obecny utwór może być coverem - jeśli tak, to wyszukiwanie tekstu według oryginalnego artysty może pomóc. Jeśli to wyszukiwanie się powiedzie, to znaleziony tekst zostanie przyporządkowany w Cantacie oryginalnemu utworowi i artyście. Title: Tytuł: Artist: Artysta: Search For Lyrics Szukaj tekstów MPDConnection Unknown Nieznany Connection to %1 failed Połączenie z %1 nie powiodło się Connection to %1 failed - please check your proxy settings Połączenie z %1 nie powiodło się - proszę sprawdzić ustawienia proxy Connection to %1 failed - incorrect password Połączenie z %1 nie powiodło się - nieprawidłowe hasło Connecting to %1 Łączenie z %1 Failed to send command to %1 - not connected Nie powiodło się wysyłanie komendy do %1 - nie połączono Failed to load. Please check user "mpd" has read permission. Załadowanie nie powiodło się. Proszę upewnić się, że użytkownik "mpd" ma prawa odczytu. Failed to load. MPD can only play local files if connected via a local socket. Załadowanie nie powiodło się. MPD może odtwarzać lokalne pliki tylko gdy jest połączony poprzez lokalne gniazdo. MPD reported the following error: %1 MPD zgłosił następujący błąd: %1 Failed to send command. Disconnected from %1 Wysyłanie komendy nie powiodło się. Rozłączono z %1 Failed to rename <b>%1</b> to <b>%2</b> Zmiana nazwy z <b>%1</b> na <b>%2</b> nie powiodła się Failed to save <b>%1</b> Zapisanie <b>%1</b> nie powiodło się You cannot add parts of a cue sheet to a playlist! Nie można dodać części pliku cue do playlisty! You cannot add a playlist to another playlist! Nie można dodać playlisty do innej playlisty! Failed to send '%1' to %2. Please check %2 is registered with MPD. Wysłanie '%1' do %2 nie powiodło się. Należy sprawdzić, czy %2 jest zarejestrowany w MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. Nie można przechowywać ocen ponieważ komenda MPD 'sticker' nie jest obsługiwana. MagnatuneService None Brak Streaming Strumieniowanie MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com Muzyka online z magnatune.com MagnatuneSettingsDialog Magnatune Settings Ustawienia Magnatune Username: Użytkownik: Password: Hasło: Membership: Członkostwo: Downloads: Pobieranie: MainWindow [Dynamic] [Dynamiczne] Exit Full Screen Wyjdź z pełnego ekranu Configure Cantata... Konfiguruj Cantatę... Preferences Ustawienia Quit Zamknij About Cantata... O programie Cantata... Show Window Pokaż okno Server information... Informacja serwera... Refresh Database Odśwież bazę danych Refresh Odśwież Connect Połącz Collection Kolekcja Outputs Wyjścia Stop After Track Zatrzymaj po utworze Seek forward (%1 seconds) Przewiń do przodu (%1 sekund) Seek backward (%1 seconds) Przewiń do tyłu (%1 sekund) Add To Stored Playlist Dodaj do zapisanej playlisty Crop Others Przytnij pozostałe Add Stream URL Dodaj URL strumienia Clear Wyczyść Center On Current Track Wycentruj na obecnym utworze Expanded Interface Interfejs rozszerzony Show Current Song Information Pokaż informacje o obecnym utworze Full Screen Pełen ekran Random Losowo Repeat Powtarzaj Single Tryb pojedynczy When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. Gdy tryb 'Pojedynczy' jest aktywny, odtwarzanie jest zatrzymywane po obecnym utworze, albo utwór jest zapętlany, jeśli dodatkowo jest włączony tryb 'Powtarzaj'. Consume Tryb konsumowania When consume is activated, a song is removed from the play queue after it has been played. Gdy tryb konsumowania jest aktywny, obecny utwór jest usuwany z kolejki po jego zakończeniu. Find in Play Queue Znajdź w kolejce odtwarzania Play Stream Odtwarzaj strumień Locate In Library Znajdź w bibliotece Play next Odtwarzaj następny Edit Track Information (Play Queue) Edytuj informacje o utworze (kolejka odtwarzania) Expand All Rozwiń wszystkie Collapse All Zwiń wszystkie Cancel Anuluj Play Queue Kolejka odtwarzania Library Biblioteka Folders Katalogi Playlists Playlisty Internet Internet Devices Urządzenia Search Szukaj Info Informacje Show Menubar Pokaż pasek menu &Music &Muzyka &Edit &Edytuj &View &Widok &Queue &Kolejka &Settings &Ustawienia &Help &Pomoc Set Rating Ustaw ocenę No Rating Brak oceny Failed to locate any songs matching the dynamic playlist rules. Nie udało się znaleźć żadnych utworów pasujących do reguł dynamicznej playlisty. Connecting to %1 Łączenie z %1 Refresh MPD Database? Odświeżyć bazę danych MPD? About Cantata O programie Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> <b>Cantata %1</b><br/><br/>klient MPD.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Program wydany na licencji <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Na podstawie <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 Autorzy QtMPC<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Tło widoku kontekstowego za uprzejmością <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Metadane widoku kontekstowego za uprzejmością <a href="http://www.wikipedia.org">Wikipedii</a> i <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Proszę rozpatrzeć przesłanie własnych obrazów na <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Obecnie trwa pobieranie podcastu Wyjście z programu spowoduje przerwanie pobierania. Abort download and quit Przerwij pobieranie i zakończ Please close other dialogs first. Proszę najpierw zamknąć inne okna dialogowe. Enabled: %1 Włączone: %1 Disabled: %1 Wyłączone: %1 Server Information Informacje serwera <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Serwer</b></td></tr><tr><td align="right">Protokół:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Czas pracy:&nbsp;</td><td>%4</td></tr><tr><td align="right">Odtwarzanie:&nbsp;</td><td>%5</td></tr><tr><td align="right">Obsługa URL:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tagi:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>Baza danych</b></td></tr><tr><td align="right">Artyści:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albumy:&nbsp;</td><td>%2</td></tr><tr><td align="right">Utwory:&nbsp;</td><td>%3</td></tr><tr><td align="right">Czas trwania:&nbsp;</td><td>%4</td></tr><tr><td align="right">Aktualizacja:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD zgłosił następujący błąd: %1 Cantata Cantata Playback stopped Odtwarzanie zatrzymane Remove all songs from play queue? Usunąć wszystkie utwory z kolejki odtwarzania? Priority Priorytet Enter priority (0..255): Wpisz priorytet (0..255): Decrease priority for each subsequent track Zmniejsz priorytet dla każdego kolejnego utowru Playlist Name Nazwa playlisty Enter a name for the playlist: Proszę wpisać nazwę dla playlisty: '%1' is used to store favorite streams, please choose another name. '%1' jest używane do przechowywania ulubionych strumieni, proszę wybrać inną nazwę. A playlist named '%1' already exists! Add to that playlist? Playlista o nazwie '%1' już istnieje! Dodać do tej playlisty? Existing Playlist Istniejąca playlista %n Track(s) %n Utwór %n Utwory %n Utworów %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) MenuButton Menu Menu MessageOverlay Cancel Anuluj Mpris (Stream) (Strumień) MtpConnection Connecting to device... Łączenie się z urządzeniem... No devices found Nie znaleziono urządzeń Connected to device Połączono z urządzeniem Disconnected from device Rozłączono urządzenie Updating folders... Wysyłanie katalogów... Updating files... Uaktualniania plików... Updating tracks... Uaktualnianie utworów... MtpDevice Not Connected Nie połączono %1 free %1 wolnego miejsca MusicBrainz Failed to open CD device Otwarcie urządzenia CD nie powiodło się Track %1 Utwór %1 %1 (Disc %2) %1 (Płyta %2) No matches found in MusicBrainz Nie znaleziono pasujących wyników w MusicBrainz MusicLibraryModel Cue Sheet Plik .cue Playlist Playlista %n Track(s) %n Utwór %n Utwory %n Utworów %n Artist(s) %n Artysta %n Artyści %n Artystów %n Album(s) %n Album %n Albumy %n Albumów %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) %1 by %2 Album by Artist %1 w wykonaniu %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>UWAGA:</b> %1</i> NowPlayingWidget (Stream) (Strumień) OSXStyle &Window &Okno Close Zamknij Minimize Minimalizuj Zoom Przybliż OnlineDbService Downloading...%1% Pobieranie...%1% Parsing music list.... Przetwarzanie listy muzyki... Failed to download Pobieranie nie powiodło się %n Artist(s) %n Artysta %n Artyści %n Artystów OnlineDbWidget Group By Grupuj według Genre Gatunek Artist Artysta Configure Konfiguruj The music listing needs to be downloaded, this can consume over %1Mb of disk space Listing muzyki musi zostać pobrany, to może zająć ponad %1Mb miejsca na dysku Dowload music listing? Pobrać listing muzyki? Download Pobierz Re-download music listing? Pobrać ponownie listing muzyki? OnlineSearchService Searching... Szukanie... OnlineSearchWidget No tracks found. Nie znaleziono utworów. %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Użyj poniższych pól wyboru aby skonfigurować listę aktywnych serwisów. Configure Service Konfiguruj serwis OnlineView Song Information Informacje o utworze OnlineXmlParser Failed to parse Parsowanie nie powiodło się OpmlBrowsePage Reload Odśwież Failed to download directory listing Pobranie listingu katalogów nie powiodło się Failed to parse directory listing Parsowanie listingu katalogów nie powiodło się OtherSettings Background Image Obraz tła None Brak Artist image Obraz artysty Custom image: Własny obraz: Blur: Rozmycie: 10px 10px Opacity: Przezroczystość: 40% 40% Automatically switch to view after: Automatycznie zmień na widok po: Do not auto-switch Nie zmieniaj automatycznie ms ms Dark background Ciemnie tło Darken background, and use white text, regardless of current color palette. Przyciemnij tło oraz użyj białego tekstu, niezależnie od obecnej palety kolorów. Always collapse into a single pane Zawsze zwiń do pojedynczego panelu Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Pokazuj wyłącznie "Artystę", "Album" lub "Tekst", nawet jeśli jest wystarczająco miejsca na pokazanie wszystkich trzech. Only show basic wikipedia text Pokazuj tylko podstawowy tekst z Wikipedii Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantata pokazuje jedynie skrócone wersje stron wikipedii (bez obrazków, linków itp.). Skracanie stron nie zawsze jest poprawne w 100%, dlatego domyślnie Cantata pokazuje jedynie wstęp. Przy wybraniu opcji pokazania pełnego artykułu może dojść do błędów parsowania. W takim wypadku konieczne będzie również usunięcie artykułów obecnie przechowywanych w pamięci podręcznej (używając strony 'Pamięć podręczna'). Images (*.png *.jpg) Obrazy (*.png *.jpg) 10px pixels 10px %1% value% %1% %1 px pixels %1 px PathRequester Select Folder Wybierz katalog Select File Wybierz plik PlayQueueModel Title Tytuł Artist Artysta Album Album # Track number # Length Długość Disc Płyta Year Rok Original Year Rok oryginału Genre Gatunek Priority Priorytet Composer Kompozytor Performer Wykonawca Rating Ocena Remove Duplicates Usuń duplikaty Undo Cofnij Redo Ponów Shuffle Wymieszaj Tracks Utwory Albums Albumy Sort By Sortuj przy użyciu Album Artist Artysta albumu Track Title Tytuł utworu Track Number Numer utworu # (Track Number) # (Numer utworu) PlayQueueView Remove Usuń PlaybackSettings Playback Odtwarzanie Fa&deout on stop: Wy&cisz przy zatrzymaniu: None Brak ms ms Stop playback on exit Zatrzymaj odtwarzanie przy wyjściu Inhibit suspend whilst playing Powstrzymaj przejście w stan wstrzymania podczas odtwarzania If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Jeśli przycisk stop zostanie wciśnięty i przytrzymany, to wyświetlone zostanie menu pozwalające na wybór, czy odtwarzanie ma zostać zatrzymane teraz, czy po obecnym utworze. (Wyświetlanie przycisku stop możliwe jest przez użycie odpowiedniej opcji w sekcji Interfejs/Pasek narzędzi) Output Wyjście <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Brak połączenia.<br/>Poniższe ustawienia nie mogą być modyfikowane, ponieważ Cantata nie jest połączona z MPD.</i> &Crossfade between tracks: &Przenikanie między utworami: s s Replay &gain: Replay&Gain: About replay gain O ReplayGain Use the checkboxes below to control the active outputs. Użyj poniższych pól wyboru aby kontrolować aktywne wyjścia. Track Utwór Album Album Auto Automatycznie <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Połączono z %1<br/>Poniższe ustawienia dotyczą aktualnie połączonej kolekcji MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> ReplayGain jest propozycją standardu opublikowaną w 2001 mającą na celu znormalizowanie odczuwanej głośności komputerowych formatów dźwięku takich jak MP3 albo Ogg Vorbis. ReplayGain operuje na utworach i całych albumach i jest obsługiwany na rosnącej liczbie odtwarzaczy.<br/><br/>Następujące ustawienia ReplayGain mogą być użyte:<ul><li><i>Brak</i> - Informacja ReplayGain nie jest używana.</li><li><i>Utwór</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain utworu.</li><li><i>Album</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain albumu.</li><li><i>Automatycznie</i> - Głośność jest dostosowywana na podstawie tagu ReplayGain utworu jeśli włączone jest odtwarzanie losowe, w przeciwnym wypadku używany jest tag ReplayGain albumu.</li></ul> PlaylistRule Type: Typ: Include songs that match the following: Zawieraj utwory posiadające w tagach: Exclude songs that match the following: Nie zawieraj utworów posiadających w tagach: Artist: Artysta: Artists similar to: Artyści podobni do: Album Artist: Artysta albumu: Composer: Kompozytor: Album: Album: Title: Tytuł: Genre Gatunek From Year: Od roku: Any Dowolny To Year: Do roku: Comment: Komentarz: Filename / path: Nazwa pliku / ścieżka: Exact match Dokładne dopasowanie Only enter values for the tags you wish to be search on. Należy podać tylko wartości tagów, które mają być kryteriami wyszukiwania. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Dla gatunku, zakończenie tekstu przy użyciu gwiazdki dopasuje wiele gatunków, np. 'rock*' dopasuje zarówno 'Hard Rock' jak i 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule Dynamiczna reguła Smart Rule Inteligentne reguły Add Dodaj <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>BŁĄD</b>: 'Od roku' powinno mieć mniejszą wartość niż 'Do roku'</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>BŁĄD:</b> Zakres dat jest zbyt duży (może wynosić maksymalnie %1 lat)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> <i><b>BŁĄD:</b> Można dopasowywać tylko do nazwy pliku / ścieżki jeżeli opcja 'Dokładne dopasowanie' jest <b>nie</b> wybrana</i> PlaylistRules Name of Dynamic Rules Nazwa dynamicznych reguł Add Dodaj Edit Edytuj Remove Usuń Songs with ratings between: Utwory o ocenie pomiędzy: - - Songs with duration between: Utwory o długości pomiędzy: seconds sekund Number of songs in play queue: Ilość utworów w kolejce: Order songs: Kolejność utworów: About Rules O regułach PlaylistRulesDialog Dynamic Rules Dynamiczne reguły None Brak No Limit Brak limitu About dynamic rules Informacje o dynamicznych regułach <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with 10 entries. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Program Cantata będzie odpytywać bibliotekę przy użyciu podanych reguł. Lista reguł oznaczona jako <i>Zawiera</i> zostanie użyta do zbudowania zbioru utworów, które mogą zostać dodane. Lista reguł oznaczona jako <i>Nie zawiera</i> zostanie użyta do zbudowania zbioru utworów, które nie mogą zostać dodane. Jeśli nie ma reguł oznaczonych jako <i>Zawiera</i>, program Cantata założy że wszystkie utwory (z wyłączeniem utworów ze zbioru <i>Nie zawiera</i>) mogą zostać dodane.</p><p>Np. aby wyszukać utwory 'z gatunku Rock w wykonaniu Wibble ALBO utwory w wykonaniu Various Artists', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble Gatunek=Rock</li><li>Zawiera ArtystaAlbumu=Various Artists</li></ul> Aby wyszukać utwory 'w wykonaniu Wibble ale nie z albumu Abc', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble</li><li>Nie zawiera ArtystaAlbumu=Wibble Album=Abc</li></ul>Po utworzeniu zbioru uworów program Cantata losowo wybierze z niego utwory tak aby kolejka odtwarzania miała 10 elementów. Jeżeli zakres ocen zostanie ustawiony, to tylko utwory o takich ocenach będą dodane. Analogicznie można ustawić zakres długości.</p> Smart Rules Inteligentne reguły Ascending Rosnąco Descending Malejąco Name of Smart Rules Nazwa inteligentnych reguł Number of songs Ilość utworów <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Program Cantata będzie odpytywać bibliotekę przy użyciu podanych reguł. Lista reguł oznaczona jako <i>Zawiera</i> zostanie użyta do zbudowania zbioru utworów, które mogą zostać dodane. Lista reguł oznaczona jako <i>Nie zawiera</i> zostanie użyta do zbudowania zbioru utworów, które nie mogą zostać dodane. Jeśli nie ma reguł oznaczonych jako <i>Zawiera</i>, program Cantata założy że wszystkie utwory (z wyłączeniem utworów ze zbioru <i>Nie zawiera</i>) mogą zostać dodane.</p><p>Np. aby wyszukać utwory 'z gatunku Rock w wykonaniu Wibble ALBO utwory w wykonaniu Various Artists', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble Gatunek=Rock</li><li>Zawiera ArtystaAlbumu=Various Artists</li></ul> Aby wyszukać utwory 'w wykonaniu Wibble ale nie z albumu Abc', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble</li><li>Nie zawiera ArtystaAlbumu=Wibble Album=Abc</li></ul>Po utworzeniu zbioru uworów program Cantata losowo wybierze z niego utwory tak aby kolejka odtwarzania miała określoną liczbę elementów (domyślnie 10). Jeżeli zakres ocen zostanie ustawiony, to tylko utwory o takich ocenach będą dodane. Analogicznie można ustawić zakres długości.</p> About smart rules O inteligentnych regułach <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> <p>Program Cantata będzie odpytywać bibliotekę przy użyciu podanych reguł. Lista reguł oznaczona jako <i>Zawiera</i> zostanie użyta do zbudowania zbioru utworów, które mogą zostać dodane. Lista reguł oznaczona jako <i>Nie zawiera</i> zostanie użyta do zbudowania zbioru utworów, które nie mogą zostać dodane. Jeśli nie ma reguł oznaczonych jako <i>Zawiera</i>, program Cantata założy że wszystkie utwory (z wyłączeniem utworów ze zbioru <i>Nie zawiera</i>) mogą zostać dodane.</p><p>Np. aby wyszukać utwory 'z gatunku Rock w wykonaniu Wibble ALBO utwory w wykonaniu Various Artists', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble Gatunek=Rock</li><li>Zawiera ArtystaAlbumu=Various Artists</li></ul> Aby wyszukać utwory 'w wykonaniu Wibble ale nie z albumu Abc', należy użyć następujących reguł: <ul><li>Zawiera ArtystaAlbumu=Wibble</li><li>Nie zawiera ArtystaAlbumu=Wibble Album=Abc</li></ul>Po utworzeniu zbioru uworów program Cantata doda określoną ilość utworów do kolejki. Jeżeli zakres ocen zostanie ustawiony, to tylko utwory o takich ocenach będą dodane. Analogicznie można ustawić zakres długości.</p> Failed to save %1 Nie powiodło się zapisywanie %1 A set of rules named '%1' already exists! Overwrite? Zestaw reguł o nazwie '%1' już istnieje! Nadpisać? Overwrite Rules Nadpisz reguły Saving %1 Zapisywanie %1 PlaylistsModel New Playlist... Nowa playlista... Stored Playlists Przechowywane playlisty Standard playlists Standardowe playlisty %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) Smart Playlist Inteligentna lista odtwarzania Plurals %n Track(s) Utwory: %n Utwory: %n Utwory: %n %n Tracks (%1) Utwory: %n (%1) Utwory: %n (%1) Utwory: %n (%1) %n Album(s) Albumy: %n Albumy: %n Albumy: %n %n Artist(s) Artyści: %n Artyści: %n Artyści: %n %n Stream(s) Strumienie: %n Strumienie: %n Strumienie: %n %n Entry(s) Elementy: %n Elementy: %n Elementy: %n %n Rule(s) Reguły: %n Reguły: %n Reguły: %n %n Podcast(s) Podcasty: %n Podcasty: %n Podcasty: %n %n Episode(s) Odcinki: %n Odcinki: %n Odcinki: %n %n Update(s) available Dostępne aktualizacje: %n Dostępne aktualizacje: %n Dostępne aktualizacje: %n PodcastPage RSS: RSS: Website: Strona: Podcast details Szczegóły podcastu Select a podcast to display its details Zaznacz podcast aby wyświetlić jego szczegóły PodcastSearchDialog Subscribe Subskrybuj Enter URL Wprowadź URL Manual podcast URL Ręczny URL podcastu Search %1 Szukaj %1 Search for podcasts on %1 Szukaj podcastów na %1 Add Podcast Subscription Dodaj subskrypcję podcastu Browse %1 Przeglądaj %1 Browse %1 podcasts Przeglądaj %1 podcastów You are already subscribed to this podcast! Subskrypcja do danego podcastu już istnieje! Subscription added Dodano subskrypcję PodcastSearchPage Enter search term... Wprowadź termin do wyszukiwania... Search Szukaj Failed to fetch podcasts from %1 Pobieranie podcastów z %1 nie powiodło się There was a problem parsing the response from %1 Wystąpił problem podczas parsowania odpowiedzi z %1 PodcastService Subscribe to RSS feeds Zasubskrybuj kanał RSS %n Podcast(s) %n Podcast %n Podcasty %n Podcastów %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) %n Odcinek %n Odcinki %n Odcinków (Downloading: %1%) (Pobieranie: %1%) Failed to parse %1 Parsowanie %1 nie powiodło się Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata obsługuje jedynie podcasty audio! %1 zawiera wyłącznie podcasty wideo. Failed to download %1 Pobieranie %1 nie powiodło się PodcastSettingsDialog Check for new episodes: Sprawdź w poszukiwaniu nowych odcinków: Download episodes to: Pobierz odcinki do: Download automatically: Pobieraj automatycznie: Podcast Settings Ustawienia podcastów Manually Ręcznie Every 15 minutes Co 15 minut Every 30 minutes Co 30 minut Every hour Co godzinę Every 2 hours Co 2 godziny Every 6 hours Co 6 godzin Every 12 hours Co 12 godzin Every day Codziennie Every week Co tydzień Don't automatically download episodes Nie pobieraj automatycznie odcinków Latest episode Najnowszy odcinek Latest %1 episodes Najnowsze %1 odcinków All episodes Wszystkie odcinki PodcastUrlPage URL URL Enter podcast URL... Podaj adres URL podcastu... Load Załaduj Enter podcast URL below, and press 'Load' Wprowadź poniżej URL podcastu i wciśnij przycisk 'Załaduj' Invalid URL! Nieprawidłowy URL! Failed to fetch podcast! Pobieranie podcastu nie powiodło się! Failed to parse podcast. Parsowanie podcastu nie powiodło się. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata obsługuje jedynie podcasty audio! Podany URL zawiera wyłącznie podcasty wideo. PodcastWidget Add Subscription Dodaj subskrypcję Remove Subscription Usuń subskrypcję Download Episodes Pobierz odcinki Delete Downloaded Episodes Usuń pobrane odcinki Cancel Download Anuluj pobieranie Mark Episodes As New Oznacz odcinek jako nowy Mark Episodes As Listened Oznacz odcinek jako odsłuchany Show Unplayed Only Pokaż tylko nieodtworzone Unsubscribe from '%1'? Wypisać się z '%1'? Do you wish to download the selected podcast episodes? Czy pobrać zaznaczone odcinki podcastu? Cancel podcast episode downloads (both current and any that are queued)? Anulować pobieranie odcinków podcastu (zarówno obecnych jak i zakolejnowanych)? Do you wish to the delete downloaded files of the selected podcast episodes? Czy usunąć pobrane pliki zaznaczonych odcinków podcastu? Do you wish to mark the selected podcast episodes as new? Czy oznaczyć zaznaczone odcinki podcastu jako nowe? Do you wish to mark the selected podcast episodes as listened? Czy oznaczyć zaznaczone odcinki podcastu jako wysłuchane? Refresh all subscriptions? Odświeżyć wszystkie subskrypcje? Refresh Odśwież Refresh All Odśwież wszystkie Refresh all subscriptions, or only those selected? Odświeżyć wszystkie subskrypcje, czy tylko zaznaczone? Refresh Selected Odśwież zaznaczone PowerManagement Cantata is playing a track Cantata odtwarza utwór PreferencesDialog Collection Kolekcja Collection Settings Ustawienia kolekcji Playback Odtwarzanie Playback Settings Ustawienia odtwarzania Downloaded Files Pobrane pliki Downloaded Files Settings Ustawienia pobranych plików Interface Interfejs Interface Settings Ustawienia interfejsu Info Informacje Info View Settings Ustawienia widoku informacyjnego Scrobbling Scrobbling Scrobbling Settings Ustawienia scrobblowania Audio CD Płyta audio Audio CD Settings Ustawienia płyt audio Proxy Proxy Proxy Settings Ustawienia Proxy Shortcuts Skróty Keyboard Shortcut Settings Ustawienia skrótów klawiszowych Cache Pamięć podręczna Cached Items Elementy w pamięci podręcznej Custom Actions Własne akcje Cantata Preferences Ustawienia Cantaty Configure Konfiguruj ProxySettings Mode: Tryb: Type: Typ: HTTP Proxy HTTP Proxy SOCKS Proxy SOCKS Proxy Host: Host: Port: Port: Username: Użytkownik: Password: Hasło: No proxy Brak Proxy Use the system proxy settings Użyj systemowych ustawień proxy Manual proxy configuration Ręczne ustawienia proxy QObject Track listing Listing utworów Read more on wikipedia Czytaj więcej na Wikipedii Open in browser Otwórz w przeglądarce <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://pl.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) to opatentowany algorytm stratnej kompresji danych dźwiękowych.<br>AAC generalnie osiąga lepszą jakość dźwięku niż MP3 przy podobnych rozmiarach pliku. Jest to dobry wybór dla iPodów oraz niektórych innych przenośnych odtwarzaczy muzycznych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>AAC</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> - wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem <a href=http://www.ffmpeg.org/faq.html#SEC21>średniego bitrate'u</a> wynikowego utworu.<br><b>150kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>120kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>200kb/s</b> jest prawdopodobnie przesadzone. Expected average bitrate for variable bitrate encoding Szacowany średni bitrate przy kodowaniu ze zmiennym bitrate Smaller file Mniejszy plik Better sound quality Lepsza jakość dźwięku <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://pl.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) jest opatentowanym cyfrowym kodekiem audio używającym stratnej kompresji danych.<br>Pomimo swoich słabych stron, jest bardzo popularnym formatem do przechowywania muzyki oraz jest obsługiwany przez zdecydowaną większość przenośnych odtwarzaczy muzycznych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>MP3</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem średniego bitrate'u wynikowego utworu.<br><b>160kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>120kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>205kb/s</b> jest prawdopodobnie przesadzone. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> jest stratnym kodekiem audio wydanym na licencji open-source nie wymagającym licencji patentowych.<br>Generuje mniejsze pliki niż format MP3 przy porównywalnych lub lepszych jakościach. Ogg Vorbis jest ogólnie doskonałym wyborem, szczególnie dla przenośnych odtwarzaczy obsługujących ten format. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>Vorbis</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a>- wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Enkoder Vorbis używa wskaźnika jakości ze skali o zakresie od -1 do 10 w celu oceny oczekiwanej jakości. Przedstawiona wartość bitrate jest tylko zgrubnym oszacowaniem (dostarczonym przez twórców kodeka Vorbis) średniej wartości bitrate'u wynikowego pliku przy zadanej jakości. W związku z tym, nowsze i bardziej efektywne wersje kodeka Vorbis tworzą pliki o jeszcze mniejszej wartości bitrate.<br><b>5</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>3</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>8</b> jest prawdopodobnie przesadzone. Quality rating Wskaźnik jakości Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://pl.wikipedia.org/wiki/Opus_(format)>Opus</a> jest wolnym od patentów kodekiem audio używającym stratnej kompresji danych. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>Kodek <b>Opus</b> używany przez program Cantata obsługuje <a href=http://pl.wikipedia.org/wiki/VBR>zmienny bitrate (VBR)</a> wartość bitrate waha się w trakcie utworu w zależności od stopnia skomplikowania zawartości pliku. Bardziej skomplikowane fragmenty kodowane są przy użyciu większej ilości danych niż fragmenty mniej skomplikowane; takie podejście daje ogólnie lepszą jakość i mniejsze pliki, niż gdyby użyć stałego bitrate'u w całym utworze.<br>Z tego powodu, wyświetlana wartość bitrate jest tylko oszacowaniem średniego bitrate'u wynikowego utworu.<br><b>128kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych. <br/>Wartości poniżej <b>100kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>256kb/s</b> jest prawdopodobnie przesadzone. Bitrate Bitrate Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) jest kodekiem audio do bezstratnej kompresji muzyki cyfrowej.<br>Polecany jedynie dla odtwarzaczy muzycznych firmy Apple oraz odtwarzaczy nie obsługujących formatu FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) jest bezstratnym kodekiem audio wydanym na licencji open-source nie wymagającym licencji patentowych.<br>Kodek ten jest polecany do przechowywania muzyki bez straty jakości dźwięku. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Poziom kompresji</a> jest liczbą całkowitą z przedziału od 0 do 8 reprezentującą kompromis pomiędzy rozmiarem pliku a prędkością kompresji przy użyciu formatu <b>FLAC</b>.<br/> Ustawienie poziomu kompresji na <b>0</b> daje najkrótsze czasy kompresji jednak generowane pliki są stosunkowo duże.<br/>Z drugiej strony, poziom kompresji równy <b>8</b> powoduje, że kompresja jest dosyć powolna, jednak generowane pliki są najmniejsze.<br/>Należy pamiętać, że format FLAC jest z definicji bezstratny, więc jakość dźwięku wyjściowego jest identyczna z oryginałem niezależnie od ustawionego poziomu kompresji.<br/>Ponadto, poziomy powyżej <b>5</b> dramatycznie podnoszą czas kompresji dając jedynie niewiele mniejsze pliki wyjściowe i są niezalecane. Compression level Poziom kompresji Faster compression Szybsza kompresja Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://pl.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) jest stworzonym przez firmę Microsoft zamkniętym kodekiem audio służącym do stratnej kompresji dźwięku.<br>Zalecany jedynie dla odtwarzaczy muzycznych nie obsługujących formatu Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Bitrate jest miarą ilości danych używanych do reprezentacji sekundy ścieżki muzycznej.<br>W związku z ograniczeniami zamkniętego formatu <b>WMA</b> oraz z trudnościami związanymi z analizą działania zamkniętego enkodera, używany przez program Cantata enkoder WMA używa opcji <a https://pl.wikipedia.org/wiki/Stała_przepływność>stały bitrate (CBR)</a>.<br>Z tego powodu wyświetlana wartość bitrate jest dosyć dokładnym oszacowaniem bitrate'u pliku wynikowego.<br><b>136kb/s</b> jest dobrym wyborem dla użytku w odtwarzaczach przenośnych.<br/>Wartości poniżej <b>112kb/s</b> mogą nie być satysfakcjonujące dla muzyki, a wszystko powyżej <b>182kb/s</b> jest prawdopodobnie przesadzone. Empty filename. Pusta nazwa pliku. Invalid filename. (%1) Nieprawidłowa nazwa pliku. (%1) Failed to save %1. Zapisywanie nie powiodło się %1. Failed to delete rules file. (%1) Usunięcie pliku z regułami nie powiodło się. (%1) Invalid command. (%1) Nieprawidłowa komenda. (%1) Could not remove active rules link. Nie można usunąć powiązania dla aktywnej reguły. Active rules is not a link. Aktywna reguła nie jest powiązaniem. Could not create active rules link. Nie można stworzyć powiązania aktywnej reguły. Rules file, %1, does not exist. Plik reguł, %1, nie istnieje. Incorrect arguments supplied. Podano nieprawidłowe argumenty. Unknown method called. Wywołano nieznaną metodę. Unknown error Nieznany błąd Artist Artysta SimilarArtists PodobniArtyści AlbumArtist ArtystaAlbumu Composer Kompozytor Comment Komentarz Album Album Title Tytuł Genre Gatunek Date Data File Plik Include Zawiera Exclude Nie zawiera (Exact) (Dokładnie) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Obecna okładka CoverArt Archive Archiwum okładek Grouped Albums Grupuj Albumy Table Tabela Parse in Library view, and show in Folders view Przetwarzaj w widoku Biblioteki i pokazuj w widoku Katalogów Only show in Folders view Pokazuj tylko w widoku katalogów Do not list Nie pokazuj na liście %1 Tracks Plural (N!=1) Utwory: %1 1 Track (%1) Singular Utwory: 1 (%1) %1 Tracks (%2) Plural (N!=1) Utwory: %1 (%2) %1 Albums Plural (N!=1) Albumy: %1 %1 Artists Plural (N!=1) Artyści: %1 %1 Streams Plural (N!=1) Strumienie: %1 %1 Entries Plural (N!=1) Elementy: %1 %1 Rules Plural (N!=1) Reguły: %1 %1 Podcasts Plural (N!=1) Podcasty: %1 %1 Episodes Plural (N!=1) Odcinki: %1 %1 Updates available Plural (N!=1) Dostępne aktualizacje: %1 Previous Track Poprzedni utwór Next Track Następny utwór Play/Pause Odtwarzaj/Wstrzymaj Stop Stop Stop After Current Track Zatrzymaj po obecnym utworze Stop After Track Zatrzymaj po utworze Increase Volume Zwiększ głośność Decrease Volume Zmniejsz głośność Save As Zapisz jako Append Dodaj na koniec Append To Play Queue Dodaj na koniec kolejki odtwarzania Append And Play Dodaj na koniec i odtwarzaj Add And Play Dodaj i odtwarzaj Append To Play Queue And Play Dodaj do kolejki odtwarzania i odtwarzaj Insert After Current Wstaw za obecnym Append Random Album Dodaj losowy album Play Now (And Replace Play Queue) Odtwarzaj teraz (i zastąp kolejkę odtwarzania) Add With Priority Dodaj z priorytetem Set Priority Ustaw priorytet Highest Priority (255) Najwyższy priorytet (255) High Priority (200) Wysoki priorytet (200) Medium Priority (125) Średni priorytet (125) Low Priority (50) Niski priorytet (50) Default Priority (0) Domyślny priorytet (0) Custom Priority... Inny priorytet... Add To Playlist Dodaj do playlisty Organize Files Organizuj pliki Edit Track Information Edytuj informacje o utworze ReplayGain ReplayGain Copy Songs To Device Skopiuj utwory do urządzenia Delete Songs Usuń utwory Set Image Ustaw obraz Remove Usuń Find Szukaj Add To Play Queue Dodaj do kolejki odtwarzania Parse error loading cache file, please check your songs tags. Błąd przetwarzania podczas ładowania plików pamięci podręcznej, zaleca się sprawdzenie tagów utworów. Other Inne Default Domyślne "%1" name (host) "%1" "%1" (%2:%3) name (host:port) "%1" (%2:%3) Single Tracks Pojedyncze utwory Personal Osobiste Unknown Nieznany Various Artists Various Artists Album artist Artysta albumu Performer Wykonawca Track number Numer utworu Disc number Numer płyty Year Rok Orignal Year Rok oryginału Length Długość <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> na <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> w wykonaniu <b>%2</b> na <b>%3</b> Invalid service Nieprawidłowy serwis Invalid method Nieprawidłowa metoda Authentication failed Uwierzytelnianie nie powiodło się Invalid format Nieprawidłowy format Invalid parameters Nieprawidłowe parametry Invalid resource specified Podano nieprawidłowy zasób Operation failed Operacja nie powiodła się Invalid session key Nieprawidłowy klucz sesji Invalid API key Nieprawidłowy klucz API Service offline Serwis jest offline Last.fm is currently busy, please try again in a few minutes Last.fm jest obecnie zajęty, proszę spróbować za kilka minut Rate-limit exceeded Przekroczono limit prób General Ogólne Digitally Imported Digitally Imported Local and National Radio (ListenLive) Lokalne i krajowe radio (ListenLive) &OK &Ok &Cancel &Anuluj &Yes &Tak &No &Nie &Discard &Odrzuć &Save Zapi&sz &Apply &Zastosuj &Close Zam&knij &Help &Pomoc &Overwrite Nad&pisz &Reset &Resetuj &Continue &Kontynuuj &Delete &Skasuj &Stop &Stop &Remove &Usuń &Previous &Poprzedni &Next &Następny Close Zamknij Error Błąd Information Informacja Warning Ostrzeżenie Question Pytanie %1 B %1 B %1 kB %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Drzewo podstawowe (bez ikon) Simple Tree Proste drzewo Detailed Tree Szczegółowe drzewo List Lista Grid Siatka %n Track(s) Utwory: %n Utwory: %n Utwory: %n %n Tracks (%1) Utwory: %n (%1) Utwory: %n (%1) Utwory: %n (%1) %n Album(s) Albumy: %n Albumy: %n Albumy: %n %n Artist(s) Artyści: %n Artyści: %n Artyści: %n %n Stream(s) Strumienie: %n Strumienie: %n Strumienie: %n %n Entry(s) Elementy: %n Elementy: %n Elementy: %n %n Rule(s) Reguły: %n Reguły: %n Reguły: %n %n Podcast(s) Podcasty: %n Podcasty: %n Podcasty: %n %n Episode(s) Odcinki: %n Odcinki: %n Odcinki: %n %n Update(s) available Dostępne aktualizacje: %n Dostępne aktualizacje: %n Dostępne aktualizacje: %n RemoteDevicePropertiesDialog Device Properties Ustawienia urządzenia Connection Połączenie Music Library Biblioteka muzyki Add Device Dodaj urządzenie A remote device named '%1' already exists! Please choose a different name. Urządzenie zdalne o nazwie '%1' już istnieje! Proszę wybrać inną nazwę. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Te ustawienia można edytować jedynie gdy urządzenie nie jest podłączone. Type: Typ: Name: Nazwa: Options Opcje Host: Host: Port: Port: User: Użytkownik: Domain: Domena: Password: Hasło: Share: Udział: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Jeżeli hasło zostanie wpisane tutaj, to będzie ono przechowywane w postaci <b>niezaszyfrowanej</b> w pliku konfiguracyjnym Cantaty. Aby program Cantata za każdym razem pytał o hasło podczas dostępu do udziału należy hasło ustawić na '-' Service name: Nazwa serwisu: Folder: Katalog: Extra Options: Opcje dodatkowe: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. W związku z mechanizmem działania sshfs, odpowiednia aplikacja typu ssh-askpass (ksshaskpass, ssh-askpass-gnome, itp.) konieczna jest do wpisania hasła. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. To okno dialogowe jest używane jedynie do dodawania zdalnych urządzeń (np. za pomocą protokołu Samba) albo w celu dostępu do lokalnie zamontowanych katalogów. W przypadku normalnych odtwarzaczy mediów podłączonych przez USB Cantata automatycznie wyświetli urządzenie gdy będzie ono podłączone. Samba Share Udziały Samby Samba Share (Auto-discover host and port) Udziały Samby (Automatycznie wykryj hosta i porty) Secure Shell (sshfs) Bezpieczna powłoka (sshfs) Locally Mounted Folder Katalog zamontowany lokalnie RemoteFsDevice Available Dostępny Not Available Niedostępny Failed to resolve connection details for %1 Nie powiodło się pobranie szczegółów połączenia dla %1 Connecting... Łączenie... Password prompting does not work when cantata is started from the commandline. Pytanie o hasło nie działa, jeśli Cantata uruchomiana została z wiersza poleceń. No suitable ssh-askpass application installed! This is required for entering passwords. Nie znaleziono odpowiedniej aplikacji ssh-askpass! Jest ona wymagana do wprowadzania haseł. Mount point ("%1") is not empty! Punkt montowania ("%1") nie jest pusty! "sshfs" is not installed! "sshfs" nie jest zainstalowane! Disconnecting... Rozłączanie... "fusermount" is not installed! "fusermount" nie jest zainstalowane! Failed to connect to "%1" Nie powiodło się łączenie z "%1" Failed to disconnect from "%1" Nie powiodło się rozłączanie z "%1" Updating tracks... Uaktualnianie utworów... Not Connected Nie połączono Capacity Unknown Pojemność nieznana %1 free %1 wolnego miejsca RgDialog ReplayGain ReplayGain Show All Tracks Pokaż wszystkie utwory Show Untagged Tracks Pokaż utwory bez tagów Remove From List Usuń z listy Artist Artysta Album Album Title Tytuł Album Gain Gain albumu Track Gain Gain utworu Album Peak Peak albumu Track Peak Peak utworu Scan Skanuj Update ReplayGain tags in tracks? Uaktualnić tagi ReplayGain w utworach? Update Tags Uaktualnij tagi Abort scanning of tracks? Przerwać skanowanie plików? Abort Przerwij Abort reading of existing tags? Przerwać czytanie istniejących tagów? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Skanować <b>wszystkie</b> utwory?<br/><br/><i>Wszystkie utwory mają już istniejące tagi ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Czy przeskanować wszystkie utwory, czy tylko te bez istniejących tagów? Untagged Tracks Utwory bez tagów All Tracks Wszystkie utwory Scanning tracks... Skanowanie utworów... Reading existing tags... Czytanie istniejących tagów... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Uszkodzone tagi?) Failed to update the tags of the following tracks: Uaktualnianie tagów następujących utworów nie powiodło się: Device has been removed! Urządzenie zostało usunięte! Device is not connected. Urządzenie nie jest podłączone. Device is busy? Urządzenie jest zajęte? %1 dB %1 dB Failed Nie powiodło się Original: %1 dB Oryginał: %1 dB Original: %1 Oryginał: %1 Remove the selected tracks from the list? Usunąć zaznaczone utwory z listy? Remove Tracks Usuń utwory RulesPlaylists Album Artist Artysta albumu Artist Artysta Album Album Composer Kompozytor Date Data Genre Gatunek Rating Ocena File Age Wiek pliku Random Losowo %n Rule(s) %n reguła %n reguły % reguł , Rating: %1..%2 , ocena: %1..%2 Ascending Rosnąco Descending Malejąco Scrobbler %1 error: %2 %1 błąd: %2 ScrobblingLove %1: Loved Current Track %1: Polubiono obecny utwór %1: Love Current Track %1: Polub obecny utwór ScrobblingSettings Scrobble using: Scrobbluj przy użyciu: Username: Użytkownik: Password: Hasło: Status: Stan: Login Login Scrobble tracks Scrobbluj utwory Show 'Love' button Pokazuj przycisk 'polub' %1 (via MPD) scrobbler name (via MPD) %1 (przez MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Jeśli użyty jest scrobbler oznaczony jako '(przy użyciu MPD)' (na przykład %1), to musi on być już uruchomiony i działający. Cantata może jedynie 'Polubić' utwory przy jego pomocy, ale nie może wyłączyć lub włączyć scrobblowania. Authenticating... Uwierzytelnianie... Authenticated Uwierzytelniono Not Authenticated Nie uwierzytelniono ScrobblingStatus %1: Scrobble Tracks %1: Scrobbluj utwory SearchModel # (Track Number) # (Numer utworu) SearchPage Locate In Library Znajdź w bibliotece Artist: Artysta: Composer: Kompozytor: Performer: Wykonawca: Album: Album: Title: Tytuł: Genre: Gatunek: Comment: Komentarz: Date: Data: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Wyszukiwanie utworów będzie brało pod uwagę tag 'Data'.<br/><br/>Zazwyczaj podanie samego roku powinno wystarczyć. Original Date: Rok oryginału: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Wyszukiwanie utworów będzie brało pod uwagę tag 'Data oryginału'.<br/><br/>Zazwyczaj podanie samego roku powinno wystarczyć. Modified: Zmodyfikowano: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. Aby wyszukać pliki zmienione od daty należy wprowadzić datę w formacie YYYY/MM/DD - np. 2015/01/31.<br/><br>Wprowadzenie liczby spowoduje wyszukanie plików zmienionych od podanej liczby dni. File: Plik: Any: Dowolne: No tracks found. Nie znaleziono utworów. %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) SearchWidget Search... Szukaj... Close Search Bar Zamknij pasek wyszukiwania ServerSettings Collection: Kolekcja: Name: Nazwa: Host: Host: Password: Hasło: Music folder: Katalog z muzyką: Cover filename: Nazwa pliku okładki: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Nazwa pliku (bez rozszerzenia) do zapisu okładek.<br/>Przy pozostawieniu tego pola pustego, nazwa 'cover' zostanie użyta.<br/><br/><i>%artist% zostanie zastąpiony przez nazwę artysty albumu obecnego utworu, natomiast %album% zostanie zastąpiony przez nazwę albumu.</i></p> HTTP stream URL: URL strumienia HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. Ustawienie 'Katalog z muzyką' jest używane to wyszukiwania okładek. Jeśli używana instancja MPD jest na zdalnym komputerze, a okładki dostępne są przez HTTP, to ten adres może być ustawiony na URL HTTP. Jeżeli nie jest używany URL HTTP i użytkownik ma prawa zapisu do podanego katalogu (oraz jego podkatalogów), to program Cantata będzie zapisywał wszystkie pobrane okładki do odpowiednich katalogów albumów. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> Jeżeli nie podano ustawienia 'Nazwa pliku okładki', to Cantata użyje domyślnej wartości <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. 'URL strumienia HTTP' używany jest jedynie jeśli MPD skonfigurowany jest z wyjściem do strumienia HTTP a Cantata ma odtwarzać ten strumień. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. Jeśli ustawienie 'Katalog z muzyką' zostanie zmienione, należy ręcznie uaktualnić bazę danych muzyki. Można to zrobić używając akcji 'Odśwież bazę danych' w widokach 'Artyści' lub 'Albumy'. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. Ten katalog zostanie będzie również używany do wyszukiwania plików do edycji tagów, skanowania ReplayGain, oraz do transferu do i z urządzeń. This folder will also be used to locate music files for tag-editing, replay gain, etc. Ten katalog zostanie będzie również używany do wyszukiwania plików do edycji tagów, skanowania ReplayGain, itp. Which type of collection do you wish to connect to? Do jakiego typu kolekcji program ma się połączyć? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Standardowy - kolekcja muzyki może być współdzielona, znajduje się na innej maszynie, jest już skonfigurowa albo ma być używana z innymi klientami (np. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. Podstawowy - kolekcja muzyki nie jest współdzielona a program Cantata skonfiguruje i będzie kontrolować program MPD. Taka konfiguracja będzie dostępna wyłącznie dla programu Cantata i <b>nie</b> będzie można jej używać z innymi klientami MPD. <i><b>NOTE:</b> %1</i> <i><b>UWAGA:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Jeżeli mają być używane zaawansowane ustawienia MPD (np. wiele wyjść audio, pełne wsparcie DSD, itp.) wtedy <b>trzeba</b> wybrać opcję 'Standardowy' Add Collection Dodaj kolekcję Standard Standardowa Basic Podstawowa Delete '%1'? Usunąć '%1'? Delete Usuń New Collection %1 Nowa kolekcja %1 Default Domyślne ServiceStatusLabel Logged into %1 Zalogowano do %1 <b>NOT</b> logged into %1 <b>NIE</b> zalogowano do %1 ShortcutsModel Action Akcja Shortcut Skrót ShortcutsSettingsWidget Search: Szukaj: Shortcut for Selected Action Skrót dla zaznaczonej akcji Default: Domyślny: None Brak Custom: Własny: SinglePageWidget Refresh Odśwież View Widok SmartPlaylists Smart Playlists Inteligentne listy odtwarzania Rules based playlists Listy odtwarzania bazujące na regułach SmartPlaylistsPage Add Dodaj Edit Edytuj Remove Usuń Are you sure you wish to remove the selected rules? This cannot be undone. Czy na pewno usunąć zaznaczone reguły? Ta operacja nie może być cofnięta. Remove Smart Rules Usuń inteligentne reguły Failed to locate any matching songs Znalezienie pasujących utworów nie powiodło się SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Dostęp do plików utworów nie powiódł się! Proszę sprawdzić poprawność ustawień "Katalog z muzyką" w programie Cantata oraz "music_directory" w MPD. Cannot access song files! Please check that the device is still attached. Dostęp do plików utworów nie powiódł się! Proszę sprawdzić czy urządzenie nadal jest podłączone. SongView Lyrics Teksty Information Informacja Metadata Metadane Scroll Lyrics Przewijaj teksty Refresh Lyrics Odśwież teksty Edit Lyrics Edytuj teksty Delete Lyrics File Usuń plik z tekstami Refresh Track Information Odśwież informacje o utworze Cancel Anuluj Track Utwór Reload lyrics? Reload from disk, or delete disk copy and download? Załadować teksty ponownie? Załadować z dysku, czy usunąć z dysku i pobrać? Reload Odśwież Reload From Disk Załaduj z dysku Download Pobierz Current playing song has changed, still perform search? Obecnie odtwarzany utwór uległ zmianie. Czy nadal wykonać wyszukiwanie? Song Changed Zmieniono utwór Perform Search Szukaj Delete lyrics file? Usunąć plik z tekstami? Delete File Usuń plik Artist Artysta Album artist Artysta albumu Composer Kompozytor Lyricist Twórca tekstu Conductor Dyrygent Remixer Twórca remixu Album Album Subtitle Podtytuł Track number Numer utworu Disc number Numer płyty Genre Gatunek Date Data Original date Oryginalna data Comment Komentarz Copyright Prawa autorskie Label Wytwórnia muzyczna Catalogue number Numer katalogowy Title sort Tytuł (sortowanie) Artist sort Artysta (sortowanie) Album artist sort Artysta albumu (sortowanie) Album sort Album (sortowanie) Encoded by Enkodowane przez Encoder Enkoder Mood Nastrój Media Nośnik Bitrate Bitrate Sample rate Częstość próbkowania Channels Kanały Tagging time Czas tagowania Performer (%1) Wykonawca (%1) %1 kb/s %1 kb/s %1 Hz %1 Hz Bits Bity Performer Wykonawca Year Rok Filename Nazwa pliku Fetching lyrics via %1 Pobieranie tekstów z %1 SoundCloudService Search for tracks from soundcloud.com Szukaj utworów na soundcloud.com SpaceLabel Calculating... Obliczanie... Total space used: %1 Całkowita użyta przestrzeń: %1 SqlLibraryModel %n Artist(s) %n Artysta %n Artyści %n Artystów %n Album(s) %n Album %n Albumy %n Albumów %n Tracks (%1) %n Utwór (%1) %n Utwory (%1) %n Utworów (%1) Cue Sheet Plik .cue Playlist Playlista StoredPlaylistsPage Rename Zmień nazwę Remove Duplicates Usuń duplikaty Initially Collapse Albums Wstępnie zwiń albumy Are you sure you wish to remove the selected playlists? This cannot be undone. Czy na pewno usunąć zaznaczone playlisty? Ta operacja nie może być cofnięta. Remove Playlists Usuń playlisty Playlist Name Nazwa playlisty Enter a name for the playlist: Proszę wpisać nazwę dla playlisty: A playlist named '%1' already exists! Overwrite? Playlista o nazwie '%1' już istnieje! Nadpisać? Overwrite Playlist Nadpisz playlisty Rename Playlist Zmiana nazwy playlisty Enter new name for playlist: Proszę wpisać nową nazwę dla playlisty: Cannot add songs from '%1' to '%2' Nie można dodać utworów z '%1' do '%2' StreamDialog Add stream to favourites Dodaj strumień do ulubionych Name: Nazwa: URL: URL: Add Stream Dodaj strumień Edit Stream Edytuj strumień <i><b>ERROR:</b> Invalid protocol</i> <i><b>BŁAD:</b> Niewłaściwy protokół</i> StreamFetcher Loading %1 Ładowanie %1 StreamProviderListDialog Installed Zainstalowano Update available Aktualizacja dostępna Check the providers you wish to install/update. Proszę sprawdzić dostawców, którzy mają zostać zainstalowani/zaktualizowani. Install/Update Stream Providers Instaluj/aktualizuj dostawców strumieni Downloading list... Pobieranie listy... Failed to download list of stream providers! Pobieranie listy dostawców strumieni nie powiodło się! Installing/updating %1 Instalowanie/aktualizowanie %1 Failed to install '%1' Instalacja '%1' nie powiodła się Failed to download '%1' Pobieranie '%1' nie powiodło się Install/update the selected stream providers? Zainstalować/zaktualizować zaznaczonych dostawców strumieni? Install the selected stream providers? Zainstalować zaznaczonych dostawców strumieni? Update the selected stream providers? Zaktualizować zaznaczonych dostawców strumieni? Install/Update Instaluj/aktualizuj Abort installation/update? Przerwać instalację/aktualizację? Abort Przerwij %n Update(s) available %n Dostępna aktualizacja %n Dostępne aktualizacje %n Dostępnych aktualizacji Downloading %1 Pobieranie %1 Update all updateable providers Zaktualizuj wszystkich dostawców StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Szukaj strumienia Search for radio streams Szukaj strumieni radiowych Enter string to search Wprowadź termin do wyszukiwania Not Loaded Nie załadowano Loading... Ładowanie... %n Entry(s) %n Element %n Elementy %n Elementów StreamSearchPage Added '%1'' to favorites Dodano '%1' do ulubionych StreamsBrowsePage Import Streams Into Favorites Importuj strumienie do ulubionych Export Favorite Streams Eksportuj ulubione strumienie Add New Stream To Favorites Dodaj nowy strumień do ulubionych Edit Edytuj Seatch For Streams Szukaj strumieni Configure Konfiguruj Digitally Imported Service name Digitally Imported Import Streams Importuj strumienie XML Streams (*.xml *.xml.gz *.cantata) Strumienie XML (*.xml *.xml.gz *.cantata) Export Streams Eksportuj strumienie XML Streams (*.xml.gz) Strumienie XML (*.xml.gz) Failed to create '%1'! Tworzenie '%1' nie powiodło się! Stream '%1' already exists! Strumień '%1' już istnieje! A stream named '%1' already exists! Strumień o nazwie '%1' już istnieje! Bookmark added Dodano zakładkę Already bookmarked Już w zakładkach Already in favorites Już w ulubionych Reload '%1' streams? Wczytać ponownie '%1' strumieni? Are you sure you wish to remove bookmark to '%1'? Czy na pewno usunąć zakładkę do '%1'? Are you sure you wish to remove all '%1' bookmarks? Czy na pewno usunąć '%1' zakładek? Are you sure you wish to remove the %1 selected streams? Czy jesteś pewien, że chcesz usunąć %1 zaznaczonych strumieni? Are you sure you wish to remove '%1'? Czy na pewno usunąć '%1'? Added '%1'' to favorites Dodano "%1" do ulubionych StreamsModel Bookmarks Zakładki TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Ulubione Bookmark Category Kategoria zakładek Add Stream To Favorites Dodaj strumień do ulubionych Configure Digitally Imported Skonfiguruj Digitally Imported Reload Odśwież Streams Strumienie Radio stations Stacje radiowe Not Loaded Nie załadowano Loading... Ładowanie... %n Entry(s) %n Element %n Elementy %n Elementów StreamsSettings Use the checkboxes below to configure the list of active providers. Użyj poniższych pól wyboru aby skonfigurować listę aktywnych dostawców. Built-in categories are shown in italic, and these cannot be removed. Wbudowane kategorie wyświetlane są kursywą i nie mogą zostać usunięte. Configure Streams Konfiguruj strumienie From File... Z pliku... Download... Pobierz... Configure Provider Konfiguruj dostawcę Install Instaluj Remove Usuń Install Streams Instaluj strumienie Cantata Streams (*.streams) Strumienie Cantata (*.streams) A category named '%1' already exists! Overwrite? Kategoria o nazwie '%1' już istnieje! Nadpisać? Failed top open package file. Otwieranie pliku pakietu nie powiodło się. Invalid file format! Niepoprawny format pliku! Failed to create stream category folder! Tworzenie katalogu kategorii strumieni nie powiodło się! Failed to save stream list! Zapisanie listy strumieni nie powiodło się! Are you sure you wish to remove '%1'? Czy na pewno usunąć '%1'? Failed to remove streams folder! Usunięcie katalogu strumieni nie powiodło się! SyncCollectionWidget Search Szukaj Check Items Zaznacz elementy Uncheck Items Odznacz elementy SyncDialog Library: Biblioteka: Device: Urządzenie: Loading all songs from library, please wait... Ładowanie wszystkich utworów z biblioteki, proszę czekać... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. <code>Biblioteka</code> wyświetla tylko utwory, które zawarte są w bibliotece ale nie na urządzeniu. Analogicznie <code>Urządzenie</code> wyświetla utwory, które są na urządzeniu.<br/>Należy zaznaczyć utwory z <code>Biblioteki</code>, które mają zostać skopiowane na <code>Urządzenie</code>, oraz zaznaczyć utwory na <code>Urządzeniu</code>, które mają zostać skopiowane do <code>Biblioteki</code>. następnie należy wcisnąć przycisk <code>Synchronizuj</code>. Synchronize Synchronizuj Device and library are in sync. Urządzenie i biblioteka są zsynchronizowane. Loading all songs from library, please wait...%1%... Ładowanie utworów z bilioteki, proszę czekać...%1%... Local Music Library Properties Ustawienia lokalnej biblioteki muzyki Device has been removed! Urządzenie zostało usunięte! Device has been changed? Urządzenie zostało zmienione? Device is busy? Urządzenie jest zajęte? TableView Stretch Columns To Fit Window Rozciągnij kolumny aby dopasować do okna Left Z lewej Center Środek Right Z prawej Alignment Wyrównanie TagEditor Track: Utwór: Title: Tytuł: Artist: Artysta: Album artist: Artysta albumu: Composer: Kompozytor: Album: Album: Track number: Numer utworu: Disc number: Numer płyty: Genre: Gatunek: Year: Rok: Rating: Ocena: <i>(Various)</i> <i>(Various)</i> Comment: Komentarz: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Podanie kilku gatunków powinno być oddzielane przecinkiem (np. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Oceny przechowywane są w zewnętrznej bazie danych i <b>nie</b> w pliku utworu. Tags Tagi Tools Narzędzia Apply "Various Artists" Workaround Zastosuj obejście dla "Various Artists" Revert "Various Artists" Workaround Cofnij obejście dla "Various Artists" Set 'Album Artist' from 'Artist' Ustaw tag 'Artysta Albumu' na podstawie tagu 'Artysta' Capitalize Duża pierwsza litera Adjust Track Numbers Dostosuj numery utworów Read Ratings from File Wczytaj oceny z pliku Write Ratings to File Zapisz oceny do pliku All tracks Wszystkie utwory Apply "Various Artists" workaround to <b>all</b> tracks? Czy zastosować obejście dla "Various Artists" we <b>wszystkich</b> utworach? Apply "Various Artists" workaround? Zastosować obejście dla "Various Artists"? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Spowoduje to ustawienie tagów 'ArtystaAlbumu' oraz 'Artysta' na wartość "Various Artists", zaś tagu 'Tytuł' na "ArtystaUtworu - TytułUtworu"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Czy cofnąć obejście dla "Various Artists" we <b>wszystkich</b> utworach? Revert "Various Artists" workaround Cofnij obejście dla "Various Artists" <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>W przypadku, gdy tag 'ArtystaAlbumu' jest taki sam jak 'Artysta' oraz tag 'Tytuł' jest formatu "ArtystaUtworu - TytułUtworu", to tag 'Artysta' zostanie pobrany z tagu 'Tytuł' a sam 'Tytuł' zostanie przywrócony do wartości 'TytułUtworu'. Dla przykładu: <br/><br/> Jeśli 'Tytuł' to "Wibble - Wobble", wtedy 'Artysta' zostanie ustawiony na "Wibble" natomiast 'Title' będzie miał wartość "Wobble"</i> Revert Odwróć Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Czy ustawić tag 'Artystę Albumu' na podstawie tagu 'Artysta' (jeśli 'Artysta Albumu' jest pusty) dla <b>wszystkich</b> utworów? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Czy ustawić tag 'Artystę Albumu' na podstawie tagu 'Artysta' (jeśli 'Artysta Albumu' jest pusty)? Album Artist from Artist Artysta Albumu na podstawie Artysty Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Ustawić dużą pierwszą literę pól tekstowych (np. 'Tytuł', 'Artysta', itp.) <b>wszystkich</b> utworów? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Ustawić dużą pierwszą literę pól tekstowych (np. 'Tytuł', 'Artysta', itp.)? Adjust the value of each track number by: Zwiększ wartość numeru każdego utworu o: Adjust track number by: Zmień numery utworów o: Read ratings for all tracks from the music files? Odczytać oceny wszystkich utworów z plików muzycznych? Read rating from music file? Odczytać ocenę z pliku muzycznego? Ratings Oceny Read Ratings Odczytaj oceny Read Rating Odczytaj ocenę Read, and updated, ratings from the following tracks: Odczytaj i uaktualnij oceny z następujących utworów: Not all Song ratings have been read from MPD! Nie wszystkie oceny utworów zostały odczytane z MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Oceny utworów nie są przechowywane w plikach utworów, ale w bazie danych 'sticker' serwera MPD. W celu zapisania ocen do plików, Cantata musi odczytać je najpierw z MPD. Song rating has not been read from MPD! Oceny utworów nie zostały odczytane z MPD! Write ratings for all tracks to the music files? Zapisać oceny dla wszystkich utworów do plików muzycznych? Write rating to music file? Zapisać ocenę do pliku muzycznego? Write Ratings Zapisz oceny Write Rating Zapisz ocenę Failed to write ratings of the following tracks: Zapisywanie ocen nie powiodło się dla następujących utworów: Failed to write rating to music file! Zapisywanie oceny do pliku muzycznego nie powiodło się! All tracks [modified] Wszystkie utwory [zmodyfikowano] %1 [modified] %1 [zmodyfikowano] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (Uszkodzone tagi?) Failed to update the tags of the following tracks: Uaktualnianie tagów następujących utworów nie powiodło się: Would you also like to rename your song files, so as to match your tags? Czy również zmienić nazwy plików tak, aby pasowały do tagów? Rename Files Zmień nazwy plików Rename Zmień nazwę Device has been removed! Urządzenie zostało usunięte! Device is not connected. Urządzenie nie jest podłączone. Device is busy? Urządzenie jest zajęte? TagSpinBox (Various) (różne) ThinSplitter Reset Spacing Resetuj odstępy TitleWidget Click to go back Kliknij aby pójść wstecz Add All To Play Queue Dodaj wszystko do kolejki odtwarzania Add All And Replace Play Queue Dodaj wszystko i zamień kolejkę odtwarzania ToggleList Available: Dostępny: Selected: Wybrany: TrackOrganiser Filenames Nazwy plików Filename scheme: Schemat nazwy pliku: VFAT safe Bezpieczne dla VFAT Use only ASCII characters Używaj jedynie znaków ASCII Replace spaces with underscores Zastąp spacje znakami podkreślenia Append 'The' to artist names Dodaj 'The' to nazw artystów Original Name Obecna nazwa New Name Nowa nazwa Ratings will be lost if a file is renamed. Oceny zostaną utracone jeśli nazwa pliku zostanie zmieniona. Organize Files Organizuj pliki Rename Zmień nazwę Remove From List Usuń z listy Abort renaming of files? Przerwać zmianę nazw plików? Abort Przerwij Source file does not exist! Plik źródłowy nie istnieje! Skip Pomiń Auto Skip Automatycznie pomiń Destination file already exists! Plik docelowy już istnieje! Failed to create destination folder! Tworzenie katalogu docelowego nie powiodło się! Failed to rename '%1' to '%2' Zmiana nazwy pliku z '%1' na '%2' nie powiodła się Remove the selected tracks from the list? Usunąć zaznaczone utwory z listy? Remove Tracks Usuń utwory Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Oceny utworów nie są przechowywane w plikach utworów, ale w bazie danych 'sticker' serwera MPD. Jeśli nazwa pliku zostanie zmieniona (albo nazwa folderu, który zawiera ten plik), to ocena związana z utworem zostanie utracona. Device has been removed! Urządzenie zostało usunięte! Device is not connected. Urządzenie nie jest podłączone. Device is busy? Urządzenie jest zajęte? TrayItem Cantata Cantata Now playing Teraz odtwarzane UltimateLyricsProvider (Polish Translations) (Polskie tłumaczenia) (Portuguese Translations) (Portugalskie tłumaczenia) UmsDevice Not Scanned Nie przeskanowano Not Connected Nie połączono %1 free %1 wolnego miejsca ValueSlider (recommended) (zalecane) View Cancel Anuluj VolumeSlider Mute Wycisz Unmute Wyłącz wyciszenie Volume %1% (Muted) Głośność %1% (wyciszono) Volume %1% Głośność %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | artysta|zespół|piosenkarz|wokalista|muzyk album|score|soundtrack Search pattern for an album, separated by | album|ścieżka dźwiękowa|kompozycja WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Proszę wybrać języki Wikipedii, które mają być użyte przy wyszukiwaniu informacji o artyście i albumie. Reload Odśwież cantata-2.2.0/translations/cantata_ru.ts000066400000000000000000027067431316350454000203640ustar00rootroot00000000000000 Refresh Album Information Обновить сведения об альбоме Album Альбом Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Дорожки Refresh Artist Information Обновить сведения об исполнителе Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) Исполнитель Albums Альбомы Web Links Ссылки Similar Artists Похожие исполнители Lyrics Providers Источники текстов Wikipedia Languages Языки Wikipedia Other Другое Reset Spacing Перенастроить отступ &Artist &Артист Al&bum &Альбом &Track &Трек Read more on last.fm Больше информации см. на last.fm If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. В случае если Cantata не нашла текста песни, или нашла неправильный вариант, введите здесь новые ключевые слова для поиска. Например, текущая песня может быть ковер-версией, в таком случае может помочь поиск слов от изначального исполнителя . Если этот поиск не принесёт новых результатов, слова песни будут привязаны к названию оригинала и к изначальному артисту, как это показано в Cantata. Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) Название: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) Исполнитель: Search For Lyrics Искать слова песни Choose the websites you want to use when searching for lyrics. Сайты для поиска текстов песен Song Information Информация о песне Images (*.png *.jpg) Изображения (*.png *.jpg) 10px pixels 10пикс %1% value% %1% %1 px pixels %1 пикс Lyrics Слова песни Information Инфо Metadata Метаданные Scroll Lyrics Прокрутка слов песни Refresh Lyrics Обновить слова песни Edit Lyrics Изменить слова песни Delete Lyrics File Удалить файл со словами песни Refresh Track Information Обновление информации о треках Cancel Отмена Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) Трек Reload lyrics? Reload from disk, or delete disk copy and download? Загрузить слова песни повторно? Перезагрузить с диска или удалить копию с диска и загрузить заново? Reload Обновить Reload From Disk Загрузить с диска Download Скачать Current playing song has changed, still perform search? Текущая песня сменилась, произвести поиск всё равно? Song Changed Песня изменена Perform Search Произвести поиск Delete lyrics file? Удалить файл со словами песни? Delete File Удалить файл Album artist Исполнитель альбома Composer i18n: file: devices/filenameschemedialog.ui:102 i18n: ectx: property (text), widget (QPushButton, composer) Автор музыки Lyricist Поэт-песенник Conductor Дирижер Remixer Ремиксер Subtitle Субтитры Track number Номер трека Disc number Номер диска Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) Жанр Date Дата Original date Исходная дата Comment Комментарий Copyright Copyright Label Лейбл Catalogue number Номер по каталогу Title sort Сортировать название Artist sort Сортировать артист Album artist sort Сортировать исполнитель альбома Album sort Сортировать альбом Encoded by Закодировано с помощью Encoder Кодировщик Mood Настроение Media Медиа Bitrate Битрейт Sample rate Частота дискретизации Channels Каналы Tagging time Пометка времени Performer (%1) Исполнитель (%1) %1 kb/s %1 кбит/с %1 Hz %1 Гц Bits Бит Performer Исполнитель Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) Год Filename Имя файла Fetching lyrics via %1 Загрузка слов песни с %1 (Polish Translations) (Переводы на польский) (Portuguese Translations) (Переводы на португальский) Track listing Список дорожек Read more on wikipedia Доп. информация в Википедии Open in browser Открыть в браузере artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | исполнитель|группа|певец|вокалист|музыкант album|score|soundtrack Search pattern for an album, separated by | альбом|рейтинг|саундтрек Choose the wikipedia languages you want to use when searching for artist and album information. Выбрать языки Википедии, на которых будет искаться информация об исполнителе и об альбоме. Cantata is playing a track Cantata проигрывает трек <b>INVALID</b> <b>НЕДОПУСТИМО</b> <i>(When different)</i> <i>(если отличается)</i> Artists:%1, Albums:%2, Songs:%3 Исполнители:%1, альбомы:%2, песни:%3 %1 free %1 свободно Local Music Library Локальная музыкальная библиотека Audio CD Аудио-CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. На устройстве назначения недостаточно свободного места. Выбранные песни занимают %1, но доступно только %2. Для успешного копирования на устройство, композиции необходимо перекодировать с получением файлов меньшего размера. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. В месте назначения недостаточно места. Выбранные песни занимают %1, но доступно всего %2. Copy Songs To Device Копировать песни на устройство Copy Songs Копировать песни Delete Songs Удалить песни You have not configured the destination device. Continue with the default settings? Устройство назначения не было настроено. Продолжать с настройками по умолчанию? Not Configured Не настроено Use Defaults Использовать параметры по умолчанию You have not configured the source device. Continue with the default settings? Исходное устройство не было настроено. Продолжать с настройками по умолчанию? Are you sure you wish to stop? Точно остановить? Stop Остановить Device has been removed! Устройство было удалено. Device is not connected! Устройство не подключено. Device is busy? Устройство занято? Device has been changed? Устройство было изменено? Clearing unused folders Очищение неиспользуемых папок Calculate ReplayGain for ripped tracks? Рассчитать ReplayGain для скопированных с диска треков? ReplayGain ReplayGain Calculate Рассчитать The destination filename already exists! Имя конечного файла уже существует! Song already exists! Песня уже существует! Song does not exist! Песня не существует! Failed to create destination folder!<br/>Please check you have sufficient permissions. Не удалось создать папку!<br/>Пожалуйста, проверьте достаточно ли у вас прав. Source file no longer exists? Исходного файла больше не существует? Failed to copy. Не удается скопировать. Failed to delete. Не удается удалить. Not connected to device. Не подключен к устройству. Selected codec is not available. Выбранный кодек не доступен. Transcoding failed. Ошибка транскодирования. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Сбой создания временного файла.<br/>(Требуется для перекодирования устройств MTP.) Failed to read source file. Не удается прочитать исходный файл. Failed to write to destination file. Не удается записать файл назначения. No space left on device. Нет места на устройстве. Failed to update metadata. Не удалось обновить метаданные. Failed to download track. Не удалось загрузить трек. Failed to lock device. Не удалось заблокировать устройство. Local Music Library Properties Свойства локальной музыкальной библиотеки Error Ошибка Skip Пропустить Auto Skip Авто-пропуск Retry Повторить Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) Альбом: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) Трек: Source file: Исходный файл: Destination file: Конечный файл: File: Файл: Calculating... Вычисление... %1 (Estimated) time (Estimated) %1 (примерно) Time remaining: Оставшееся время: Saving cache Сохранение кеша Apply "Various Artists" Workaround Применить обходное решение «несколько исполнителей» Revert "Various Artists" Workaround Откатить обходное решение «несколько исполнителей» Capitalize Перевести в верхний режим Adjust Track Numbers Настроить номера треков Tools Инструменты Apply "Various Artists" workaround? Применить обходное решение «несколько исполнителей»? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Параметры «Исполнитель альбома» и «Исполнитель» будут установлены на значение «Несколько исполнителей», а «Название» — на "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Откатить обходное решение «несколько исполнителей»? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Там, где значение «Исполнитель альбома» совпадает с «Исполнителем», а «Название» имеет формат "TrackArtist - TrackTitle", значение «Исполнитель» будет взято из «Названия», а само «Название» будет только названием. Например, если «Название» будет иметь значение "Wibble - Wobble", то «Исполнитель» будет указан как "Wibble", а «Название» — "Wobble" Revert Откатить Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Должна ли быть заглавной первая буква тегов «название», «исполнитель», «исполнитель альбома» и «альбом»? Adjust track number by: Настроить номера треков по: Reading disc Чтение диска CDDB CDDB MusicBrainz MusicBrainz Data Track Трек с данными Failed to open CD device Сбой открытия устройства CD Track %1 Трек %1 Failed to create CDDB connection Сбой создания подключения CDDB No matches found in CDDB Совпадений в CDDB не найдено. CDDB error: %1 Ошибка CDDB: %1 Multiple matches were found. Please choose the relevant one from below: Найдено несколько совпадений. Выберите релевантный ниже: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) Название Disc Selection Выбор диска %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 диск %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) Updating (%1)... Обновление (%1)... Updating (%1%)... Обновление (%1%)... Device Properties Свойства устройства Don't copy covers Не копировать обложки Embed cover within each file Встроенная обложка в каждом файле No maximum size Нет максимального размера 400 pixels 400 пикс. 300 pixels 300 пикс. 200 pixels 200 пикс. 100 pixels 100 пикс. <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Во время копирования треков на устройство, и при значении «Исполнитель альбома»установленном как «Несколько исполнителей», Cantata установит значение тега «Исполнитель» для всех треков на значение «Несколько исполнителей», а тег трека «Название» на 'TrackArtist - TrackTitle'.<hr/> Во время копирования треков с устройства Cantata проверит настройки «Исполнитель альбома» и«Исполнитель», которые должны иметь значение «Несколько исполнителей», и в этом случае попробует получить правильные данные исполнителя из тега «Название» и удалить имя исполнителя из тега «Название»</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Если активировать этот параметр, Cantata создаст кеш музыкальной библиотекиустройства, что ускорит последующие сканирования библиотеки (файл кеша будет использоваться вместо чтения тегов каждого файла)<hr/><b>ВНИМАНИЕ:</b> при использовании другого приложения для обновления библиотеки устройства, данные этого кеша станут неактуальными. Для их коррекции нажмите на значок «обновить» в списке устройств. Файл кеша буде удалён, а содержимое устройства будет просканировано заново.</p> Do not transcode Не перекодировать Transcode to %1 Перекодировать в %1 %1 (%2 free) name (size free) %1 (%2 свободно) Copy To Library Скопировать в библиотеку Forget Device Исключить устройство Add Device Добавить устройство Lookup album and track details? Произвести поиск подробной информациидля альбома и трека? Refresh Обновить Via CDDB Через CDDB Via MusicBrainz Через MusicBrainz Which type of refresh do you wish to perform? Какой способ обновления выполнить? Partial - Only new songs are scanned (quick) Частичное — сканируются только новые композиции (быстро) Full - All songs are rescanned (slow) Полное — повторное сканирование всех композиций (долго) Partial Частично Full Полностью Are you sure you wish to delete the selected songs? This cannot be undone. Точно удалить выбранные песни? Это действие нельзя будет отменить. Are you sure you wish to forget '%1'? Точно исключить «%1»? Are you sure you wish to eject Audio CD '%1 - %2'? Точно извлечь Аудио CD «%1 - %2»? Eject Извлечь Are you sure you wish to disconnect '%1'? Точно отключить «%1»? Disconnect Отключить Please close other dialogs first. Сначала необходимо закрыть другие диалоги. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) — это запатентованный кодек для цифрового аудио, использующий сжатие с потерями. <br>Как правило, AAC предоставляет лучшее качество звука, чем MP3 при аналогичном битрейте. Это разумный выбор для iPod и некоторых других портативных плееров. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодировщик <b>AAC</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека.<br>Битрейт, равный <b>150кбит/с</b>, — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>120 кбит/с</b> могут приводить к неудовлетворительному качеству. Значения больше <b>200 кбит/с</b> обычно излишни. Expected average bitrate for variable bitrate encoding Ожидаемое среднее значение битрейта для кодирования переменного битрейта. Smaller file Меньший размер файла Better sound quality Лучшее качество звука <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) — это запатентованный кодек цифрового аудио, использующий сжатие данных с потерями.<br>Несмотря на свои недостатки, он является общепризнанным форматом для любительского хранения аудио, и широко поддерживается портативными музыкальными плеерами. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодер <b>MP3</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/MP3#VBR>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека. <br>Битрейт, равный <b>160 кбит/с</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>120 кбит/с</b> могут приводить к неудовлетворительному качеству. Значения больше <b>205 кбит/с</b> обычно излишни. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> — это открытый, не требующий финансовых отчислений кодек для сжатия аудио с потерями.<br>Файлы этом формате имеют меньший размер, чем файлы MP3, при аналогичномили более высоком качестве.Ogg Vorbis — отличный и универсальный выбор, особенно для поддерживающих его портативных плееров. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодировщик <b>Vorbis</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_details>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br> Кодер Vorbis имеет параметр качества «-q», принимающий значения от 1 до 10, который задаёт ожидаемый уровень качества звука. Битрейт, устанавливаемый этим параметром — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека. В более новых и более эффективных версиях Vorbis каждому значению качества соответствует меньший битрейт.<br>Параметр качества, равный <b>-q5</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>-q3</b> могут приводить к неудовлетворительному качеству. Значения больше <b>-q8</b> обычно излишни. Quality rating Рейтинг по качеству Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> является патентно-бесплатным цифровым аудио кодеком, используемым форму сжатия с потерями данных. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодер <b>Opus</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Variable_bitrate>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение среднего битрейта кодируемого трека. <br>Битрейт, равный <b>128 кбит/с</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>100 кбит/с</b> могут приводить к неудовлетворительному качеству музыки. Значения больше <b>256 кбит/с</b> обычно излишни. Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) — аудио-кодек для сжатия цифрового аудио без потерь.<br>Рекомендуется только для музыкальных плееров компании Apple и плееров, не поддерживающих формат FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC) — открытый и не требующий финансовых отчислений кодек для сжатия цифрового аудио без потерь.<br>FLAC — прекрасный выбор для тех, кто хочет хранить музыку, не экономя на качестве. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Уровень сжатия</a> — это целое число со значением от 0 до 8, представляющее оптимальное соотношение между размером файла и скоростью сжатия во время кодирования в формат <b>FLAC</b>.<br/> Уровень сжатия, равный <b>0</b>, даёт самое короткое время сжатия, но создаёт относительно большой файл.<br/>С другой стороны, уровень сжатия <b>8</b> потребует много времени на процесс сжатия, но размер созданного файла будет самым маленьким.<br/>Поскольку, по определению, FLAC — это кодек со сжатием без потерь, то качество аудио на выходе не изменится, вне зависимости от уровня сжатия.<br/>Кроме того, уровни сжатия более <b>5</b> очень сильно увеличивают время сжатия, но лишь незначительно уменьшают размер файла по сравнению с более низкими уровнями сжатия, и поэтому не рекомендуются. Compression level Уровень сжатия Faster compression Быстрое сжатие Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ru.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) — это проприетарный кодек, разработанный компанией Microsoft для сжатия аудио (с потерями).<br>Рекомендуется только для портативных аудиопроигрывателей, не поддерживающих Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Из-за ограничений проприетарного формата <b>WMA</b> и трудностей реверс-инжиниринга проприетарного кодировщика, кодировщик WMA, используемый программой Cantata, имеет настройку <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>постоянный битрейт (CBR)</a>.<br>Поэтому битрейт, устанавливаемый этим ползунком — это довольно точное оценочное значение битрейта кодируемого трека. Битрейт, равный <br><b>136кбит/с</b> — хороший выбор для музыки, прослушиваемой в портативном плеере. <br/>Любые значения меньше <b> 112кбит/с</b> приведут к неудовлетворительному качеству музыки, а значения <b>182кбит/с</b> просто излишни. Filename Scheme Схема имён файлов Various Artists Example album artist Несколько исполнителей Wibble Example artist Wibble Vivaldi Example composer Чайковский Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Дэнс The following variables will be replaced with their corresponding meaning for each track name. Следующие переменные будут заменены соответствующими значениями для каждого названия трека. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Переменная</em></th><th><em>Кнопка</em></th><th><em>Описание</em></th></tr> Updating... Обновление... Reading cache Чтение кеша %1 %2% Message percent %1 %2% Connecting to device... Подключение к устройству... No devices found Устройства не обнаружены Connected to device Подключено к устройству Disconnected from device Отключено от устройства Updating folders... Обновление папок... Updating files... Обновление файлов... Updating tracks... Обновление треков... Not Connected Не подключено %1 (Disc %2) %1 (диск %2) No matches found in MusicBrainz Совпадений в MusicBrainz не найдено. Connection Соединение Music Library Музыкальная библиотека A remote device named '%1' already exists! Please choose a different name. Удалённое устройство с названием «%1» уже существует. Выберите другое название. Samba Share Общий ресурс Samba Samba Share (Auto-discover host and port) Общий ресурс Samba (авто-определение порта и хоста) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Локально смонтированная папка Available Доступно Not Available Недоступно Failed to resolve connection details for %1 Сбой определения подробностей подключения для %1 Connecting... Соединение... Password prompting does not work when cantata is started from the commandline. Запрос паролей не работает, если cantata была запущена из командной строки. No suitable ssh-askpass application installed! This is required for entering passwords. Требуемое приложение ssh-askpass не установлено. Оно необходимо для ввода паролей. Mount point ("%1") is not empty! Точка монтирования («%1») не пуста. "sshfs" is not installed! «sshfs» не установлен Disconnecting... Отключение... "fusermount" is not installed! «fusermount» не установлен Failed to connect to "%1" Сбой подключения к «%1» Failed to disconnect from "%1" Сбой отключения от «%1» Capacity Unknown Ёмкость неизвестна Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) Поиск Check Items Отметить элементы Uncheck Items Сбросить отмеченные элементы Synchronize Синхронизировать Device and library are in sync. Устройство и библиотека синхронизированы Not Scanned Не просканировано (recommended) (рекомендуется) Empty filename. Пустое имя файла. Invalid filename. (%1) Недопустимое имя файла. (%1) Failed to save %1. Не удалось сохранить %1 Failed to delete rules file. (%1) Сбой удаления файла правил (%1) Invalid command. (%1) Недопустимая команда. (%1) Could not remove active rules link. Не удалось удалить ссылку на активные правила. Active rules is not a link. Активные правила не являются ссылкой. Could not create active rules link. Не удалось создать ссылку на активные правила. Rules file, %1, does not exist. Файл правил %1 не существует. Incorrect arguments supplied. Неверные аргументы. Unknown method called. Неизвестный называемый метод. Unknown error Неизвестная ошибка Start Dynamic Playlist Запустить динамический плейлист Stop Dynamic Mode Остановить динамический режим Dynamic Playlists Динамический Плейлист - Rating: %1..%2 - Рейтинг: %1..%2 You need to install "perl" on your system in order for Cantata's dynamic mode to function. Для работы динамического режима необходимо установить «perl». Failed to locate rules file - %1 Сбой нахождения файла правил — %1 Failed to remove previous rules file - %1 Сбой удаления предыдущего файла правил — %1 Failed to install rules file - %1 -> %2 Сбой установки файла правил — %1 -> %2 Dynamizer has been terminated. Dynamizer был завершён Saving rule Сохранение правила Deleting rule Удаление правила Awaiting response for previous command. (%1) Ожидание ответа на предыдущую команду (%1) Failed to save %1. (%2) Не удалось сохранить %1. (%2) Failed to control dynamizer state. (%1) Сбой проверки состояния dynamizer. (%1) Failed to set the current dynamic rules. (%1) Сбой установки текущих динамических правил (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) Добавить Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) Изменить Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) Удалить Remote dynamizer is not running. Удалённый dynamizer не запущен Are you sure you wish to remove the selected rules? This cannot be undone. Точно удалить выбранные правила? Это действие нельзя отменить. Remove Dynamic Rules Удалить динамические правила Dynamic Rule Динамическое правило <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ОШИБКА</b>: значение «с года» должно быть меньше значения «до года»</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ОШИБКА:</b> слишком большой диапазон даты (максимум равен %1 годам)</i> SimilarArtists SimilarArtists AlbumArtist AlbumArtist Include Включить Exclude Исключить (Exact) (Точное) Dynamic Rules Динамические правила None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Нет About dynamic rules О динамических правилах Failed to save %1 Не удалось сохранить %1 A set of rules named '%1' already exists! Overwrite? Набор правил с названием «%1» уже существует. Перезаписать? Overwrite Rules Перезаписать правила Saving %1 Сохранение %1 Deleting... Удаление... Name Имя Item Count Количество элементов Space Used Используемое пространство Total space used: %1 Всего использовано места на диске: %1 Covers Обложки Scaled Covers Масштабированные обложки Backdrops Художественное оформление Artist Information Сведения об исполнителе Album Information Сведения об альбоме Track Information Сведения об треке Stream Listings Каталог потоков Podcast Directories Каталоги подкастов Scrobble Tracks Скробблить треки Delete All Удалить всё Delete all '%1' items? Удалить все «%1» объектов? Delete Cache Items Удалить элементы кеша Delete items from all selected categories? Удалить элементы из всех выбранных категорий? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Текущая обложка CoverArt Archive CoverArt Archive Image Изображение Downloading... Загружается... Image (%1 x %2 %3%) Image (width x height zoom%) Изображение (%1 x %2 %3%) An image already exists for this artist, and the file is not writeable. Для этого артиста изображение уже существует, и файл не доступен для записи. A cover already exists for this album, and the file is not writeable. Для этого альбома обложка уже существует, и файл не доступен для записи. '%1' Artist Image '%1' Изображение исполнителя '%1 - %2' Album Cover 'Artist - Album' Album Cover Обложка альбома «%1 - %2» Failed to set cover! Could not download to temporary file! Сбой установки обложки. Загрузка во временный файл не удалась. Failed to download image! Сбой загрузки изображения. Load Local Cover Загрузить локальную обложку File is already in list! Файл уже в списке. Failed to read image! Сбой чтения изображения Display Экран Failed to set cover! Could not make copy! Не удалось настроить обложку. Сбой создания копии. Failed to set cover! Could not backup original! Не удалось настроить обложку. Сбой создания резервной копи оригинала. Failed to set cover! Could not copy file to '%1'! Не удалось настроить обложку. Сбой копирования файла в «%1». Searching... Идёт поиск... Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) Имя: Open In File Manager Открыть в файловом менеджере Connection Established Соединение установлено Connection Failed Сбой подключения Grouped Albums Отсортированные альбомы Table Таблица Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) Проиграть очередь Folders Папки Playlists Плейлисты Devices - UMS, MTP (e.g. Android), and AudioCDs Устройства - UMS, MTP (например Android) и AudioCDs Search (via MPD) Поиск (через MPD) Info - Current song information (artist, album, and lyrics) Информация - текущая информация о песне (исполнитель, альбом и текст) Large Большой Small Маленький Tab-bar Панель вкладок Left Слева Right Справа Top Сверху Bottom Снизу Notifications Уведомления System default Системный Album, Artist, Year Альбом, Артист, Год Album, Year, Artist Альбом, Год, Артист Artist, Album, Year Артист, Альбом, Год Artist, Year, Album Артист, Год, Альбом Year, Album, Artist Год, Альбом, Артист Year, Artist, Album Год, Артист, Альбом Configure Cantata... Настроить Cantata... Preferences Настройки Quit Выход About Cantata... Qt-only О программе Cantata... Show Window Показать окно Server information... Сведения о сервере... Refresh Database Обновить базу данных Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) Подключиться Collection Коллекция Outputs Выводы Stop After Track Остановить после трека Add To Stored Playlist Добавить в сохраняемый плейлист Add Stream URL Добавить URL потока Clear Очистить Center On Current Track Центр по текущей дорожке Expanded Interface Расширенный интерфейс Show Current Song Information Показать инфо текущей песни Full Screen Полный экран Random Случайно Repeat Повтор Single Одиночный When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. «Одиночный» режим: воспроизведение останавливается после текущей песни, или, в случае активного режима «Повтор», текущая песня повторяется Consume Поглощение When consume is activated, a song is removed from the play queue after it has been played. Режим «Поглощение»: песня удаляется из очереди после того, как была проиграна Find in Play Queue Найти в очереди на воспроизведение Play Stream Воспроизводить поток Locate In Library Найти в библиотеке Expand All Развернуть всё Collapse All Свернуть всё Devices Устройства Info Инфо Show Menubar Показать меню &Music &Музыка &Edit &Редактировать &View &Вид &Queue &Очередь &Settings &Настройки &Help &Помощь Set Rating Указать рейтинг No Rating Без рейтинга Failed to locate any songs matching the dynamic playlist rules. Не удалось найти песен, подходящих под правила динамического плейлиста. Connecting to %1 Подключение к %1 Refresh MPD Database? Обновить базу данных MPD? About Cantata Qt-only О программе Cantata Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Qt-only Художественное оформление контекстного вида предоставлено сайтом <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Qt-only Метаданные контекстного вида предоставлены сайтами <a href="http://www.wikipedia.org">Wikipedia</a> и <a href="http://www.last.fm">Last.fm</a>. Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Вы можете выкладывать ваши творческие работы на темы музыкального фан-арта на сайт <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. В настоящее время идёт загрузка подкаста. Выключение прекратит загрузку. Abort download and quit Прервать загрузку и выключить Enabled: %1 Включено: %1 Disabled: %1 Отключено: %1 Server Information Сведения о сервере <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Сервер</b></td></tr><tr><td align="right">Протокол:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Аптайм:&nbsp;</td><td>%4</td></tr><tr><td align="right">Играя:&nbsp;</td><td>%5</td></tr><tr><td align="right">Обработчики:&nbsp;</td><td>%6</td></tr><tr><td align="right">Тэги:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>База данных</b></td></tr><tr><td align="right">Артисты:&nbsp;</td><td>%1</td></tr><tr><td align="right">Альбомы:&nbsp;</td><td>%2</td></tr><tr><td align="right">Песни:&nbsp;</td><td>%3</td></tr><tr><td align="right">Продолжительность:&nbsp;</td><td>%4</td></tr><tr><td align="right">Обновление:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD выдал следующую ошибку: %1 Cantata Cantata Playback stopped Воспроизведение остановлено Remove all songs from play queue? Удалить все песни из очереди? Priority Приоритет Enter priority (0..255): Введите приоритет (0..255): Playlist Name Имя плейлиста Enter a name for the playlist: Введите имя плейлиста: '%1' is used to store favorite streams, please choose another name. «%1» используется для хранения «любимых» потоков, выберите другое название. A playlist named '%1' already exists! Add to that playlist? Список воспроизведения с названием «%1» уже существует. Добавить к этому списку? Existing Playlist Существующие плейлисты Auto Авто <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Подключено к %1<br/>Элементы ниже относятся к текущей подключённой коллекции MPD.</i> <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> i18n: file: gui/playbacksettings.ui:94 i18n: ectx: property (text), widget (QLabel, messageLabel) <i>Не подключено.<br/>Элементы ниже нельзя изменять, т.к. Cantata не подключена к MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gain — это стандарт, предложенный в 2001 году, для нормализации воспринимаемой громкости компьютерных аудио-форматов, таких, как MP3 и Ogg Vorbis. Он работает на базе трека/альбома, и на данный момент поддерживается всё большим количеством плееров.<br/><br/>Следующие параметры ReplayGain можно использовать:<ul><li><i>Нет</i>: ReplayGain не применяется.</li><li><i>Трек</i>: Громкость будет настроена с использованием тегов ReplayGain трека.</li><li><i>Альбом</i>: Громкость будет настроена с использованием тегов ReplayGain альбома.</li><li><i>Автоматически</i>: в случае, если включён режим случайного воспроизведения, громкость будет настроена с использованием тегов ReplayGain трека, в противном случае используются теги альбома.</li></ul> Rename Переименовывать Remove Duplicates Удаление дубликатов Are you sure you wish to remove the selected playlists? This cannot be undone. Точно удалить выбранный список воспроизведения? Это действие нельзя отменить. Remove Playlists Удалить плейлист A playlist named '%1' already exists! Overwrite? Список воспроизведения с названием «%1» уже существует. Перезаписать? Overwrite Playlist Перезаписать плейлист Rename Playlist Переименовать плейлист Enter new name for playlist: Новое имя плейлиста: Cannot add songs from '%1' to '%2' Не может добавить песни из '%1' в '%2' 1 Track %1 трек %1 трека %1 треков %1 Tracks 1 Track (%2) %1 трек (%2) %1 трека (%2) %1 треков (%2) %1 Tracks (%2) 1 Album %1 альбом %1 альбома %1 альбомов %1 Albums 1 Artist %1 исполнитель %1 исполнителя %1 исполнителей %1 Artists 1 Stream 1 поток %1 потока %1 потоков %1 Streams 1 Entry 1 элемент %1 элемента %1 элементов %1 Entries 1 Rule %1 правило %1 правила %1 правил %1 Rules 1 Podcast %1 подкаст %1 подкаста %1 подкастов %1 Podcasts 1 Episode %1 серия %1 серий %1 серий %1 Episodes 1 Update available Доступно 1 обновление Доступно %1 обновления Доступно %1 обновлений %1 Updates available Collection Settings Настройки коллекции Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) Воспроизведение Playback Settings Настройки воспроизведения Interface Интерфейс Interface Settings Настройки интерфейса Scrobbling Скробблинг Scrobbling Settings Параметры скробблинга Audio CD Settings Настройки аудио-CD Proxy Прокси Proxy Settings Qt-only Настройки прокси Shortcuts Qt-only Комбинации клавиш Keyboard Shortcut Settings Qt-only Настройки комбинаций клавиш Cache Кеш Cached Items Кешированные элементы Cantata Preferences Настроить Cantata Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) Настройка Composer: i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) i18n: file: devices/albumdetails.ui:50 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: dynamic/dynamicrule.ui:89 i18n: ectx: property (text), widget (BuddyLabel, composerLabel) i18n: file: tags/tageditor.ui:93 i18n: ectx: property (text), widget (StateLabel, composerLabel) Автор музыки: Performer: Исполнитель: Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) Жанр: Comment: i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) i18n: file: dynamic/dynamicrule.ui:187 i18n: ectx: property (text), widget (BuddyLabel, commentLabel) i18n: file: tags/tageditor.ui:195 i18n: ectx: property (text), widget (StateLabel, commentLabel) Комментарий: Date: Дата: Modified: Изменено: Any: Любые: No tracks found. Треки не найдены. Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) Хост: Which type of collection do you wish to connect to? К какому типу коллекции необходимо подключиться? <i><b>NOTE:</b> %1</i> <i><b>Примечание:</b> %1</i> Add Collection Добавить коллекцию Standard Стандартный Basic Базовый Delete '%1'? Удалить «%1»? Delete Удалить New Collection %1 Новая коллекция %1 Default По умолчанию Previous Track Предыдущий трек Next Track Следующий трек Play/Pause Воспр./Пауза Stop After Current Track Остановить после текущего трека Increase Volume Увеличить громкость Decrease Volume Уменьшить громкость Save As Сохранить как Add With Priority Добавить с приоритетом Set Priority Настроить приоритет Highest Priority (255) Высший приоритет (255) High Priority (200) Высокий приоритет (200) Medium Priority (125) Средний приоритет (125) Low Priority (50) Низкий приоритет (50) Default Priority (0) Приоритет по умолчанию (0) Custom Priority... Пользовательское значение приоритета... Add To Playlist Добавить в плейлист Organize Files Систематизировать файлы Edit Track Information Редактировать информацию о треке Set Image Указать изображение Find Поиск Add To Play Queue Добавить в очередь воспроизведения Now playing Проигрывается Play Играть Pause Пауза Cue Sheet Cue Sheet Playlist Плейлист Configure Device Настройка устройства Refresh Device Обновить устройство Connect Device Подключить устройство Disconnect Device Отключить устройство Edit CD Details Редактировать подробности CD No Devices Attached Нет подключённых устройств Not logged in Вход не выполнен Logged in Вход выполнен No subscriptions Нет подписок You do not have an active subscription У вас нет активных подписок Logged in (expiry:%1) Вход выполнен (истекает: %1) Session expired Время сеанса истекло %1 by %2 Album by Artist %1 исполняет %2 New Playlist... Новый плейлист... Smart Playlist Смарт-плейлист Length Длина Disc Диск Rating Рейтинг Undo Отменить Redo Повторить Shuffle Перемешать Sort By Сортировать по Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) Исполнитель альбома Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) Название трека TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Not Loaded Не загружено Loading... Загрузка... Bookmarks Закладки IceCast IceCast Favorites Избранное Bookmark Category Категория закладки Add Stream To Favorites Добавить поток в Избранное Streams Потоки Unknown Неизвестно "%1" name (host) «%1» "%1" (%2:%3) name (host:port) «%1» (%2:%3) Connection to %1 failed Сбой подключения к %1 Connection to %1 failed - please check your proxy settings Сбой подключения к %1 — проверьте параметры прокси. Connection to %1 failed - incorrect password Сбой подключения к %1 — неправильный пароль Failed to send command to %1 - not connected Сбой передачи команды для %1 — нет подключения Failed to load. Please check user "mpd" has read permission. Сбой загрузки. Проверьте права на чтение для пользователя «mpd». Failed to load. MPD can only play local files if connected via a local socket. Сбой загрузки. MPD может проигрывать локальные файлы только при подключении к локальному сокету. Failed to send command. Disconnected from %1 Сбой передачи команды. Отключено от %1. Failed to rename <b>%1</b> to <b>%2</b> Сбой переименования <b>%1</b> в <b>%2</b> Failed to save <b>%1</b> Сбой сохранения <b>%1</b> You cannot add parts of a cue sheet to a playlist! Нельзя добавлять фрагменты файла cue sheet в плейлист. You cannot add a playlist to another playlist! Нельзя добавлять плейлист к другому плейлисту. Failed to send '%1' to %2. Please check %2 is registered with MPD. Не удалось послать «%1» на %2. Проверьте, зарегистрирован ли %2 в MPD. Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) Отдельные треки Personal Частный Various Artists Несколько исполнителей <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> на <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> исполнителя <b>%2</b> на <b>%3</b> No proxy Без прокси Use the system proxy settings Использовать системные настройки прокси Manual proxy configuration Ручная настройка прокси Jamendo Settings Настройки Jamendo MP3 MP3 Ogg Ogg Streaming format: Формат потока: Streaming Потоковая передача MP3 128k MP3 128k MP3 VBR MP3 VBR WAV WAV Magnatune Settings Настройки Magnatune Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) Пользователь: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) Пароль: Membership: Членство: Downloads: Загрузки: Failed to parse Сбой обработки Failed to download Сбой загрузки RSS: RSS: Website: Сайт: Podcast details Подробности подкаста Select a podcast to display its details Выбрать подкаст для показа подробностей Enter search term... Введите поисковый запрос… Failed to fetch podcasts from %1 Сбой получения подкаста с %1 There was a problem parsing the response from %1 При выполнении обработки ответа от %1 возникла ошибка Failed to download directory listing Не удалось загрузить содержимое каталога Failed to parse directory listing Не удалось проанализировать содержимое каталога URL URL Enter podcast URL... Введите URL-адрес подкаста... Load Загрузить Enter podcast URL below, and press 'Load' Введи ниже адрес URL подкаста и нажмите «Загрузить» Invalid URL! Недопустимый URL. Failed to fetch podcast! Не удалось загрузить подкаст! Failed to parse podcast. Не удалось проанализировать подкаст. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata поддерживает только аудио-подкасты! URL-адрес содержит только видео подкасты. Subscribe Подписаться Enter URL Введите URL-адрес Manual podcast URL Ручной URL-адрес подкаста Search %1 Искать %1 Search for podcasts on %1 Поиск подкастов на %1 Add Podcast Subscription Добавить подкаст подписку Browse %1 Просмотреть %1 Browse %1 podcasts Просмотреть %1 подкастов You are already subscribed to this podcast! Вы уже подписаны на этот подкаст Subscription added Подписка добавлена %1 (%2) podcast name (num unplayed episodes) %1 (%2) (Downloading: %1%) (Идёт загрузка: %1%) Failed to parse %1 Сбой обработки %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata поддерживает только аудио-подкасты, а %1 содержит только видео-подкасты. Failed to download %1 Не удалось загрузить %1 Check for new episodes: Проверить наличие новых серий: Download episodes to: Загрузить серии в: Download automatically: Загружать автоматически: Podcast Settings Настройки подкастов Manually Вручную Every 15 minutes Каждые 15 минут Every 30 minutes Каждые 30 минут Every hour Каждый час Every 2 hours Каждые 2 часа Every 6 hours Каждые 6 часов Every 12 hours Каждые 12 часов Every day Каждый день Every week Каждую неделю Don't automatically download episodes Не загружать серии автоматически Latest episode Самая последняя серия Latest %1 episodes Последние %1 серии All episodes Все серии Add Subscription Добавить подписку Remove Subscription Удалить подписку Unsubscribe from '%1'? Отменить подписку на «%1»? Do you wish to download the selected podcast episodes? Загрузить выбранные серии подкаста? Cancel podcast episode downloads (both current and any that are queued)? Отменить загрузку серий подкаста (как текущую, так и стоящие в очереди на загрузку)? Do you wish to the delete downloaded files of the selected podcast episodes? Удалить загруженные файлы выбранной серии подкаста? Do you wish to mark the selected podcast episodes as new? Отметить выбранные серии подкаста как новые? Do you wish to mark the selected podcast episodes as listened? Отметить выбранные серии подкаста как уже прослушанные? Background Image i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: context/othersettings.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:207 i18n: ectx: property (title), widget (QGroupBox, groupBox) Фоновое изображение Artist image i18n: file: context/othersettings.ui:39 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_artist) Изображение исполнителя Custom image: i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) i18n: file: context/othersettings.ui:46 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_custom) i18n: file: gui/interfacesettings.ui:227 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_custom) Пользовательское изображение: Blur: i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: context/othersettings.ui:66 i18n: ectx: property (text), widget (QLabel, label_4b) i18n: file: gui/interfacesettings.ui:247 i18n: ectx: property (text), widget (QLabel, label_4b) Размытие: 10px i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) i18n: file: context/othersettings.ui:103 i18n: ectx: property (text), widget (QLabel, contextBackdropBlurLabel) i18n: file: gui/interfacesettings.ui:293 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundBlurLabel) 10px Opacity: i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: context/othersettings.ui:115 i18n: ectx: property (text), widget (QLabel, label_4) i18n: file: gui/interfacesettings.ui:305 i18n: ectx: property (text), widget (QLabel, label_4) Непрозрачность: 40% i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) i18n: file: context/othersettings.ui:152 i18n: ectx: property (text), widget (QLabel, contextBackdropOpacityLabel) i18n: file: gui/interfacesettings.ui:351 i18n: ectx: property (text), widget (QLabel, playQueueBackgroundOpacityLabel) 40% no-c-format Automatically switch to view after: i18n: file: context/othersettings.ui:167 i18n: ectx: property (text), widget (BuddyLabel, contextSwitchTimeLabel) Автоматически переключаться на окно после: Do not auto-switch i18n: file: context/othersettings.ui:177 i18n: ectx: property (specialValueText), widget (QSpinBox, contextSwitchTime) Без автоматического переключения ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) мс Dark background i18n: file: context/othersettings.ui:193 i18n: ectx: property (text), widget (QCheckBox, contextDarkBackground) Тёмный фон Darken background, and use white text, regardless of current color palette. i18n: file: context/othersettings.ui:196 i18n: ectx: property (toolTip), widget (QCheckBox, contextDarkBackground) Затемнить фон и использовать белый текст, независимо от текущей цветовой палитры. Always collapse into a single pane i18n: file: context/othersettings.ui:203 i18n: ectx: property (text), widget (QCheckBox, contextAlwaysCollapsed) Всегда сворачиваться в одну область Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. i18n: file: context/othersettings.ui:206 i18n: ectx: property (toolTip), widget (QCheckBox, contextAlwaysCollapsed) Показывать только теги «Исполнитель», «Альбом» или «Трек» даже при наличии места для показа всего дерева. Only show basic wikipedia text i18n: file: context/othersettings.ui:213 i18n: ectx: property (text), widget (QCheckBox, wikipediaIntroOnly) Показывать только основной текст wikipedia Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). i18n: file: context/othersettings.ui:220 i18n: ectx: property (text), widget (NoteLabel, wikipediaIntroOnlyNote) Cantata показывает только урезанные версии страниц wikipedia (без изображений, ссылок и т.д.). Это обрезание не всегда является корректным, поэтому Cantata по умолчанию показывает только введение. Если выбрать показ статьи целиком, могут возникнуть ошибки обработки. Также потребуется удалить текущий кеш статей (с помощью страницы «Кеш»). no-c-format Available: i18n: file: context/togglelist.ui:17 i18n: ectx: property (text), widget (QLabel, label_2) Доступно: Selected: i18n: file: context/togglelist.ui:24 i18n: ectx: property (text), widget (QLabel, label_3) Выбрано: Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) Копировать песни с: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (Необходима настройка) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) Копировать песни на: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) Целевой формат: Overwrite songs i18n: file: devices/actiondialog.ui:310 i18n: ectx: property (text), widget (QCheckBox, overwrite) Перезаписать песни To copy: i18n: file: devices/actiondialog.ui:317 i18n: ectx: property (text), widget (QLabel, songCountLabel) Копировать: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) Подробности альбома Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) Год: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) Диск: Single artist i18n: file: devices/albumdetails.ui:115 i18n: ectx: property (text), widget (QCheckBox, singleArtist) Один артист Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) Получение информации об альбоме и треке: Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) Искать изначально через: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) Хост CDDB: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) Порт CDDB: Lookup information as soon as CD is inserted i18n: file: devices/audiocdsettings.ui:84 i18n: ectx: property (text), widget (QCheckBox, cdAuto) Поиск информации, как только будет вставлен CD Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) Извлечение аудио Full paranoia mode (best quality) i18n: file: devices/audiocdsettings.ui:100 i18n: ectx: property (text), widget (QCheckBox, paranoiaFull) Режим full paranoia (лучшее качество) Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) Никогда не пропускать при ошибке чтения These settings are only valid, and editable, when the device is connected. i18n: file: devices/devicepropertieswidget.ui:20 i18n: ectx: property (text), widget (PlainNoteLabel, remoteDeviceNote) Эти параметры действуют и являются изменяемыми только при подключённом устройстве. Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) Папка с музыкой: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) Скопировать обложки альбома как: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) Максимальный размер обложки: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) Громкость по умолчанию: 'Various Artists' workaround i18n: file: devices/devicepropertieswidget.ui:102 i18n: ectx: property (text), widget (QCheckBox, fixVariousArtists) Обходное решение «несколько исполнителей» Automatically scan music when attached i18n: file: devices/devicepropertieswidget.ui:109 i18n: ectx: property (text), widget (QCheckBox, autoScan) Автоматически сканировать музыку при подключении Use cache i18n: file: devices/devicepropertieswidget.ui:116 i18n: ectx: property (text), widget (QCheckBox, useCache) Использование кэша Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) Имена файлов: Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) Схема имени файла: VFAT safe i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: devices/devicepropertieswidget.ui:171 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) i18n: file: tags/trackorganiser.ui:71 i18n: ectx: property (text), widget (QCheckBox, vfatSafe) безопасный VFAT Use only ASCII characters i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: devices/devicepropertieswidget.ui:178 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) i18n: file: tags/trackorganiser.ui:78 i18n: ectx: property (text), widget (QCheckBox, asciiOnly) Использовать только символы ASCII Replace spaces with underscores i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: devices/devicepropertieswidget.ui:185 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) i18n: file: tags/trackorganiser.ui:85 i18n: ectx: property (text), widget (QCheckBox, replaceSpaces) Заменить пробелы на подчеркивания Append 'The' to artist names i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: devices/devicepropertieswidget.ui:192 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) i18n: file: tags/trackorganiser.ui:92 i18n: ectx: property (text), widget (QCheckBox, ignoreThe) Добавлять артикль 'The' к именам исполнителей на английском. Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) Перекодирование Only transcode if source file is of a different format i18n: file: devices/devicepropertieswidget.ui:214 i18n: ectx: property (text), widget (QCheckBox, transcoderWhenDifferent) Перекодировать только если исходный файл имеет другой формат Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) Пример: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) О схемах имени файла The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> i18n: file: devices/filenameschemedialog.ui:79 i18n: ectx: property (toolTip), widget (QPushButton, albumArtist) Исполнитель альбома. Для большинства альбомов он будет аналогичен <i>Исполнителю трека.</i> В сборниках часто встречается <i>Несколько исполнителей.</i> The name of the album. i18n: file: devices/filenameschemedialog.ui:89 i18n: ectx: property (toolTip), widget (QPushButton, albumTitle) Имя альбома. Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) Название альбома The composer. i18n: file: devices/filenameschemedialog.ui:99 i18n: ectx: property (toolTip), widget (QPushButton, composer) Композитор. The artist of each track. i18n: file: devices/filenameschemedialog.ui:109 i18n: ectx: property (toolTip), widget (QPushButton, trackArtist) Артист каждого трека. Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) Исполнитель трека The track title (without <i>Track Artist</i>). i18n: file: devices/filenameschemedialog.ui:119 i18n: ectx: property (toolTip), widget (QPushButton, trackTitle) Название трека (без <i>Исполнителя трека</i>). The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). i18n: file: devices/filenameschemedialog.ui:141 i18n: ectx: property (toolTip), widget (QPushButton, trackArtistAndTitle) Название трека (с <i>Исполнителем трека</i>, если он отличается от <i>Исполнителя альбома</i>). Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) Название трека (+Исполнитель) The track number. i18n: file: devices/filenameschemedialog.ui:151 i18n: ectx: property (toolTip), widget (QPushButton, trackNo) Номер трека. Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) Трек № The album number of a multi-album album. Often compilations consist of several albums. i18n: file: devices/filenameschemedialog.ui:161 i18n: ectx: property (toolTip), widget (QPushButton, cdNo) Номер альбома, состоящего из нескольких альбомов. Часто сборники состоят из нескольких альбомов. CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD № The year of the album's release. i18n: file: devices/filenameschemedialog.ui:171 i18n: ectx: property (toolTip), widget (QPushButton, year) Год альбома выпуска. The genre of the album. i18n: file: devices/filenameschemedialog.ui:181 i18n: ectx: property (toolTip), widget (QPushButton, genre) Жанр альбома. These settings are only editable when the device is not connected. i18n: file: devices/remotedevicepropertieswidget.ui:17 i18n: ectx: property (text), widget (PlainNoteLabel, connectionNote) Эти параметры можно изменять только при отключённом устройстве. Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) Тип: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) Параметры Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) Порт: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) Пользователь: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) Домен: Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) Общий ресурс: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) i18n: file: devices/remotedevicepropertieswidget.ui:160 i18n: ectx: property (text), widget (PlainNoteLabel, label_4) i18n: file: devices/remotedevicepropertieswidget.ui:257 i18n: ectx: property (text), widget (PlainNoteLabel, label_4x) Введённый здесь пароль будет храниться <b>незашифрованным</b> в конфигурационном файле Cantata. Чтобы заставить Cantata запрашивать пароль при каждой попытке доступа к общим ресурсам, укажите пароль «-». Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) Имя сервиса: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) Папка: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) Дополнительные параметры: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. i18n: file: devices/remotedevicepropertieswidget.ui:360 i18n: ectx: property (text), widget (PlainNoteLabel, label_5) В связи с особенностями работы sshfs, для ввода пароля требуется подходящее приложение ssh-askpass (ksshaskpass, ssh-askpass-gnome, и т.п.). This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. i18n: file: devices/remotedevicepropertieswidget.ui:410 i18n: ectx: property (text), widget (PlainNoteLabel, infoLabel) Этот диалог используется только для добавления удалённых устройств (например, с помощью Samba) или для доступа к локально смонтированным папкам. Обычные устройства, подключенные через USB, Cantata показывает автоматически в момент подключения. Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) Имя динамических правил - i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) i18n: file: dynamic/dynamicrules.ui:119 i18n: ectx: property (text), widget (QLabel, ratingToLabel) i18n: file: dynamic/dynamicrules.ui:166 i18n: ectx: property (text), widget (QLabel, rangeLabel2) - seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) секунд About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) О правилах Include songs that match the following: i18n: file: dynamic/dynamicrule.ui:37 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Включить песни, отвечающие следующему условию: Exclude songs that match the following: i18n: file: dynamic/dynamicrule.ui:42 i18n: ectx: property (text), item, widget (QComboBox, typeCombo) Исключить песни, отвечающие следующему условию: Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) Найти похожих исполнителей: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) Исполнитель альбома: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) От года: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) Любой To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) До года: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) Точное совпадение Only enter values for the tags you wish to be search on. i18n: file: dynamic/dynamicrule.ui:241 i18n: ectx: property (text), widget (NoteLabel, label_7) Вводите значения только тех тегов, среди которых нужно производить поиск. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. i18n: file: dynamic/dynamicrule.ui:248 i18n: ectx: property (text), widget (NoteLabel, label_7x) Для указания жанров заканчивайте каждую строку знаком звёздочки (*). Например, жанру «рок*» будет соответствовать и «хард-рок» и «рок-н-ролл». Add a local file i18n: file: gui/coverdialog.ui:30 i18n: ectx: property (toolTip), widget (FlatToolButton, addFileButton) Добавить локальный файл Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) Сохранять загруженные тексты песен в папке с музыкой Save downloaded backdrops in music folder i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/filesettings.ui:46 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) i18n: file: gui/initialsettingswizard.ui:689 i18n: ectx: property (text), widget (QCheckBox, storeBackdropsInMpdDir) Сохранять загруженный фон/оформление в папке с музыкой If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) i18n: file: gui/filesettings.ui:53 i18n: ectx: property (text), widget (NoteLabel, label_2) i18n: file: gui/initialsettingswizard.ui:696 i18n: ectx: property (text), widget (NoteLabel, persNote) Если в параметрах Cantata указано хранить слова песен, обложки дисков или фоновое оформление внутри папки с музыкой, но у пользователя нет доступа на запись в эту папку, то Cantata будет сохранять эти файлы в папке кеша пользователя. Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) Первый запуск программы Cantata Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) Добро пожаловать в программу Cantata! <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> i18n: file: gui/initialsettingswizard.ui:69 i18n: ectx: property (text), widget (QLabel, label_2) <html><head/><body><p>Cantata — это дружественный к пользователю и богатый функционалом клиент для Music Player Daemon (MPD). MPD — это гибкое и мощное серверное приложение для проигрывания музыки.</p><p>Подробности об MPD можно найти на сайте MPD: <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Данный мастер проведёт вас по базовым настройкам, которые необходимы для правильной работы Cantata.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> i18n: file: gui/initialsettingswizard.ui:108 i18n: ectx: property (text), widget (QLabel, label_7) <html><head/><body><p>Добро пожаловать в программу Cantata!</p></body></html> Standard multi-user/server setup i18n: file: gui/initialsettingswizard.ui:159 i18n: ectx: property (text), widget (QRadioButton, advanced) Стандартная серверная многопользовательская настройка Basic single user setup i18n: file: gui/initialsettingswizard.ui:204 i18n: ectx: property (text), widget (QRadioButton, basic) Базовая настройка для одного пользователя Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) Подробности соединения The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. i18n: file: gui/initialsettingswizard.ui:344 i18n: ectx: property (text), widget (QLabel, label_4) Настройки ниже — это базовые настройки, необходимые программе Cantata. Введите необходимые детали и нажмите кнопку «Подключиться» для проверки соединения. The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. i18n: file: gui/initialsettingswizard.ui:481 i18n: ectx: property (text), widget (NoteLabel, musicFolderNoteLabel) Параметр «Папка с музыкой» используется для поиска обложек, слов песен и т.п. Если запущенный экземпляр MPD является удалённым хостом, то этот параметр можно указать в виде HTTP URL. Music folder i18n: file: gui/initialsettingswizard.ui:511 i18n: ectx: property (text), widget (QLabel, label_13) Папка с музыкой Please choose the folder containing your music collection. i18n: file: gui/initialsettingswizard.ui:534 i18n: ectx: property (text), widget (QLabel, label_12) Выбрать папку, в которой хранится ваша музыкальная коллекция. <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> i18n: file: gui/initialsettingswizard.ui:643 i18n: ectx: property (text), widget (QLabel, label_5f) <p>Cantata загружает отсутствующие обложки и слова песен из Интернета.</p><p>Для каждого загруженного элемента необходимо подтверждать, сохранять ли его в папке с музыкой или в папках с персональным кешем пользователя</p> The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. i18n: file: gui/initialsettingswizard.ui:710 i18n: ectx: property (text), widget (NoteLabel, httpNote) Параметром для «Папки с музыкой» указан адрес HTTP, но в настоящий момент Cantata не может загружать файлы на внешние серверы HTTP. Поэтому параметры, указанные выше, нужно оставить без изменений. Finished! i18n: file: gui/initialsettingswizard.ui:740 i18n: ectx: property (text), widget (QLabel, label_6) Готово! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. i18n: file: gui/initialsettingswizard.ui:763 i18n: ectx: property (text), widget (QLabel, label_5) Cantata теперь настроен! <br/><br/>Диалоговое окно конфигурации может быть использовано для настройки внешнего вида Cantata, а также для добавления дополнительных хостов MPD и т.д. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. i18n: file: gui/initialsettingswizard.ui:795 i18n: ectx: property (text), widget (NoteLabel, albumArtistsNoteLabel) Cantata группирует треки в альбомы с помощью тега «AlbumArtist», если он указан. В противном случае будет использован тег «Исполнитель». При наличии альбома с несколькими исполнителями, тег «AlbumArtist» <b>должен</b> быть указан для корректной работы группировки треков. В этом сценарии рекомендуется использовать тег «Несколько исполнителей». <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. i18n: file: gui/initialsettingswizard.ui:827 i18n: ectx: property (text), widget (QLabel, groupWarningLabel) <b>Предупреждение:</b> вы не являетесь членом группы 'users'. Функционал программы Cantata улучшится (сохранение с правильными правами обложек, слов песен и т.п.) если вы (или ваш администратор) добавите своего пользователя в эту группу. Если вы будете делать это самостоятельно, то затем, для применения настройки, будет необходимо закончить текущий сеанс пользователя и снова войти в систему. Sidebar i18n: file: gui/interfacesettings.ui:36 i18n: ectx: attribute (title), widget (QWidget, sidebarTab) Боковая панель Views i18n: file: gui/interfacesettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, viewsGroup) Вид Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) Стиль: Position: i18n: file: gui/interfacesettings.ui:95 i18n: ectx: property (text), widget (BuddyLabel, sbPositionLabel) Позиция: Only show icons, no text i18n: file: gui/interfacesettings.ui:108 i18n: ectx: property (text), widget (QCheckBox, sbIconsOnly) Показывать только значки Auto-hide i18n: file: gui/interfacesettings.ui:115 i18n: ectx: property (text), widget (QCheckBox, sbAutoHide) Автоматически скрывать Initially collapse albums i18n: file: gui/interfacesettings.ui:150 i18n: ectx: property (text), widget (QCheckBox, playQueueStartClosed) Изначально альбомы свернуты Automatically expand current album i18n: file: gui/interfacesettings.ui:157 i18n: ectx: property (text), widget (QCheckBox, playQueueAutoExpand) Автоматически раскрывать текущий альбом Scroll to current track i18n: file: gui/interfacesettings.ui:164 i18n: ectx: property (text), widget (QCheckBox, playQueueScroll) Прокручивать список до текущего трека Prompt before clearing i18n: file: gui/interfacesettings.ui:171 i18n: ectx: property (text), widget (QCheckBox, playQueueConfirmClear) Запрос перед удалением Separate action (and shortcut) for play queue search i18n: file: gui/interfacesettings.ui:178 i18n: ectx: property (text), widget (QCheckBox, playQueueSearch) Отдельное действие (и сочетание клавиш) для поиска в очереди воспроизведения. Current album cover i18n: file: gui/interfacesettings.ui:220 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_cover) Текущая обложка альбома Toolbar i18n: file: gui/interfacesettings.ui:367 i18n: ectx: attribute (title), widget (QWidget, toolbarTab) Панель инструментов Show stop button i18n: file: gui/interfacesettings.ui:376 i18n: ectx: property (text), widget (QCheckBox, showStopButton) Показывать кнопку «стоп» Show cover of current track i18n: file: gui/interfacesettings.ui:383 i18n: ectx: property (text), widget (QCheckBox, showCoverWidget) Показывать обложку альбома текущего трека Show track rating i18n: file: gui/interfacesettings.ui:390 i18n: ectx: property (text), widget (QCheckBox, showRatingWidget) Показывать рейтинг трека External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) Внешнее Show popup messages when changing tracks i18n: file: gui/interfacesettings.ui:411 i18n: ectx: property (text), widget (QCheckBox, systemTrayPopup) Показать всплывающие сообщения при смене дорожек Show icon in notification area i18n: file: gui/interfacesettings.ui:421 i18n: ectx: property (text), widget (QCheckBox, systemTrayCheckBox) Показывать значок в области уведомлений Minimize to notification area when closed i18n: file: gui/interfacesettings.ui:431 i18n: ectx: property (text), widget (QCheckBox, minimiseOnClose) Минимизировать в область уведомлений при закрытии On Start-up i18n: file: gui/interfacesettings.ui:438 i18n: ectx: property (title), widget (QGroupBox, startupState) При запуске Show main window i18n: file: gui/interfacesettings.ui:444 i18n: ectx: property (text), widget (QRadioButton, startupStateShow) Показать главное окно Hide main window i18n: file: gui/interfacesettings.ui:451 i18n: ectx: property (text), widget (QRadioButton, startupStateHide) Скрыть главное окно Restore previous state i18n: file: gui/interfacesettings.ui:458 i18n: ectx: property (text), widget (QRadioButton, startupStateRestore) Восстановить прежнее состояние General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) Общее Fetch missing covers from Last.fm i18n: file: gui/interfacesettings.ui:620 i18n: ectx: property (text), widget (QCheckBox, fetchCovers) Получить недостающие обложки из Last.fm Show delete action in context menus i18n: file: gui/interfacesettings.ui:627 i18n: ectx: property (text), widget (QCheckBox, showDeleteAction) Показать действие удаления в контекстных меню Enforce single-click activation of items i18n: file: gui/interfacesettings.ui:634 i18n: ectx: property (text), widget (QCheckBox, forceSingleClick) Включать одним щелчком <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> i18n: file: gui/interfacesettings.ui:642 i18n: ectx: property (toolTip), widget (QCheckBox, touchFriendly) <p>Это действие изменит интерфейс Cantata следующим образом: <ul><li>Кнопки проигрывания и управления станут на 33% шире</li><li>Окна будут «скользить» при прокрутке</li><li>Чтобы протащить элемент, нужно будет «прикоснуться» к верхнему левому углу</li><li>Панели прокрутки будут всего несколько пикселей в ширину</li><li>Действия, (например, «Добавить в очередь воспроизведения») всегда будут видимыми (а не только когда элемент находится точно под курсором)</li><li>Кнопки увеличения/уменьшения будут иметь дополнительные кнопочки «+» и «-» сбоку текстового поля</li></ul></p> no-c-format Make interface more touch friendly i18n: file: gui/interfacesettings.ui:645 i18n: ectx: property (text), widget (QCheckBox, touchFriendly) Более дружественный сенсорный интерфейс Show song information tooltips i18n: file: gui/interfacesettings.ui:652 i18n: ectx: property (text), widget (QCheckBox, infoTooltips) Показывать подсказки с информацией о песне Support retina displays i18n: file: gui/interfacesettings.ui:659 i18n: ectx: property (text), widget (QCheckBox, retinaSupport) Поддержка дисплеев Retina Language: i18n: file: gui/interfacesettings.ui:666 i18n: ectx: property (text), widget (BuddyLabel, langLabel) Язык: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:681 i18n: ectx: property (text), widget (NoteLabel, singleClickLabel) Изменение параметра «Включать одним щелчком» требует перезапуска Cantata Changing the language setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:688 i18n: ectx: property (text), widget (NoteLabel, langNoteLabel) Изменение языкового параметра требует перезапуска Cantata Changing the 'touch friendly' setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:695 i18n: ectx: property (text), widget (NoteLabel, touchFriendlyNoteLabel) Изменение параметра «Более дружественный сенсорный интерфейс» требует перезапуска Cantata Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. i18n: file: gui/interfacesettings.ui:702 i18n: ectx: property (text), widget (NoteLabel, retinaSupportNoteLabel) Включение поддержки для дисплеев Retina сделает иконки более чёткими на дисплеях Retina, и более расплывчатыми на других устройствах. Изменение этого параметра потребует перезапуска Cantata. [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [Динамический] Exit Full Screen i18n: file: gui/mainwindow.ui:204 i18n: ectx: property (text), widget (UrlLabel, fullScreenLabel) Выход из полноэкранного режима Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) Остановить воспроизведение при выходе из программы Inhibit suspend whilst playing i18n: file: gui/playbacksettings.ui:65 i18n: ectx: property (text), widget (QCheckBox, inhibitSuspend) Блокировать активацию ждущего режима во время проигрывания If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) i18n: file: gui/playbacksettings.ui:72 i18n: ectx: property (text), widget (NoteLabel, noteLabel) При нажатии и удержании кнопки «Стоп» будет показано меню, предлагающее остановить воспроизведение немедленно или после окончание проигрываемого трека. (Показ кнопки «Стоп» можно включить в разделе «Интерфейс/Панель инструментов».) Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) Вывод s i18n: file: gui/playbacksettings.ui:125 i18n: ectx: property (suffix), widget (QSpinBox, crossfading) s About replay gain i18n: file: gui/playbacksettings.ui:178 i18n: ectx: property (text), widget (UrlLabel, aboutReplayGain) Информация о replay gain Use the checkboxes below to control the active outputs. i18n: file: gui/playbacksettings.ui:187 i18n: ectx: property (text), widget (QLabel, outputsViewLabel) Используйте флажки ниже, чтобы контролировать активные выходы. Collection: i18n: file: gui/serversettings.ui:35 i18n: ectx: property (text), widget (QLabel, label) Коллекция: Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) Имя файла обложки: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> i18n: file: gui/serversettings.ui:149 i18n: ectx: property (toolTip), widget (LineEdit, coverName) <p>Имя файла (без расширения) для сохранения загруженных обложек.<br/>Если поле не заполнено, используется 'cover'.<br/><br/><i>%artist% будет заменён на значение исполнителя текущей песни, а %album% — на название альбома.</i></p> no-c-format HTTP stream URL: i18n: file: gui/serversettings.ui:156 i18n: ectx: property (text), widget (BuddyLabel, streamUrlLabel) Адрес потока HTTP: 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. i18n: file: gui/serversettings.ui:185 i18n: ectx: property (text), widget (NoteLabel, streamUrlNoteLabel) «Адрес потока HTTP» используется только в том случае, если MPD настроен на вывод в поток HTTP, и Cantata должна иметь возможность проигрывать этот поток. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. i18n: file: gui/serversettings.ui:258 i18n: ectx: property (text), widget (NoteLabel, basicMusicFolderNoteLabel) При изменении параметра «Папка с музыкой» необходимо вручную обновить базу данных композиций. Это можно сделать, нажав кнопку «Обновить базу данных» в окне «Исполнители» или «Альбомы». Mode: i18n: file: network/proxysettings.ui:26 i18n: ectx: property (text), widget (BuddyLabel, modeLabel) Режим: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP-прокси SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS прокси Use the checkboxes below to configure the list of active services. i18n: file: online/onlinesettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Используйте флажки ниже, чтобы настроить список активных служб. Configure Service i18n: file: online/onlinesettings.ui:47 i18n: ectx: property (text), widget (QPushButton, configureButton) Настройка сервиса Scrobble using: i18n: file: scrobbling/scrobblingsettings.ui:32 i18n: ectx: property (text), widget (BuddyLabel, scrobblerLabel) Скробблить с помощью: Status: i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: scrobbling/scrobblingsettings.ui:88 i18n: ectx: property (text), widget (QLabel, statusLabel) i18n: file: streams/digitallyimportedsettings.ui:94 i18n: ectx: property (text), widget (BuddyLabel, label_5) Статус: Login i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: scrobbling/scrobblingsettings.ui:114 i18n: ectx: property (text), widget (QPushButton, loginButton) i18n: file: streams/digitallyimportedsettings.ui:120 i18n: ectx: property (text), widget (QPushButton, loginButton) Логин Scrobble tracks i18n: file: scrobbling/scrobblingsettings.ui:131 i18n: ectx: property (text), widget (QCheckBox, enableScrobbling) Скробблить треки Show 'Love' button i18n: file: scrobbling/scrobblingsettings.ui:138 i18n: ectx: property (text), widget (QCheckBox, showLove) Показывать кнопку «Любимый» You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. i18n: file: streams/digitallyimportedsettings.ui:29 i18n: ectx: property (text), widget (QLabel, label) Прослушивание песен бесплатно и не требует регистрации, но владельцы учётных записей Premium слушают потоки в более высоком качестве и без рекламы. Чтобы зарегистрировать учётную запись Premium, посетите <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>. Premium Account i18n: file: streams/digitallyimportedsettings.ui:42 i18n: ectx: property (title), widget (QGroupBox, groupBox) Учётная запись Premium Stream type: i18n: file: streams/digitallyimportedsettings.ui:81 i18n: ectx: property (text), widget (BuddyLabel, label_4) Тип потока: Session expiry: i18n: file: streams/digitallyimportedsettings.ui:127 i18n: ectx: property (text), widget (QLabel, expiryLabel) Сеанс истекает: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm i18n: file: streams/digitallyimportedsettings.ui:157 i18n: ectx: property (text), widget (NoteLabel, noteLabel) Эти параметры применимы для Digitally Imported, JazzRadio.com, RockRadio.com, и Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. i18n: file: streams/digitallyimportedsettings.ui:164 i18n: ectx: property (text), widget (NoteLabel, note2Label) Если открыть подробности учётной записи, то под списком потоков появится указатель статуса «DI». Он показывает, совершён ли вход в учётную запись, или нет. Use the checkboxes below to configure the list of active providers. i18n: file: streams/streamssettings.ui:12 i18n: ectx: property (text), widget (QLabel, label) Используйте флажки ниже для настройки списка активных источников. Built-in categories are shown in italic, and these cannot be removed. i18n: file: streams/streamssettings.ui:25 i18n: ectx: property (text), widget (PlainNoteLabel, note) Встроенные категории показаны курсивом, и их нельзя удалить. Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) Поиск: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) Горячие клавиши для выбранного действия Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) По умолчанию: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) Пользовательский: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) Исполнитель альбома: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) Номер трека: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) Номер диска: Rating: i18n: file: tags/tageditor.ui:171 i18n: ectx: property (text), widget (StateLabel, ratingLabel) Рейтинг: <i>(Various)</i> i18n: file: tags/tageditor.ui:186 i18n: ectx: property (text), widget (QLabel, ratingVarious) <i>(Разные)</i> Ratings are stored in an external database, and <b>not</b> in the song's file. i18n: file: tags/tageditor.ui:217 i18n: ectx: property (text), widget (PlainNoteLabel, ratingNoteLabel) Рейтинги хранятся во внешней базе данных, а <b>не</b> в файле композиции. Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) Исходное название New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) Новое название Ratings will be lost if a file is renamed. i18n: file: tags/trackorganiser.ui:130 i18n: ectx: property (text), widget (UrlNoteLabel, ratingsNote) При переименовании файла композиции, её рейтинг будет потерян. Your names NAME OF TRANSLATORS Юлия Дронова Your emails EMAIL OF TRANSLATORS juliette.tux@gmail.com Show All Tracks Показать все треки Show Untagged Tracks Показать треки без тегов Remove From List Удалить из списка Album Gain Album Gain Track Gain Track Gain Album Peak Album Peak Track Peak Track Peak Scan Сканировать Update ReplayGain tags in tracks? Обновить теги ReplayGain для треков? Update Tags Обновить теги Abort scanning of tracks? Прервать сканирование треков? Abort Прервать Abort reading of existing tags? Прервать чтение существующих тегов? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Сканировать <b>все</b> треки?<br/><br/><i>У каждого трека есть существующий тег ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Сканировать все треки, или только треки без существующих тегов? Untagged Tracks Треки без тегов All Tracks Все треки Scanning tracks... Сканирование треков... Reading existing tags... Чтение существующих треков... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (испорченные теги?) Failed to update the tags of the following tracks: Сбой обновления тегов для следующих треков: Device is not connected. Устройство не подключено %1 dB %1 дБ Failed Произошла ошибка Original: %1 dB Оригинал: %1 дБ Original: %1 Оригинал: %1 Remove the selected tracks from the list? Удалить выбранные треки из списка? Remove Tracks Удалить треки Invalid service Недопустимая служба Invalid method Недопустимый метод Authentication failed Ошибка аутентификации Invalid format Недопустимый формат Invalid parameters Недопустимые параметры Invalid resource specified Недопустимый ресурс Operation failed Ошибка операции Invalid session key Недействительный ключ сеанса Invalid API key Недопустимый ключ API Service offline Сервис недоступен Last.fm is currently busy, please try again in a few minutes Last.fm в настоящее время занят, повторите попытку через несколько минут Rate-limit exceeded Превышено ограничение скорости %1 error: %2 %1 ошибка: %2 %1: Loved Current Track %1: текущий трек отмечен «любимым» %1: Love Current Track %1: отметить текущий трек как «любимый» %1 (via MPD) scrobbler name (via MPD) %1 (через MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. При использовании скробблера, обозначенного «(через MPD)» (например, %1), его необходимо вначале запустить. Cantata может отметить «Любимые» треки только с его помощью, и не может самостоятельно включать/отключать скробблинг. Authenticating... Вход... Authenticated Вход выполнен Not Authenticated Вход не выполнен %1: Scrobble Tracks %1: скробблить треки Digitally Imported Settings Настройки Digitally Imported MP3 256k MP3 256к AAC 64k AAC 64к AAC 128k AAC 128к Logout Выйти URL: URL: Add Stream Добавить поток Edit Stream Редактировать поток <i><b>ERROR:</b> Invalid protocol</i> <i><b>ОШИБКА:</b> недопустимый протокол</i> Installed Установлен Update available Доступно обновление Check the providers you wish to install/update. Отметьте источники, которые нужно установить/обновить. Install/Update Stream Providers Установить/обновить источники потоков Downloading list... Загрузка списка... Digitally Imported Digitally Imported Local and National Radio (ListenLive) Местное и национальное радио (ListenLive) Failed to download list of stream providers! Не удалось получить список источников потоков. Installing/updating %1 Идёт установка/обновление %1 Failed to install '%1' Сбой установки «%1» Failed to download '%1' Сбой загрузки «%1» Install/update the selected stream providers? Установить/обновить выбранные источники потоков? Install the selected stream providers? Установить выбранные источники потоков? Update the selected stream providers? Обновить выбранные источники потоков? Install/Update Установка/Обновление Abort installation/update? Прервать установку/обновление? Downloading %1 Идёт загрузка %1 Update all updateable providers Обновить все обновляемые источники Import Streams Into Favorites Импортировать потоки в Избранное Export Favorite Streams Экспортировать избранные потоки Add New Stream To Favorites Добавить новый поток в Избранное Digitally Imported Service name Digitally Imported Import Streams Импортировать потоки XML Streams (*.xml *.xml.gz *.cantata) Потоки XML (*.xml *.xml.gz *.cantata) Export Streams Экспортировать потоки XML Streams (*.xml.gz) Потоки XML (*.xml.gz) Failed to create '%1'! Сбой создания «%1» Stream '%1' already exists! Поток «%1» уже существует A stream named '%1' already exists! Поток с названием «%1» уже существует Bookmark added Закладка добавлена Already bookmarked Уже в закладках Already in favorites Уже в Избранном Reload '%1' streams? Перезагрузить потоки «%1»? Are you sure you wish to remove bookmark to '%1'? Точно удалить закладку для «%1»? Are you sure you wish to remove all '%1' bookmarks? Точно удалить все закладки «%1»? Are you sure you wish to remove the %1 selected streams? Точно удалить все %1 выбранных потоков? Are you sure you wish to remove '%1'? Точно удалить «%1»? Added '%1'' to favorites Добавлено '%1' в избранное Configure Streams Настройка потоков From File... Из файла… Download... Загрузка… Configure Provider Настроить источник Install Установить Install Streams Установить потоки Cantata Streams (*.streams) потоки Cantata (*.streams) A category named '%1' already exists! Overwrite? Категория с названием «%1» уже существует Перезаписать? Failed top open package file. Сбой открытия файла пакета Invalid file format! Недопустимый формат файла Failed to create stream category folder! Не удалось создать папку для категории потока Failed to save stream list! Не удалось сохранить список потоков Failed to remove streams folder! Не удалось удалить папку с потоками. &OK &Ok &Cancel &Отмена &Yes &Да &No &Нет &Discard &Отклонить &Save &Сохранить &Apply &Применить &Close &Закрыть &Overwrite &Заменить &Reset &Сбросить &Continue &Продолжить &Delete &Удалить &Stop &Остановить &Remove &Удалить &Previous &Предыдущее &Next &Далее Configure... Настроить... Password Пароль Please enter password: Введите пароль: Close Закрыть Warning Предупреждение Question Вопрос &Window &Окно Minimize Свернуть Zoom Масштаб Select Folder Выбрать папку Select File Выбрать файл %1 B %1 Б %1 kB %1 кБ %1 MB %1 MБ %1 GB %1 ГБ %1 KiB %1 КиБ %1 MiB %1 МиБ %1 GiB %1 ГиБ Tags Теги Set 'Album Artist' from 'Artist' Взять значение «Исполнитель альбома» из значения «Исполнитель» Read Ratings from File Прочесть рейтинги из файла Write Ratings to File Записать рейтинги в файл All tracks Все треки Apply "Various Artists" workaround to <b>all</b> tracks? Применить обходное решение «несколько исполнителей» для <b>всех</b> треков? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Эта настройка установит значения «Исполнитель альбома» и «Исполнитель» на «Несколько исполнителей» и значение «Название» на "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Откатить обходное решение «несколько исполнителей» для <b>всех</b> треков? Revert "Various Artists" workaround Откатить обходное решение «несколько исполнителей» <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Там, где значение «Исполнитель альбома» равно значению «Исполнитель», и название имеет формат "TrackArtist - TrackTitle", значение «Исполнитель» будет получаться из «Названия», а само значение «Название» будет равно названию композиции, т.е. <br/><br/>Скажем, если «Название» имеет формат "Wibble - Wobble", тогда «Исполнитель» будет иметь значение «Wibble», а «Название» — «Wobble»</i> Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Установить значение «Исполнитель альбома» из значения «Исполнитель» (при пустом значении «Исполнитель альбома») для <b>всех</b> треков? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Установить значение «Исполнитель альбома» из значения «Исполнитель» (при пустом значении «Исполнитель альбома») ? Album Artist from Artist Исполнитель альбома как Исполнитель Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Сделать начальную букву текстовых полей (напр.«Название», «Исполнитель» и т.д.) заглавной для <b>всех</b> треков? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Сделать начальную букву текстовых полей (напр.«Название», «Исполнитель» и т.д.) заглавной? Adjust the value of each track number by: Настроить значение номера каждого трека по: Read ratings for all tracks from the music files? Прочитать рейтинги всех треков из файлов композиций? Read rating from music file? Прочитать рейтинг из файла композиции? Ratings Рейтинги Read Ratings Прочитать рейтинги Read Rating Прочитать рейтинг Read, and updated, ratings from the following tracks: Считать и обновить рейтинги из следующих треков: Not all Song ratings have been read from MPD! Не все рейтинги композиций были прочитаны из MPD Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Рейтинги песен хранятся не в файлах композиций, а в базе данных MPD 'sticker'. Чтобы сохранить их в действительный файл, Cantata сначала должна считать их из MPD. Song rating has not been read from MPD! Рейтинги композиций не были считаны из MPD Write ratings for all tracks to the music files? Записать рейтинги всех треков в файлы композиций? Write rating to music file? Записать рейтинг в файл композиции? Write Ratings Записать рейтинги Write Rating Записать рейтинг Failed to write ratings of the following tracks: Не удалось записать рейтинги для следующих треков: Failed to write rating to music file! Не удалось записать рейтинг в файл композиции All tracks [modified] Все треки [изменено] %1 [modified] %1 [изменено] Would you also like to rename your song files, so as to match your tags? Переименовать также и файлы песен, для соответствия тегам? Rename Files Переименовать файлы Abort renaming of files? Прервать переименование файлов? Source file does not exist! Файл-источник не существует Destination file already exists! Целевой файл уже существует Failed to create destination folder! Не удалось создать папку назначения Failed to rename '%1' to '%2' Не удалось переименовать «%1» в «%2» Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Рейтинги песен хранятся не в файлах композиций, а в базе данных MPD 'sticker'. При переименовании файла (или папки, в которой он хранится), рейтинг композиции будет потерян. <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Автор музыки:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Исполнитель:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Исполнитель:</b></td><td>%1</td></tr><tr><td align="right"><b>Альбом:</b></td><td>%2</td></tr><tr><td align="right"><b>Год:</b></td><td>%3</td></tr> Filter On Genre Фильтр по жанру All Genres Все жанры Go Back Назад Menu Меню (Stream) (Поток) Search... Поиск… Close Search Bar Закрыть панель поиска Logged into %1 %1: вход выполнен <b>NOT</b> logged into %1 %1: вход <b>НЕ</b> выполнен Basic Tree (No Icons) Основные дерево (Без иконок) Simple Tree Простое дерево Detailed Tree Подробное дерево List Список Grid Сетка Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Нет доступа к файлам композиций! Проверьте параметр «папка с музыкой» в Cantata и параметр "music_directory" в MPD. Cannot access song files! Please check that the device is still attached. Нет доступа к файлам композиций. Проверьте, подключено ли устройство. Stretch Columns To Fit Window Растянуть столбцы по размеру окна (Various) (Несколько) Mute Заглушить звук Unmute Включить звук Volume %1% (Muted) Громкость %1% (звук отключён) Volume %1% Громкость %1% 1 Track Singular 1 трек %1 Tracks Plural (N!=1) %1 треков 1 Track (%1) Singular 1 трек (%1) %1 Tracks (%2) Plural (N!=1) %1 треков (%2) 1 Album Singular 1 альбом %1 Albums Plural (N!=1) %1 альбомов 1 Artist Singular 1 исполнитель %1 Artists Plural (N!=1) %1 исполнителей 1 Stream Singular 1 поток %1 Streams Plural (N!=1) %1 потоков 1 Entry Singular 1 элемент %1 Entries Plural (N!=1) %1 элементов 1 Rule Singular 1 правило %1 Rules Plural (N!=1) %1 правил 1 Podcast Singular 1 подкаст %1 Podcasts Plural (N!=1) %1 подкастов 1 Episode Singular 1 серия %1 Episodes Plural (N!=1) %1 серий 1 Update available Singular Доступно 1 обновление %1 Updates available Plural (N!=1) Доступно обновлений: %1 ActionDialog Calculating size of files to be copied, please wait... Copy songs from: Копировать песни с: Configure Настройка (Needs configuring) (Необходима настройка) Copy songs to: Копировать песни на: Destination format: Целевой формат: Overwrite songs Перезаписать песни To copy: Копировать: <b>INVALID</b> <b>НЕДОПУСТИМО</b> <i>(When different)</i> <i>(если отличается)</i> Artists:%1, Albums:%2, Songs:%3 Исполнители:%1, альбомы:%2, песни:%3 %1 free %1 свободно Local Music Library Локальная музыкальная библиотека Audio CD Аудио-CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. На устройстве назначения недостаточно свободного места. Выбранные песни занимают %1, но доступно только %2. Для успешного копирования на устройство, композиции необходимо перекодировать с получением файлов меньшего размера. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. В месте назначения недостаточно места. Выбранные песни занимают %1, но доступно всего %2. Copy Songs To Library Copy Songs To Device Копировать песни на устройство Copy Songs Копировать песни Delete Songs Удалить песни You have not configured the destination device. Continue with the default settings? Устройство назначения не было настроено. Продолжать с настройками по умолчанию? Not Configured Не настроено Use Defaults Использовать параметры по умолчанию You have not configured the source device. Continue with the default settings? Исходное устройство не было настроено. Продолжать с настройками по умолчанию? Are you sure you wish to stop? Точно остановить? Stop Остановить Device has been removed! Устройство было удалено. Device is not connected! Устройство не подключено. Device is busy? Устройство занято? Device has been changed? Устройство было изменено? Clearing unused folders Очищение неиспользуемых папок Calculate ReplayGain for ripped tracks? Рассчитать ReplayGain для скопированных с диска треков? ReplayGain ReplayGain Calculate Рассчитать The destination filename already exists! Имя конечного файла уже существует! Song already exists! Песня уже существует! Song does not exist! Песня не существует! Failed to create destination folder!<br/>Please check you have sufficient permissions. Не удалось создать папку!<br/>Пожалуйста, проверьте достаточно ли у вас прав. Source file no longer exists? Исходного файла больше не существует? Failed to copy. Не удается скопировать. Failed to delete. Не удается удалить. Not connected to device. Не подключен к устройству. Selected codec is not available. Выбранный кодек не доступен. Transcoding failed. Ошибка транскодирования. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Сбой создания временного файла.<br/>(Требуется для перекодирования устройств MTP.) Failed to read source file. Не удается прочитать исходный файл. Failed to write to destination file. Не удается записать файл назначения. No space left on device. Нет места на устройстве. Failed to update metadata. Не удалось обновить метаданные. Failed to download track. Не удалось загрузить трек. Failed to lock device. Не удалось заблокировать устройство. Local Music Library Properties Свойства локальной музыкальной библиотеки Error Ошибка Skip Пропустить Auto Skip Авто-пропуск Retry Повторить Artist: Исполнитель: Album: Альбом: Track: Трек: Source file: Исходный файл: Destination file: Конечный файл: File: Файл: Saving cache Сохранение кеша Calculating... Вычисление... Time remaining: Оставшееся время: AlbumDetails Album Details Подробности альбома Artist: Исполнитель: Composer: Автор музыки: Title: Название: Genre: Жанр: Year: Год: Disc: Диск: Single artist Один артист Tracks Дорожки Track Трек Artist Исполнитель Title Название AlbumDetailsDialog Audio CD Аудио-CD Apply "Various Artists" Workaround Применить обходное решение «несколько исполнителей» Revert "Various Artists" Workaround Откатить обходное решение «несколько исполнителей» Capitalize Перевести в верхний режим Adjust Track Numbers Настроить номера треков Tools Инструменты Apply "Various Artists" workaround? Применить обходное решение «несколько исполнителей»? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Параметры «Исполнитель альбома» и «Исполнитель» будут установлены на значение «Несколько исполнителей», а «Название» — на "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Откатить обходное решение «несколько исполнителей»? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Там, где значение «Исполнитель альбома» совпадает с «Исполнителем», а «Название» имеет формат "TrackArtist - TrackTitle", значение «Исполнитель» будет взято из «Названия», а само «Название» будет только названием. Например, если «Название» будет иметь значение "Wibble - Wobble", то «Исполнитель» будет указан как "Wibble", а «Название» — "Wobble" Revert Откатить Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Должна ли быть заглавной первая буква тегов «название», «исполнитель», «исполнитель альбома» и «альбом»? Adjust track number by: Настроить номера треков по: AlbumView Refresh Album Information Обновить сведения об альбоме Album Альбом Tracks Дорожки ArtistView Refresh Artist Information Обновить сведения об исполнителе Artist Исполнитель Albums Альбомы Web Links Ссылки Similar Artists Похожие исполнители AudioCdDevice Reading disc Чтение диска %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) AudioCdSettings Album and Track Information Retrieval Получение информации об альбоме и треке: Initially look up via: Искать изначально через: CDDB Host: Хост CDDB: CDDB Port: Порт CDDB: Lookup information as soon as CD is inserted Поиск информации, как только будет вставлен CD Audio Extraction Извлечение аудио Full paranoia mode (best quality) Режим full paranoia (лучшее качество) Never skip on read error Никогда не пропускать при ошибке чтения CDDB CDDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Cue Sheet Playlist Плейлист CacheItem Deleting... Удаление... Calculating... Вычисление... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers Обложки Scaled Covers Масштабированные обложки Backdrops Художественное оформление Lyrics Слова песни Artist Information Сведения об исполнителе Album Information Сведения об альбоме Track Information Сведения об треке Stream Listings Каталог потоков Podcast Directories Каталоги подкастов Wikipedia Languages Языки Wikipedia Scrobble Tracks Скробблить треки Delete All Удалить всё Delete all '%1' items? Удалить все «%1» объектов? Delete Cache Items Удалить элементы кеша Delete items from all selected categories? Удалить элементы из всех выбранных категорий? CacheTree Name Имя Item Count Количество элементов Space Used Используемое пространство CddbInterface Data Track Трек с данными Failed to open CD device Сбой открытия устройства CD Track %1 Трек %1 Failed to create CDDB connection Сбой создания подключения CDDB Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB Совпадений в CDDB не найдено. CDDB error: %1 Ошибка CDDB: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: Найдено несколько совпадений. Выберите релевантный ниже: Artist Исполнитель Title Название Disc Selection Выбор диска %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 диск %3 (%4) %1 - %2 (%3) artist - album (year) %1 - %2 (%3) ContextSettings Lyrics Providers Источники текстов Wikipedia Languages Языки Wikipedia Other Другое ContextWidget &Artist &Артист Al&bum &Альбом &Track &Трек CoverDialog Search Поиск Add a local file Добавить локальный файл Configure Настройка This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive CoverArt Archive An image already exists for this artist, and the file is not writeable. Для этого артиста изображение уже существует, и файл не доступен для записи. A cover already exists for this album, and the file is not writeable. Для этого альбома обложка уже существует, и файл не доступен для записи. '%1' Artist Image '%1' Изображение исполнителя '%1 - %2' Album Cover 'Artist - Album' Album Cover Обложка альбома «%1 - %2» Failed to set cover! Could not download to temporary file! Сбой установки обложки. Загрузка во временный файл не удалась. Failed to download image! Сбой загрузки изображения. Load Local Cover Загрузить локальную обложку Images (*.png *.jpg) Изображения (*.png *.jpg) File is already in list! Файл уже в списке. Failed to read image! Сбой чтения изображения Display Экран Remove Удалить Failed to set cover! Could not make copy! Не удалось настроить обложку. Сбой создания копии. Failed to set cover! Could not backup original! Не удалось настроить обложку. Сбой создания резервной копи оригинала. Failed to set cover! Could not copy file to '%1'! Не удалось настроить обложку. Сбой копирования файла в «%1». Searching... Идёт поиск... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Автор музыки:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Исполнитель:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>Исполнитель:</b></td><td>%1</td></tr><tr><td align="right"><b>Альбом:</b></td><td>%2</td></tr><tr><td align="right"><b>Год:</b></td><td>%3</td></tr> CoverPreview Image Изображение Downloading... Загружается... Image (%1 x %2 %3%) Image (width x height zoom%) Изображение (%1 x %2 %3%) CustomActionDialog Name: Имя: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add Добавить Edit Изменить Remove Удалить Name Имя Command Remove the selected commands? Device Updating (%1)... Обновление (%1)... Updating (%1%)... Обновление (%1%)... DevicePropertiesDialog Device Properties Свойства устройства DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Эти параметры действуют и являются изменяемыми только при подключённом устройстве. Name: Имя: Music folder: Папка с музыкой: Copy album covers as: Скопировать обложки альбома как: Maximum cover size: Максимальный размер обложки: Default volume: Громкость по умолчанию: 'Various Artists' workaround Обходное решение «несколько исполнителей» Automatically scan music when attached Автоматически сканировать музыку при подключении Use cache Использование кэша Filenames Имена файлов: Filename scheme: Схема имени файла: VFAT safe безопасный VFAT Use only ASCII characters Использовать только символы ASCII Replace spaces with underscores Заменить пробелы на подчеркивания Append 'The' to artist names Добавлять артикль 'The' к именам исполнителей на английском. If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding Перекодирование Only transcode if source file is of a different format Перекодировать только если исходный файл имеет другой формат Only transcode if source is FLAC/WAV Don't copy covers Не копировать обложки Embed cover within each file Встроенная обложка в каждом файле No maximum size Нет максимального размера 400 pixels 400 пикс. 300 pixels 300 пикс. 200 pixels 200 пикс. 100 pixels 100 пикс. <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>Во время копирования треков на устройство, и при значении «Исполнитель альбома»установленном как «Несколько исполнителей», Cantata установит значение тега «Исполнитель» для всех треков на значение «Несколько исполнителей», а тег трека «Название» на 'TrackArtist - TrackTitle'.<hr/> Во время копирования треков с устройства Cantata проверит настройки «Исполнитель альбома» и«Исполнитель», которые должны иметь значение «Несколько исполнителей», и в этом случае попробует получить правильные данные исполнителя из тега «Название» и удалить имя исполнителя из тега «Название»</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> <p>Если активировать этот параметр, Cantata создаст кеш музыкальной библиотекиустройства, что ускорит последующие сканирования библиотеки (файл кеша будет использоваться вместо чтения тегов каждого файла)<hr/><b>ВНИМАНИЕ:</b> при использовании другого приложения для обновления библиотеки устройства, данные этого кеша станут неактуальными. Для их коррекции нажмите на значок «обновить» в списке устройств. Файл кеша буде удалён, а содержимое устройства будет просканировано заново.</p> Do not transcode Не перекодировать Encoder Кодировщик Transcode to %1 Перекодировать в %1 %1 (%2 free) name (size free) %1 (%2 свободно) DevicesModel Configure Device Настройка устройства Refresh Device Обновить устройство Connect Device Подключить устройство Disconnect Device Отключить устройство Edit CD Details Редактировать подробности CD Not Connected Не подключено No Devices Attached Нет подключённых устройств DevicesPage Copy To Library Скопировать в библиотеку Synchronise Forget Device Исключить устройство Add Device Добавить устройство Lookup album and track details? Произвести поиск подробной информациидля альбома и трека? Refresh Обновить Via CDDB Через CDDB Via MusicBrainz Через MusicBrainz Which type of refresh do you wish to perform? Какой способ обновления выполнить? Partial - Only new songs are scanned (quick) Частичное — сканируются только новые композиции (быстро) Full - All songs are rescanned (slow) Полное — повторное сканирование всех композиций (долго) Partial Частично Full Полностью Are you sure you wish to delete the selected songs? This cannot be undone. Точно удалить выбранные песни? Это действие нельзя будет отменить. Delete Songs Удалить песни Are you sure you wish to forget '%1'? Точно исключить «%1»? Are you sure you wish to eject Audio CD '%1 - %2'? Точно извлечь Аудио CD «%1 - %2»? Eject Извлечь Are you sure you wish to disconnect '%1'? Точно отключить «%1»? Disconnect Отключить Please close other dialogs first. Сначала необходимо закрыть другие диалоги. DigitallyImported Not logged in Вход не выполнен Logged in Вход выполнен Unknown error Неизвестная ошибка No subscriptions Нет подписок You do not have an active subscription У вас нет активных подписок Logged in (expiry:%1) Вход выполнен (истекает: %1) Session expired Время сеанса истекло DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Прослушивание песен бесплатно и не требует регистрации, но владельцы учётных записей Premium слушают потоки в более высоком качестве и без рекламы. Чтобы зарегистрировать учётную запись Premium, посетите <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a>. Premium Account Учётная запись Premium Username: Пользователь: Password: Пароль: Stream type: Тип потока: Status: Статус: Login Логин Session expiry: Сеанс истекает: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm Эти параметры применимы для Digitally Imported, JazzRadio.com, RockRadio.com, и Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Если открыть подробности учётной записи, то под списком потоков появится указатель статуса «DI». Он показывает, совершён ли вход в учётную запись, или нет. Digitally Imported Settings Настройки Digitally Imported MP3 256k MP3 256к AAC 64k AAC 64к AAC 128k AAC 128к Not Authenticated Вход не выполнен Authenticating... Вход... Authenticated Вход выполнен Logout Выйти DockMenu Play Играть Pause Пауза DynamicPlaylists Start Dynamic Playlist Запустить динамический плейлист Stop Dynamic Mode Остановить динамический режим Dynamic Playlists Динамический Плейлист Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Для работы динамического режима необходимо установить «perl». Failed to locate rules file - %1 Сбой нахождения файла правил — %1 Failed to remove previous rules file - %1 Сбой удаления предыдущего файла правил — %1 Failed to install rules file - %1 -> %2 Сбой установки файла правил — %1 -> %2 Dynamizer has been terminated. Dynamizer был завершён Awaiting response for previous command. (%1) Ожидание ответа на предыдущую команду (%1) Saving rule Сохранение правила Deleting rule Удаление правила Failed to save %1. (%2) Не удалось сохранить %1. (%2) Failed to delete rules file. (%1) Сбой удаления файла правил (%1) Failed to control dynamizer state. (%1) Сбой проверки состояния dynamizer. (%1) Failed to set the current dynamic rules. (%1) Сбой установки текущих динамических правил (%1) DynamicPlaylistsPage Add Добавить Edit Изменить Remove Удалить Remote dynamizer is not running. Удалённый dynamizer не запущен Are you sure you wish to remove the selected rules? This cannot be undone. Точно удалить выбранные правила? Это действие нельзя отменить. Remove Dynamic Rules Удалить динамические правила FancyTabWidget Configure... Настроить... FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Сохранять загруженные тексты песен в папке с музыкой Save downloaded backdrops in music folder Сохранять загруженный фон/оформление в папке с музыкой If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Если в параметрах Cantata указано хранить слова песен, обложки дисков или фоновое оформление внутри папки с музыкой, но у пользователя нет доступа на запись в эту папку, то Cantata будет сохранять эти файлы в папке кеша пользователя. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: Пример: About filename schemes О схемах имени файла The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Исполнитель альбома. Для большинства альбомов он будет аналогичен <i>Исполнителю трека.</i> В сборниках часто встречается <i>Несколько исполнителей.</i> Album Artist Исполнитель альбома The name of the album. Имя альбома. Album Title Название альбома The composer. Композитор. Composer Автор музыки The artist of each track. Артист каждого трека. Track Artist Исполнитель трека The track title (without <i>Track Artist</i>). Название трека (без <i>Исполнителя трека</i>). Track Title Название трека The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Название трека (с <i>Исполнителем трека</i>, если он отличается от <i>Исполнителя альбома</i>). Track Title (+Artist) Название трека (+Исполнитель) The track number. Номер трека. Track # Трек № The album number of a multi-album album. Often compilations consist of several albums. Номер альбома, состоящего из нескольких альбомов. Часто сборники состоят из нескольких альбомов. CD # CD № The year of the album's release. Год альбома выпуска. Year Год The genre of the album. Жанр альбома. Genre Жанр Filename Scheme Схема имён файлов Various Artists Example album artist Несколько исполнителей Wibble Example artist Wibble Vivaldi Example composer Чайковский Now 5001 Example album Now 5001 Wobble Example song name Wobble Dance Example genre Дэнс The following variables will be replaced with their corresponding meaning for each track name. Следующие переменные будут заменены соответствующими значениями для каждого названия трека. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> <tr><th><em>Переменная</em></th><th><em>Кнопка</em></th><th><em>Описание</em></th></tr> FolderPage Open In File Manager Открыть в файловом менеджере Are you sure you wish to delete the selected songs? This cannot be undone. Точно удалить выбранные песни? Это действие нельзя будет отменить. Delete Songs Удалить песни FsDevice Updating... Обновление... Reading cache Чтение кеша Saving cache Сохранение кеша %1 %2% Message percent %1 %2% GenreCombo Filter On Genre Фильтр по жанру All Genres Все жанры GroupedViewDelegate Audio CD Аудио-CD Streams Потоки %n Track(s) треков: %n треков: %n треков: %n InitialSettingsWizard Cantata First Run Первый запуск программы Cantata Welcome to Cantata Добро пожаловать в программу Cantata! <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Cantata — это дружественный к пользователю и богатый функционалом клиент для Music Player Daemon (MPD). MPD — это гибкое и мощное серверное приложение для проигрывания музыки.</p><p>Подробности об MPD можно найти на сайте MPD: <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>Данный мастер проведёт вас по базовым настройкам, которые необходимы для правильной работы Cantata.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <html><head/><body><p>Добро пожаловать в программу Cantata!</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup Стандартная серверная многопользовательская настройка <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup Базовая настройка для одного пользователя <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details Подробности соединения The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Настройки ниже — это базовые настройки, необходимые программе Cantata. Введите необходимые детали и нажмите кнопку «Подключиться» для проверки соединения. Host: Хост: Password: Пароль: Music folder: Папка с музыкой: Connect Подключиться The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Параметр «Папка с музыкой» используется для поиска обложек, слов песен и т.п. Если запущенный экземпляр MPD является удалённым хостом, то этот параметр можно указать в виде HTTP URL. Music folder Папка с музыкой Please choose the folder containing your music collection. Выбрать папку, в которой хранится ваша музыкальная коллекция. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> <p>Cantata загружает отсутствующие обложки и слова песен из Интернета.</p><p>Для каждого загруженного элемента необходимо подтверждать, сохранять ли его в папке с музыкой или в папках с персональным кешем пользователя</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder Сохранять загруженные тексты песен в папке с музыкой Save downloaded backdrops in music folder Сохранять загруженный фон/оформление в папке с музыкой If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Если в параметрах Cantata указано хранить слова песен, обложки дисков или фоновое оформление внутри папки с музыкой, но у пользователя нет доступа на запись в эту папку, то Cantata будет сохранять эти файлы в папке кеша пользователя. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Параметром для «Папки с музыкой» указан адрес HTTP, но в настоящий момент Cantata не может загружать файлы на внешние серверы HTTP. Поэтому параметры, указанные выше, нужно оставить без изменений. Finished! Готово! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata теперь настроен! <br/><br/>Диалоговое окно конфигурации может быть использовано для настройки внешнего вида Cantata, а также для добавления дополнительных хостов MPD и т.д. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. Cantata группирует треки в альбомы с помощью тега «AlbumArtist», если он указан. В противном случае будет использован тег «Исполнитель». При наличии альбома с несколькими исполнителями, тег «AlbumArtist» <b>должен</b> быть указан для корректной работы группировки треков. В этом сценарии рекомендуется использовать тег «Несколько исполнителей». <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. <b>Предупреждение:</b> вы не являетесь членом группы 'users'. Функционал программы Cantata улучшится (сохранение с правильными правами обложек, слов песен и т.п.) если вы (или ваш администратор) добавите своего пользователя в эту группу. Если вы будете делать это самостоятельно, то затем, для применения настройки, будет необходимо закончить текущий сеанс пользователя и снова войти в систему. Not Connected Не подключено Connection Established Соединение установлено Connection Failed Сбой подключения Cantata will now terminate InputDialog Password Пароль Please enter password: Введите пароль: InterfaceSettings Sidebar Боковая панель Views Вид Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options Параметры Style: Стиль: Position: Позиция: Only show icons, no text Показывать только значки Auto-hide Автоматически скрывать Play Queue Проиграть очередь Initially collapse albums Изначально альбомы свернуты Automatically expand current album Автоматически раскрывать текущий альбом Scroll to current track Прокручивать список до текущего трека Prompt before clearing Запрос перед удалением Separate action (and shortcut) for play queue search Отдельное действие (и сочетание клавиш) для поиска в очереди воспроизведения. Background Image Фоновое изображение None Нет Current album cover Текущая обложка альбома Custom image: Пользовательское изображение: Blur: Размытие: 10px Opacity: Непрозрачность: 40% 40% Toolbar Панель инструментов Show stop button Показывать кнопку «стоп» Show cover of current track Показывать обложку альбома текущего трека Show track rating Показывать рейтинг трека External Внешнее Enable MPRIS D-BUS interface Show popup messages when changing tracks Показать всплывающие сообщения при смене дорожек Show icon in notification area Показывать значок в области уведомлений Minimize to notification area when closed Минимизировать в область уведомлений при закрытии On Start-up При запуске Show main window Показать главное окно Hide main window Скрыть главное окно Restore previous state Восстановить прежнее состояние Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks Отдельные треки If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General Общее Fetch missing covers from Last.fm Получить недостающие обложки из Last.fm Show delete action in context menus Показать действие удаления в контекстных меню Enforce single-click activation of items Включать одним щелчком Changing the style setting will require a re-start of Cantata. <p>This will change Cantata's interface as detailed: <ul><li>Playback, and control, buttons will be 33% wider</li><li>Views will be 'flickable'</li><li>To drag items, you will need to 'touch' the top-left corner</li><li>Scrollbars will only be a few pixels wide</li><li>Actions (e.g. 'Add To Play Queue') will always be visible (not just when item is under mouse)</li><li>Spin buttons will have + and - buttons at the side of the text field</li></ul></p> <p>Это действие изменит интерфейс Cantata следующим образом: <ul><li>Кнопки проигрывания и управления станут на 33% шире</li><li>Окна будут «скользить» при прокрутке</li><li>Чтобы протащить элемент, нужно будет «прикоснуться» к верхнему левому углу</li><li>Панели прокрутки будут всего несколько пикселей в ширину</li><li>Действия, (например, «Добавить в очередь воспроизведения») всегда будут видимыми (а не только когда элемент находится точно под курсором)</li><li>Кнопки увеличения/уменьшения будут иметь дополнительные кнопочки «+» и «-» сбоку текстового поля</li></ul></p> Make interface more touch friendly Более дружественный сенсорный интерфейс Show song information tooltips Показывать подсказки с информацией о песне Support retina displays Поддержка дисплеев Retina Language: Язык: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Изменение параметра «Включать одним щелчком» требует перезапуска Cantata Changing the language setting will require a re-start of Cantata. Изменение языкового параметра требует перезапуска Cantata Changing the 'touch friendly' setting will require a re-start of Cantata. Изменение параметра «Более дружественный сенсорный интерфейс» требует перезапуска Cantata Enabling support for retina displays will produce sharper icons on the retina display, but may produce less sharp icons on non-retina displays. Changing this setting will require a re-start of Cantata. Включение поддержки для дисплеев Retina сделает иконки более чёткими на дисплеях Retina, и более расплывчатыми на других устройствах. Изменение этого параметра потребует перезапуска Cantata. Library Folders Папки Playlists Плейлисты Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Устройства - UMS, MTP (например Android) и AudioCDs Search (via MPD) Поиск (через MPD) Info - Current song information (artist, album, and lyrics) Информация - текущая информация о песне (исполнитель, альбом и текст) Large Большой Small Маленький Tab-bar Панель вкладок Left Слева Right Справа Top Сверху Bottom Снизу Images (*.png *.jpg) Изображения (*.png *.jpg) 10px pixels Notifications Уведомления English (en) System default Системный %1% value% %1% %1 px pixels %1 пикс ItemView Go Back Назад Updating... Обновление... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings Настройки Jamendo MP3 MP3 Ogg Ogg Streaming format: Формат потока: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Нет Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm Больше информации см. на last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name Имя Year Год Album, Artist, Year Альбом, Артист, Год Album, Year, Artist Альбом, Год, Артист Artist, Album, Year Артист, Альбом, Год Artist, Year, Album Артист, Год, Альбом Year, Album, Artist Год, Альбом, Артист Year, Artist, Album Год, Артист, Альбом Modified Date Group By Genre Жанр Artist Исполнитель Album Альбом Are you sure you wish to delete the selected songs? This cannot be undone. Точно удалить выбранные песни? Это действие нельзя будет отменить. Delete Songs Удалить песни LyricSettings Choose the websites you want to use when searching for lyrics. Сайты для поиска текстов песен LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. В случае если Cantata не нашла текста песни, или нашла неправильный вариант, введите здесь новые ключевые слова для поиска. Например, текущая песня может быть ковер-версией, в таком случае может помочь поиск слов от изначального исполнителя . Если этот поиск не принесёт новых результатов, слова песни будут привязаны к названию оригинала и к изначальному артисту, как это показано в Cantata. Title: Название: Artist: Исполнитель: Search For Lyrics Искать слова песни MPDConnection Unknown Неизвестно Connection to %1 failed Сбой подключения к %1 Connection to %1 failed - please check your proxy settings Сбой подключения к %1 — проверьте параметры прокси. Connection to %1 failed - incorrect password Сбой подключения к %1 — неправильный пароль Connecting to %1 Подключение к %1 Failed to send command to %1 - not connected Сбой передачи команды для %1 — нет подключения Failed to load. Please check user "mpd" has read permission. Сбой загрузки. Проверьте права на чтение для пользователя «mpd». Failed to load. MPD can only play local files if connected via a local socket. Сбой загрузки. MPD может проигрывать локальные файлы только при подключении к локальному сокету. MPD reported the following error: %1 MPD выдал следующую ошибку: %1 Failed to send command. Disconnected from %1 Сбой передачи команды. Отключено от %1. Failed to rename <b>%1</b> to <b>%2</b> Сбой переименования <b>%1</b> в <b>%2</b> Failed to save <b>%1</b> Сбой сохранения <b>%1</b> You cannot add parts of a cue sheet to a playlist! Нельзя добавлять фрагменты файла cue sheet в плейлист. You cannot add a playlist to another playlist! Нельзя добавлять плейлист к другому плейлисту. Failed to send '%1' to %2. Please check %2 is registered with MPD. Не удалось послать «%1» на %2. Проверьте, зарегистрирован ли %2 в MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Нет Streaming Потоковая передача MP3 128k MP3 128k MP3 VBR MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Настройки Magnatune Username: Пользователь: Password: Пароль: Membership: Членство: Downloads: Загрузки: MainWindow [Dynamic] [Динамический] Exit Full Screen Выход из полноэкранного режима Configure Cantata... Настроить Cantata... Preferences Настройки Quit Выход About Cantata... О программе Cantata... Show Window Показать окно Server information... Сведения о сервере... Refresh Database Обновить базу данных Refresh Обновить Connect Подключиться Collection Коллекция Outputs Выводы Stop After Track Остановить после трека Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist Добавить в сохраняемый плейлист Crop Others Add Stream URL Добавить URL потока Clear Очистить Center On Current Track Центр по текущей дорожке Expanded Interface Расширенный интерфейс Show Current Song Information Показать инфо текущей песни Full Screen Полный экран Random Случайно Repeat Повтор Single Одиночный When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. «Одиночный» режим: воспроизведение останавливается после текущей песни, или, в случае активного режима «Повтор», текущая песня повторяется Consume Поглощение When consume is activated, a song is removed from the play queue after it has been played. Режим «Поглощение»: песня удаляется из очереди после того, как была проиграна Find in Play Queue Найти в очереди на воспроизведение Play Stream Воспроизводить поток Locate In Library Найти в библиотеке Play next Edit Track Information (Play Queue) Expand All Развернуть всё Collapse All Свернуть всё Cancel Отмена Play Queue Проиграть очередь Library Folders Папки Playlists Плейлисты Internet Devices Устройства Search Поиск Info Инфо Show Menubar Показать меню &Music &Музыка &Edit &Редактировать &View &Вид &Queue &Очередь &Settings &Настройки &Help &Помощь Set Rating Указать рейтинг No Rating Без рейтинга Failed to locate any songs matching the dynamic playlist rules. Не удалось найти песен, подходящих под правила динамического плейлиста. Connecting to %1 Подключение к %1 Refresh MPD Database? Обновить базу данных MPD? About Cantata О программе Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Художественное оформление контекстного вида предоставлено сайтом <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Метаданные контекстного вида предоставлены сайтами <a href="http://www.wikipedia.org">Wikipedia</a> и <a href="http://www.last.fm">Last.fm</a>. Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> Вы можете выкладывать ваши творческие работы на темы музыкального фан-арта на сайт <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. В настоящее время идёт загрузка подкаста. Выключение прекратит загрузку. Abort download and quit Прервать загрузку и выключить Please close other dialogs first. Сначала необходимо закрыть другие диалоги. Enabled: %1 Включено: %1 Disabled: %1 Отключено: %1 Server Information Сведения о сервере <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Сервер</b></td></tr><tr><td align="right">Протокол:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Аптайм:&nbsp;</td><td>%4</td></tr><tr><td align="right">Играя:&nbsp;</td><td>%5</td></tr><tr><td align="right">Обработчики:&nbsp;</td><td>%6</td></tr><tr><td align="right">Тэги:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> <tr><td colspan="2"><b>База данных</b></td></tr><tr><td align="right">Артисты:&nbsp;</td><td>%1</td></tr><tr><td align="right">Альбомы:&nbsp;</td><td>%2</td></tr><tr><td align="right">Песни:&nbsp;</td><td>%3</td></tr><tr><td align="right">Продолжительность:&nbsp;</td><td>%4</td></tr><tr><td align="right">Обновление:&nbsp;</td><td>%5</td></tr> Cantata (%1) Cantata (%1) MPD reported the following error: %1 MPD выдал следующую ошибку: %1 Cantata Cantata Playback stopped Воспроизведение остановлено Remove all songs from play queue? Удалить все песни из очереди? Priority Приоритет Enter priority (0..255): Введите приоритет (0..255): Decrease priority for each subsequent track Playlist Name Имя плейлиста Enter a name for the playlist: Введите имя плейлиста: '%1' is used to store favorite streams, please choose another name. «%1» используется для хранения «любимых» потоков, выберите другое название. A playlist named '%1' already exists! Add to that playlist? Список воспроизведения с названием «%1» уже существует. Добавить к этому списку? Existing Playlist Существующие плейлисты %n Track(s) треков: %n треков: %n треков: %n %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) MenuButton Menu Меню MessageOverlay Cancel Отмена Mpris (Stream) (Поток) MtpConnection Connecting to device... Подключение к устройству... No devices found Устройства не обнаружены Connected to device Подключено к устройству Disconnected from device Отключено от устройства Updating folders... Обновление папок... Updating files... Обновление файлов... Updating tracks... Обновление треков... MtpDevice Not Connected Не подключено %1 free %1 свободно MusicBrainz Failed to open CD device Сбой открытия устройства CD Track %1 Трек %1 %1 (Disc %2) %1 (диск %2) No matches found in MusicBrainz Совпадений в MusicBrainz не найдено. MusicLibraryModel Cue Sheet Cue Sheet Playlist Плейлист %n Track(s) треков: %n треков: %n треков: %n %n Artist(s) исполнителей: %n исполнителей: %n исполнителей: %n %n Album(s) альбомов: %n альбомов: %n альбомов: %n %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) %1 by %2 Album by Artist %1 исполняет %2 NoteLabel <i><b>NOTE:</b> %1</i> <i><b>Примечание:</b> %1</i> NowPlayingWidget (Stream) (Поток) OSXStyle &Window &Окно Close Закрыть Minimize Свернуть Zoom Масштаб OnlineDbService Downloading...%1% Parsing music list.... Failed to download Сбой загрузки %n Artist(s) исполнителей: %n исполнителей: %n исполнителей: %n OnlineDbWidget Group By Genre Жанр Artist Исполнитель Configure Настройка The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Скачать Re-download music listing? OnlineSearchService Searching... Идёт поиск... OnlineSearchWidget No tracks found. Треки не найдены. %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Используйте флажки ниже, чтобы настроить список активных служб. Configure Service Настройка сервиса OnlineView Song Information Информация о песне OnlineXmlParser Failed to parse Сбой обработки OpmlBrowsePage Reload Обновить Failed to download directory listing Не удалось загрузить содержимое каталога Failed to parse directory listing Не удалось проанализировать содержимое каталога OtherSettings Background Image Фоновое изображение None Нет Artist image Изображение исполнителя Custom image: Пользовательское изображение: Blur: Размытие: 10px Opacity: Непрозрачность: 40% 40% Automatically switch to view after: Автоматически переключаться на окно после: Do not auto-switch Без автоматического переключения ms мс Dark background Тёмный фон Darken background, and use white text, regardless of current color palette. Затемнить фон и использовать белый текст, независимо от текущей цветовой палитры. Always collapse into a single pane Всегда сворачиваться в одну область Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Показывать только теги «Исполнитель», «Альбом» или «Трек» даже при наличии места для показа всего дерева. Only show basic wikipedia text Показывать только основной текст wikipedia Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Cantata показывает только урезанные версии страниц wikipedia (без изображений, ссылок и т.д.). Это обрезание не всегда является корректным, поэтому Cantata по умолчанию показывает только введение. Если выбрать показ статьи целиком, могут возникнуть ошибки обработки. Также потребуется удалить текущий кеш статей (с помощью страницы «Кеш»). Images (*.png *.jpg) Изображения (*.png *.jpg) 10px pixels %1% value% %1% %1 px pixels %1 пикс PathRequester Select Folder Выбрать папку Select File Выбрать файл PlayQueueModel Title Название Artist Исполнитель Album Альбом # Track number Length Длина Disc Диск Year Год Original Year Genre Жанр Priority Приоритет Composer Автор музыки Performer Исполнитель Rating Рейтинг Remove Duplicates Удаление дубликатов Undo Отменить Redo Повторить Shuffle Перемешать Tracks Дорожки Albums Альбомы Sort By Сортировать по Album Artist Исполнитель альбома Track Title Название трека Track Number # (Track Number) PlayQueueView Remove Удалить PlaybackSettings Playback Воспроизведение Fa&deout on stop: None Нет ms мс Stop playback on exit Остановить воспроизведение при выходе из программы Inhibit suspend whilst playing Блокировать активацию ждущего режима во время проигрывания If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) При нажатии и удержании кнопки «Стоп» будет показано меню, предлагающее остановить воспроизведение немедленно или после окончание проигрываемого трека. (Показ кнопки «Стоп» можно включить в разделе «Интерфейс/Панель инструментов».) Output Вывод <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> <i>Не подключено.<br/>Элементы ниже нельзя изменять, т.к. Cantata не подключена к MPD.</i> &Crossfade between tracks: s s Replay &gain: About replay gain Информация о replay gain Use the checkboxes below to control the active outputs. Используйте флажки ниже, чтобы контролировать активные выходы. Track Трек Album Альбом Auto Авто <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> <i>Подключено к %1<br/>Элементы ниже относятся к текущей подключённой коллекции MPD.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> Replay Gain — это стандарт, предложенный в 2001 году, для нормализации воспринимаемой громкости компьютерных аудио-форматов, таких, как MP3 и Ogg Vorbis. Он работает на базе трека/альбома, и на данный момент поддерживается всё большим количеством плееров.<br/><br/>Следующие параметры ReplayGain можно использовать:<ul><li><i>Нет</i>: ReplayGain не применяется.</li><li><i>Трек</i>: Громкость будет настроена с использованием тегов ReplayGain трека.</li><li><i>Альбом</i>: Громкость будет настроена с использованием тегов ReplayGain альбома.</li><li><i>Автоматически</i>: в случае, если включён режим случайного воспроизведения, громкость будет настроена с использованием тегов ReplayGain трека, в противном случае используются теги альбома.</li></ul> PlaylistRule Type: Тип: Include songs that match the following: Включить песни, отвечающие следующему условию: Exclude songs that match the following: Исключить песни, отвечающие следующему условию: Artist: Исполнитель: Artists similar to: Найти похожих исполнителей: Album Artist: Исполнитель альбома: Composer: Автор музыки: Album: Альбом: Title: Название: Genre Жанр From Year: От года: Any Любой To Year: До года: Comment: Комментарий: Filename / path: Exact match Точное совпадение Only enter values for the tags you wish to be search on. Вводите значения только тех тегов, среди которых нужно производить поиск. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. Для указания жанров заканчивайте каждую строку знаком звёздочки (*). Например, жанру «рок*» будет соответствовать и «хард-рок» и «рок-н-ролл». PlaylistRuleDialog Dynamic Rule Динамическое правило Smart Rule Add Добавить <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>ОШИБКА</b>: значение «с года» должно быть меньше значения «до года»</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>ОШИБКА:</b> слишком большой диапазон даты (максимум равен %1 годам)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules Имя динамических правил Add Добавить Edit Изменить Remove Удалить Songs with ratings between: - - Songs with duration between: seconds секунд Number of songs in play queue: Order songs: About Rules О правилах PlaylistRulesDialog Dynamic Rules Динамические правила None Нет No Limit About dynamic rules О динамических правилах Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 Не удалось сохранить %1 A set of rules named '%1' already exists! Overwrite? Набор правил с названием «%1» уже существует. Перезаписать? Overwrite Rules Перезаписать правила Saving %1 Сохранение %1 PlaylistsModel New Playlist... Новый плейлист... Stored Playlists Standard playlists %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) Smart Playlist Смарт-плейлист Plurals %n Track(s) треков: %n треков: %n треков: %n %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) %n Album(s) альбомов: %n альбомов: %n альбомов: %n %n Artist(s) исполнителей: %n исполнителей: %n исполнителей: %n %n Stream(s) потоков: %n потоков: %n потоков: %n %n Entry(s) элементов: %n элементов: %n элементов: %n %n Rule(s) подкаст: %n подкаст: %n подкаст: %n %n Podcast(s) подкастов: %n подкастов: %n подкастов: %n %n Episode(s) серий: %n серий: %n серий: %n %n Update(s) available Доступно обновлений: %n Доступно обновлений: %n Доступно обновлений: %n PodcastPage RSS: RSS: Website: Сайт: Podcast details Подробности подкаста Select a podcast to display its details Выбрать подкаст для показа подробностей PodcastSearchDialog Subscribe Подписаться Enter URL Введите URL-адрес Manual podcast URL Ручной URL-адрес подкаста Search %1 Искать %1 Search for podcasts on %1 Поиск подкастов на %1 Add Podcast Subscription Добавить подкаст подписку Browse %1 Просмотреть %1 Browse %1 podcasts Просмотреть %1 подкастов You are already subscribed to this podcast! Вы уже подписаны на этот подкаст Subscription added Подписка добавлена PodcastSearchPage Enter search term... Введите поисковый запрос… Search Поиск Failed to fetch podcasts from %1 Сбой получения подкаста с %1 There was a problem parsing the response from %1 При выполнении обработки ответа от %1 возникла ошибка PodcastService Subscribe to RSS feeds %n Podcast(s) подкастов: %n подкастов: %n подкастов: %n %1 (%2) podcast name (num unplayed episodes) %1 (%2) %n Episode(s) серий: %n серий: %n серий: %n (Downloading: %1%) (Идёт загрузка: %1%) Failed to parse %1 Сбой обработки %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Cantata поддерживает только аудио-подкасты, а %1 содержит только видео-подкасты. Failed to download %1 Не удалось загрузить %1 PodcastSettingsDialog Check for new episodes: Проверить наличие новых серий: Download episodes to: Загрузить серии в: Download automatically: Загружать автоматически: Podcast Settings Настройки подкастов Manually Вручную Every 15 minutes Каждые 15 минут Every 30 minutes Каждые 30 минут Every hour Каждый час Every 2 hours Каждые 2 часа Every 6 hours Каждые 6 часов Every 12 hours Каждые 12 часов Every day Каждый день Every week Каждую неделю Don't automatically download episodes Не загружать серии автоматически Latest episode Самая последняя серия Latest %1 episodes Последние %1 серии All episodes Все серии PodcastUrlPage URL URL Enter podcast URL... Введите URL-адрес подкаста... Load Загрузить Enter podcast URL below, and press 'Load' Введи ниже адрес URL подкаста и нажмите «Загрузить» Invalid URL! Недопустимый URL. Failed to fetch podcast! Не удалось загрузить подкаст! Failed to parse podcast. Не удалось проанализировать подкаст. Cantata only supports audio podcasts! The URL entered contains only video podcasts. Cantata поддерживает только аудио-подкасты! URL-адрес содержит только видео подкасты. PodcastWidget Add Subscription Добавить подписку Remove Subscription Удалить подписку Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Отменить подписку на «%1»? Do you wish to download the selected podcast episodes? Загрузить выбранные серии подкаста? Cancel podcast episode downloads (both current and any that are queued)? Отменить загрузку серий подкаста (как текущую, так и стоящие в очереди на загрузку)? Do you wish to the delete downloaded files of the selected podcast episodes? Удалить загруженные файлы выбранной серии подкаста? Do you wish to mark the selected podcast episodes as new? Отметить выбранные серии подкаста как новые? Do you wish to mark the selected podcast episodes as listened? Отметить выбранные серии подкаста как уже прослушанные? Refresh all subscriptions? Refresh Обновить Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track Cantata проигрывает трек PreferencesDialog Collection Коллекция Collection Settings Настройки коллекции Playback Воспроизведение Playback Settings Настройки воспроизведения Downloaded Files Downloaded Files Settings Interface Интерфейс Interface Settings Настройки интерфейса Info Инфо Info View Settings Scrobbling Скробблинг Scrobbling Settings Параметры скробблинга Audio CD Аудио-CD Audio CD Settings Настройки аудио-CD Proxy Прокси Proxy Settings Настройки прокси Shortcuts Комбинации клавиш Keyboard Shortcut Settings Настройки комбинаций клавиш Cache Кеш Cached Items Кешированные элементы Custom Actions Cantata Preferences Настроить Cantata Configure Настройка ProxySettings Mode: Режим: Type: Тип: HTTP Proxy HTTP-прокси SOCKS Proxy SOCKS прокси Host: Хост: Port: Порт: Username: Пользователь: Password: Пароль: No proxy Без прокси Use the system proxy settings Использовать системные настройки прокси Manual proxy configuration Ручная настройка прокси QObject Track listing Список дорожек Read more on wikipedia Доп. информация в Википедии Open in browser Открыть в браузере <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://ru.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) — это запатентованный кодек для цифрового аудио, использующий сжатие с потерями. <br>Как правило, AAC предоставляет лучшее качество звука, чем MP3 при аналогичном битрейте. Это разумный выбор для iPod и некоторых других портативных плееров. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодировщик <b>AAC</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека.<br>Битрейт, равный <b>150кбит/с</b>, — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>120 кбит/с</b> могут приводить к неудовлетворительному качеству. Значения больше <b>200 кбит/с</b> обычно излишни. Expected average bitrate for variable bitrate encoding Ожидаемое среднее значение битрейта для кодирования переменного битрейта. Smaller file Меньший размер файла Better sound quality Лучшее качество звука <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://ru.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) — это запатентованный кодек цифрового аудио, использующий сжатие данных с потерями.<br>Несмотря на свои недостатки, он является общепризнанным форматом для любительского хранения аудио, и широко поддерживается портативными музыкальными плеерами. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодер <b>MP3</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/MP3#VBR>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека. <br>Битрейт, равный <b>160 кбит/с</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>120 кбит/с</b> могут приводить к неудовлетворительному качеству. Значения больше <b>205 кбит/с</b> обычно излишни. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://ru.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> — это открытый, не требующий финансовых отчислений кодек для сжатия аудио с потерями.<br>Файлы этом формате имеют меньший размер, чем файлы MP3, при аналогичномили более высоком качестве.Ogg Vorbis — отличный и универсальный выбор, особенно для поддерживающих его портативных плееров. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодировщик <b>Vorbis</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_details>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br> Кодер Vorbis имеет параметр качества «-q», принимающий значения от 1 до 10, который задаёт ожидаемый уровень качества звука. Битрейт, устанавливаемый этим параметром — это только оценочное значение <a href=http://www.ffmpeg.org/faq.html#SEC21>среднего битрейта</a> кодируемого трека. В более новых и более эффективных версиях Vorbis каждому значению качества соответствует меньший битрейт.<br>Параметр качества, равный <b>-q5</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>-q3</b> могут приводить к неудовлетворительному качеству. Значения больше <b>-q8</b> обычно излишни. Quality rating Рейтинг по качеству Opus Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> является патентно-бесплатным цифровым аудио кодеком, используемым форму сжатия с потерями данных. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Кодер <b>Opus</b>, используемый в Cantata, имеет поддержку <a href=http://en.wikipedia.org/wiki/Variable_bitrate>переменного битрейта (VBR)</a>, что означает, что битрейт может колебаться на протяжении трека в зависимости от сложности звукового содержимого. Более сложные временные периоды трека кодируются с бо́льшим битрейтом, чем более простые. Этот подход позволяет достигнуть лучшего соотношения качества звука и размера файла по сравнению с использованием постоянного битрейта.<br>Поэтому битрейт, устанавливаемый этим ползунком — это только оценочное значение среднего битрейта кодируемого трека. <br>Битрейт, равный <b>128 кбит/с</b> — хороший выбор для прослушивания музыки на портативном плеере.<br/>Значения меньше <b>100 кбит/с</b> могут приводить к неудовлетворительному качеству музыки. Значения больше <b>256 кбит/с</b> обычно излишни. Bitrate Битрейт Apple Lossless Apple Lossless <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://ru.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) — аудио-кодек для сжатия цифрового аудио без потерь.<br>Рекомендуется только для музыкальных плееров компании Apple и плееров, не поддерживающих формат FLAC. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://ru.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC) — открытый и не требующий финансовых отчислений кодек для сжатия цифрового аудио без потерь.<br>FLAC — прекрасный выбор для тех, кто хочет хранить музыку, не экономя на качестве. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. <a href=http://flac.sourceforge.net/documentation_tools_flac.html>Уровень сжатия</a> — это целое число со значением от 0 до 8, представляющее оптимальное соотношение между размером файла и скоростью сжатия во время кодирования в формат <b>FLAC</b>.<br/> Уровень сжатия, равный <b>0</b>, даёт самое короткое время сжатия, но создаёт относительно большой файл.<br/>С другой стороны, уровень сжатия <b>8</b> потребует много времени на процесс сжатия, но размер созданного файла будет самым маленьким.<br/>Поскольку, по определению, FLAC — это кодек со сжатием без потерь, то качество аудио на выходе не изменится, вне зависимости от уровня сжатия.<br/>Кроме того, уровни сжатия более <b>5</b> очень сильно увеличивают время сжатия, но лишь незначительно уменьшают размер файла по сравнению с более низкими уровнями сжатия, и поэтому не рекомендуются. Compression level Уровень сжатия Faster compression Быстрое сжатие Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://ru.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) — это проприетарный кодек, разработанный компанией Microsoft для сжатия аудио (с потерями).<br>Рекомендуется только для портативных аудиопроигрывателей, не поддерживающих Ogg Vorbis. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Битрейт — это мера количества информации, используемой для представления одной секунды звукового трека.<br>Из-за ограничений проприетарного формата <b>WMA</b> и трудностей реверс-инжиниринга проприетарного кодировщика, кодировщик WMA, используемый программой Cantata, имеет настройку <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>постоянный битрейт (CBR)</a>.<br>Поэтому битрейт, устанавливаемый этим ползунком — это довольно точное оценочное значение битрейта кодируемого трека. Битрейт, равный <br><b>136кбит/с</b> — хороший выбор для музыки, прослушиваемой в портативном плеере. <br/>Любые значения меньше <b> 112кбит/с</b> приведут к неудовлетворительному качеству музыки, а значения <b>182кбит/с</b> просто излишни. Empty filename. Пустое имя файла. Invalid filename. (%1) Недопустимое имя файла. (%1) Failed to save %1. Не удалось сохранить %1 Failed to delete rules file. (%1) Сбой удаления файла правил (%1) Invalid command. (%1) Недопустимая команда. (%1) Could not remove active rules link. Не удалось удалить ссылку на активные правила. Active rules is not a link. Активные правила не являются ссылкой. Could not create active rules link. Не удалось создать ссылку на активные правила. Rules file, %1, does not exist. Файл правил %1 не существует. Incorrect arguments supplied. Неверные аргументы. Unknown method called. Неизвестный называемый метод. Unknown error Неизвестная ошибка Artist Исполнитель SimilarArtists SimilarArtists AlbumArtist AlbumArtist Composer Автор музыки Comment Комментарий Album Альбом Title Название Genre Жанр Date Дата File Include Включить Exclude Исключить (Exact) (Точное) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height %1 %2 x %3 Current Cover Текущая обложка CoverArt Archive CoverArt Archive Grouped Albums Отсортированные альбомы Table Таблица Parse in Library view, and show in Folders view Only show in Folders view Do not list %1 Tracks Plural (N!=1) %1 треков 1 Track (%1) Singular 1 трек (%1) %1 Tracks (%2) Plural (N!=1) %1 треков (%2) %1 Albums Plural (N!=1) %1 альбомов %1 Artists Plural (N!=1) %1 исполнителей %1 Streams Plural (N!=1) %1 потоков %1 Entries Plural (N!=1) %1 элементов %1 Rules Plural (N!=1) %1 правил %1 Podcasts Plural (N!=1) %1 подкастов %1 Episodes Plural (N!=1) %1 серий %1 Updates available Plural (N!=1) Доступно обновлений: %1 Previous Track Предыдущий трек Next Track Следующий трек Play/Pause Воспр./Пауза Stop Остановить Stop After Current Track Остановить после текущего трека Stop After Track Остановить после трека Increase Volume Увеличить громкость Decrease Volume Уменьшить громкость Save As Сохранить как Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority Добавить с приоритетом Set Priority Настроить приоритет Highest Priority (255) Высший приоритет (255) High Priority (200) Высокий приоритет (200) Medium Priority (125) Средний приоритет (125) Low Priority (50) Низкий приоритет (50) Default Priority (0) Приоритет по умолчанию (0) Custom Priority... Пользовательское значение приоритета... Add To Playlist Добавить в плейлист Organize Files Систематизировать файлы Edit Track Information Редактировать информацию о треке ReplayGain ReplayGain Copy Songs To Device Копировать песни на устройство Delete Songs Удалить песни Set Image Указать изображение Remove Удалить Find Поиск Add To Play Queue Добавить в очередь воспроизведения Parse error loading cache file, please check your songs tags. Other Другое Default По умолчанию "%1" name (host) «%1» "%1" (%2:%3) name (host:port) «%1» (%2:%3) Single Tracks Отдельные треки Personal Частный Unknown Неизвестно Various Artists Несколько исполнителей Album artist Исполнитель альбома Performer Исполнитель Track number Номер трека Disc number Номер диска Year Год Orignal Year Length Длина <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> на <b>%2</b> <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album <b>%1</b> исполнителя <b>%2</b> на <b>%3</b> Invalid service Недопустимая служба Invalid method Недопустимый метод Authentication failed Ошибка аутентификации Invalid format Недопустимый формат Invalid parameters Недопустимые параметры Invalid resource specified Недопустимый ресурс Operation failed Ошибка операции Invalid session key Недействительный ключ сеанса Invalid API key Недопустимый ключ API Service offline Сервис недоступен Last.fm is currently busy, please try again in a few minutes Last.fm в настоящее время занят, повторите попытку через несколько минут Rate-limit exceeded Превышено ограничение скорости General Общее Digitally Imported Digitally Imported Local and National Radio (ListenLive) Местное и национальное радио (ListenLive) &OK &Ok &Cancel &Отмена &Yes &Да &No &Нет &Discard &Отклонить &Save &Сохранить &Apply &Применить &Close &Закрыть &Help &Помощь &Overwrite &Заменить &Reset &Сбросить &Continue &Продолжить &Delete &Удалить &Stop &Остановить &Remove &Удалить &Previous &Предыдущее &Next &Далее Close Закрыть Error Ошибка Information Инфо Warning Предупреждение Question Вопрос %1 B %1 Б %1 kB %1 кБ %1 MB %1 MБ %1 GB %1 ГБ %1 KiB %1 КиБ %1 MiB %1 МиБ %1 GiB %1 ГиБ Basic Tree (No Icons) Основные дерево (Без иконок) Simple Tree Простое дерево Detailed Tree Подробное дерево List Список Grid Сетка %n Track(s) треков: %n треков: %n треков: %n %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) %n Album(s) альбомов: %n альбомов: %n альбомов: %n %n Artist(s) исполнителей: %n исполнителей: %n исполнителей: %n %n Stream(s) потоков: %n потоков: %n потоков: %n %n Entry(s) элементов: %n элементов: %n элементов: %n %n Rule(s) подкаст: %n подкаст: %n подкаст: %n %n Podcast(s) подкастов: %n подкастов: %n подкастов: %n %n Episode(s) серий: %n серий: %n серий: %n %n Update(s) available Доступно обновлений: %n Доступно обновлений: %n Доступно обновлений: %n RemoteDevicePropertiesDialog Device Properties Свойства устройства Connection Соединение Music Library Музыкальная библиотека Add Device Добавить устройство A remote device named '%1' already exists! Please choose a different name. Удалённое устройство с названием «%1» уже существует. Выберите другое название. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Эти параметры можно изменять только при отключённом устройстве. Type: Тип: Name: Имя: Options Параметры Host: Хост: Port: Порт: User: Пользователь: Domain: Домен: Password: Пароль: Share: Общий ресурс: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Введённый здесь пароль будет храниться <b>незашифрованным</b> в конфигурационном файле Cantata. Чтобы заставить Cantata запрашивать пароль при каждой попытке доступа к общим ресурсам, укажите пароль «-». Service name: Имя сервиса: Folder: Папка: Extra Options: Дополнительные параметры: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. В связи с особенностями работы sshfs, для ввода пароля требуется подходящее приложение ssh-askpass (ksshaskpass, ssh-askpass-gnome, и т.п.). This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Этот диалог используется только для добавления удалённых устройств (например, с помощью Samba) или для доступа к локально смонтированным папкам. Обычные устройства, подключенные через USB, Cantata показывает автоматически в момент подключения. Samba Share Общий ресурс Samba Samba Share (Auto-discover host and port) Общий ресурс Samba (авто-определение порта и хоста) Secure Shell (sshfs) Secure Shell (sshfs) Locally Mounted Folder Локально смонтированная папка RemoteFsDevice Available Доступно Not Available Недоступно Failed to resolve connection details for %1 Сбой определения подробностей подключения для %1 Connecting... Соединение... Password prompting does not work when cantata is started from the commandline. Запрос паролей не работает, если cantata была запущена из командной строки. No suitable ssh-askpass application installed! This is required for entering passwords. Требуемое приложение ssh-askpass не установлено. Оно необходимо для ввода паролей. Mount point ("%1") is not empty! Точка монтирования («%1») не пуста. "sshfs" is not installed! «sshfs» не установлен Disconnecting... Отключение... "fusermount" is not installed! «fusermount» не установлен Failed to connect to "%1" Сбой подключения к «%1» Failed to disconnect from "%1" Сбой отключения от «%1» Updating tracks... Обновление треков... Not Connected Не подключено Capacity Unknown Ёмкость неизвестна %1 free %1 свободно RgDialog ReplayGain ReplayGain Show All Tracks Показать все треки Show Untagged Tracks Показать треки без тегов Remove From List Удалить из списка Artist Исполнитель Album Альбом Title Название Album Gain Album Gain Track Gain Track Gain Album Peak Album Peak Track Peak Track Peak Scan Сканировать Update ReplayGain tags in tracks? Обновить теги ReplayGain для треков? Update Tags Обновить теги Abort scanning of tracks? Прервать сканирование треков? Abort Прервать Abort reading of existing tags? Прервать чтение существующих тегов? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Сканировать <b>все</b> треки?<br/><br/><i>У каждого трека есть существующий тег ReplayGain.</i> Do you wish to scan all tracks, or only tracks without existing tags? Сканировать все треки, или только треки без существующих тегов? Untagged Tracks Треки без тегов All Tracks Все треки Scanning tracks... Сканирование треков... Reading existing tags... Чтение существующих треков... %1 (Corrupt tags?) filename (Corrupt tags?) %1 (испорченные теги?) Failed to update the tags of the following tracks: Сбой обновления тегов для следующих треков: Device has been removed! Устройство было удалено. Device is not connected. Устройство не подключено Device is busy? Устройство занято? %1 dB %1 дБ Failed Произошла ошибка Original: %1 dB Оригинал: %1 дБ Original: %1 Оригинал: %1 Remove the selected tracks from the list? Удалить выбранные треки из списка? Remove Tracks Удалить треки RulesPlaylists - Rating: %1..%2 - Рейтинг: %1..%2 Album Artist Исполнитель альбома Artist Исполнитель Album Альбом Composer Автор музыки Date Дата Genre Жанр Rating Рейтинг File Age Random Случайно %n Rule(s) подкаст: %n подкаст: %n подкаст: %n , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 %1 ошибка: %2 ScrobblingLove %1: Loved Current Track %1: текущий трек отмечен «любимым» %1: Love Current Track %1: отметить текущий трек как «любимый» ScrobblingSettings Scrobble using: Скробблить с помощью: Username: Пользователь: Password: Пароль: Status: Статус: Login Логин Scrobble tracks Скробблить треки Show 'Love' button Показывать кнопку «Любимый» %1 (via MPD) scrobbler name (via MPD) %1 (через MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. При использовании скробблера, обозначенного «(через MPD)» (например, %1), его необходимо вначале запустить. Cantata может отметить «Любимые» треки только с его помощью, и не может самостоятельно включать/отключать скробблинг. Authenticating... Вход... Authenticated Вход выполнен Not Authenticated Вход не выполнен ScrobblingStatus %1: Scrobble Tracks %1: скробблить треки SearchModel # (Track Number) SearchPage Locate In Library Найти в библиотеке Artist: Исполнитель: Composer: Автор музыки: Performer: Исполнитель: Album: Альбом: Title: Название: Genre: Жанр: Comment: Комментарий: Date: Дата: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Изменено: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Файл: Any: Любые: No tracks found. Треки не найдены. %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) SearchWidget Search... Поиск… Close Search Bar Закрыть панель поиска ServerSettings Collection: Коллекция: Name: Имя: Host: Хост: Password: Пароль: Music folder: Папка с музыкой: Cover filename: Имя файла обложки: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> <p>Имя файла (без расширения) для сохранения загруженных обложек.<br/>Если поле не заполнено, используется 'cover'.<br/><br/><i>%artist% будет заменён на значение исполнителя текущей песни, а %album% — на название альбома.</i></p> HTTP stream URL: Адрес потока HTTP: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. «Адрес потока HTTP» используется только в том случае, если MPD настроен на вывод в поток HTTP, и Cantata должна иметь возможность проигрывать этот поток. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. При изменении параметра «Папка с музыкой» необходимо вручную обновить базу данных композиций. Это можно сделать, нажав кнопку «Обновить базу данных» в окне «Исполнители» или «Альбомы». This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? К какому типу коллекции необходимо подключиться? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> <i><b>Примечание:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Добавить коллекцию Standard Стандартный Basic Базовый Delete '%1'? Удалить «%1»? Delete Удалить New Collection %1 Новая коллекция %1 Default По умолчанию ServiceStatusLabel Logged into %1 %1: вход выполнен <b>NOT</b> logged into %1 %1: вход <b>НЕ</b> выполнен ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: Поиск: Shortcut for Selected Action Горячие клавиши для выбранного действия Default: По умолчанию: None Нет Custom: Пользовательский: SinglePageWidget Refresh Обновить View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add Добавить Edit Изменить Remove Удалить Are you sure you wish to remove the selected rules? This cannot be undone. Точно удалить выбранные правила? Это действие нельзя отменить. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Нет доступа к файлам композиций! Проверьте параметр «папка с музыкой» в Cantata и параметр "music_directory" в MPD. Cannot access song files! Please check that the device is still attached. Нет доступа к файлам композиций. Проверьте, подключено ли устройство. SongView Lyrics Слова песни Information Инфо Metadata Метаданные Scroll Lyrics Прокрутка слов песни Refresh Lyrics Обновить слова песни Edit Lyrics Изменить слова песни Delete Lyrics File Удалить файл со словами песни Refresh Track Information Обновление информации о треках Cancel Отмена Track Трек Reload lyrics? Reload from disk, or delete disk copy and download? Загрузить слова песни повторно? Перезагрузить с диска или удалить копию с диска и загрузить заново? Reload Обновить Reload From Disk Загрузить с диска Download Скачать Current playing song has changed, still perform search? Текущая песня сменилась, произвести поиск всё равно? Song Changed Песня изменена Perform Search Произвести поиск Delete lyrics file? Удалить файл со словами песни? Delete File Удалить файл Artist Исполнитель Album artist Исполнитель альбома Composer Автор музыки Lyricist Поэт-песенник Conductor Дирижер Remixer Ремиксер Album Альбом Subtitle Субтитры Track number Номер трека Disc number Номер диска Genre Жанр Date Дата Original date Исходная дата Comment Комментарий Copyright Copyright Label Лейбл Catalogue number Номер по каталогу Title sort Сортировать название Artist sort Сортировать артист Album artist sort Сортировать исполнитель альбома Album sort Сортировать альбом Encoded by Закодировано с помощью Encoder Кодировщик Mood Настроение Media Медиа Bitrate Битрейт Sample rate Частота дискретизации Channels Каналы Tagging time Пометка времени Performer (%1) Исполнитель (%1) %1 kb/s %1 кбит/с %1 Hz %1 Гц Bits Бит Performer Исполнитель Year Год Filename Имя файла Fetching lyrics via %1 Загрузка слов песни с %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... Вычисление... Total space used: %1 Всего использовано места на диске: %1 SqlLibraryModel %n Artist(s) исполнителей: %n исполнителей: %n исполнителей: %n %n Album(s) альбомов: %n альбомов: %n альбомов: %n %n Tracks (%1) треков: %n (%1) треков: %n (%1) треков: %n (%1) Cue Sheet Cue Sheet Playlist Плейлист StoredPlaylistsPage Rename Переименовывать Remove Duplicates Удаление дубликатов Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Точно удалить выбранный список воспроизведения? Это действие нельзя отменить. Remove Playlists Удалить плейлист Playlist Name Имя плейлиста Enter a name for the playlist: Введите имя плейлиста: A playlist named '%1' already exists! Overwrite? Список воспроизведения с названием «%1» уже существует. Перезаписать? Overwrite Playlist Перезаписать плейлист Rename Playlist Переименовать плейлист Enter new name for playlist: Новое имя плейлиста: Cannot add songs from '%1' to '%2' Не может добавить песни из '%1' в '%2' StreamDialog Add stream to favourites Name: Имя: URL: URL: Add Stream Добавить поток Edit Stream Редактировать поток <i><b>ERROR:</b> Invalid protocol</i> <i><b>ОШИБКА:</b> недопустимый протокол</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Установлен Update available Доступно обновление Check the providers you wish to install/update. Отметьте источники, которые нужно установить/обновить. Install/Update Stream Providers Установить/обновить источники потоков Downloading list... Загрузка списка... Failed to download list of stream providers! Не удалось получить список источников потоков. Installing/updating %1 Идёт установка/обновление %1 Failed to install '%1' Сбой установки «%1» Failed to download '%1' Сбой загрузки «%1» Install/update the selected stream providers? Установить/обновить выбранные источники потоков? Install the selected stream providers? Установить выбранные источники потоков? Update the selected stream providers? Обновить выбранные источники потоков? Install/Update Установка/Обновление Abort installation/update? Прервать установку/обновление? Abort Прервать %n Update(s) available Доступно обновлений: %n Доступно обновлений: %n Доступно обновлений: %n Downloading %1 Идёт загрузка %1 Update all updateable providers Обновить все обновляемые источники StreamSearchModel TuneIn TuneIn ShoutCast ShoutCast Dirble Dirble Stream Search Search for radio streams Enter string to search Not Loaded Не загружено Loading... Загрузка... %n Entry(s) элементов: %n элементов: %n элементов: %n StreamSearchPage Added '%1'' to favorites Добавлено '%1' в избранное StreamsBrowsePage Import Streams Into Favorites Импортировать потоки в Избранное Export Favorite Streams Экспортировать избранные потоки Add New Stream To Favorites Добавить новый поток в Избранное Edit Изменить Seatch For Streams Configure Настройка Digitally Imported Service name Digitally Imported Import Streams Импортировать потоки XML Streams (*.xml *.xml.gz *.cantata) Потоки XML (*.xml *.xml.gz *.cantata) Export Streams Экспортировать потоки XML Streams (*.xml.gz) Потоки XML (*.xml.gz) Failed to create '%1'! Сбой создания «%1» Stream '%1' already exists! Поток «%1» уже существует A stream named '%1' already exists! Поток с названием «%1» уже существует Bookmark added Закладка добавлена Already bookmarked Уже в закладках Already in favorites Уже в Избранном Reload '%1' streams? Перезагрузить потоки «%1»? Are you sure you wish to remove bookmark to '%1'? Точно удалить закладку для «%1»? Are you sure you wish to remove all '%1' bookmarks? Точно удалить все закладки «%1»? Are you sure you wish to remove the %1 selected streams? Точно удалить все %1 выбранных потоков? Are you sure you wish to remove '%1'? Точно удалить «%1»? Added '%1'' to favorites Добавлено '%1' в избранное StreamsModel Bookmarks Закладки TuneIn TuneIn IceCast IceCast ShoutCast ShoutCast Dirble Dirble Favorites Избранное Bookmark Category Категория закладки Add Stream To Favorites Добавить поток в Избранное Configure Digitally Imported Reload Обновить Streams Потоки Radio stations Not Loaded Не загружено Loading... Загрузка... %n Entry(s) элементов: %n элементов: %n элементов: %n StreamsSettings Use the checkboxes below to configure the list of active providers. Используйте флажки ниже для настройки списка активных источников. Built-in categories are shown in italic, and these cannot be removed. Встроенные категории показаны курсивом, и их нельзя удалить. Configure Streams Настройка потоков From File... Из файла… Download... Загрузка… Configure Provider Настроить источник Install Установить Remove Удалить Install Streams Установить потоки Cantata Streams (*.streams) потоки Cantata (*.streams) A category named '%1' already exists! Overwrite? Категория с названием «%1» уже существует Перезаписать? Failed top open package file. Сбой открытия файла пакета Invalid file format! Недопустимый формат файла Failed to create stream category folder! Не удалось создать папку для категории потока Failed to save stream list! Не удалось сохранить список потоков Are you sure you wish to remove '%1'? Точно удалить «%1»? Failed to remove streams folder! Не удалось удалить папку с потоками. SyncCollectionWidget Search Поиск Check Items Отметить элементы Uncheck Items Сбросить отмеченные элементы SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize Синхронизировать Device and library are in sync. Устройство и библиотека синхронизированы Loading all songs from library, please wait...%1%... Local Music Library Properties Свойства локальной музыкальной библиотеки Device has been removed! Устройство было удалено. Device has been changed? Устройство было изменено? Device is busy? Устройство занято? TableView Stretch Columns To Fit Window Растянуть столбцы по размеру окна Left Слева Center Right Справа Alignment TagEditor Track: Трек: Title: Название: Artist: Исполнитель: Album artist: Исполнитель альбома: Composer: Автор музыки: Album: Альбом: Track number: Номер трека: Disc number: Номер диска: Genre: Жанр: Year: Год: Rating: Рейтинг: <i>(Various)</i> <i>(Разные)</i> Comment: Комментарий: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Рейтинги хранятся во внешней базе данных, а <b>не</b> в файле композиции. Tags Теги Tools Инструменты Apply "Various Artists" Workaround Применить обходное решение «несколько исполнителей» Revert "Various Artists" Workaround Откатить обходное решение «несколько исполнителей» Set 'Album Artist' from 'Artist' Взять значение «Исполнитель альбома» из значения «Исполнитель» Capitalize Перевести в верхний режим Adjust Track Numbers Настроить номера треков Read Ratings from File Прочесть рейтинги из файла Write Ratings to File Записать рейтинги в файл All tracks Все треки Apply "Various Artists" workaround to <b>all</b> tracks? Применить обходное решение «несколько исполнителей» для <b>всех</b> треков? Apply "Various Artists" workaround? Применить обходное решение «несколько исполнителей»? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>Эта настройка установит значения «Исполнитель альбома» и «Исполнитель» на «Несколько исполнителей» и значение «Название» на "TrackArtist - TrackTitle"</i> Revert "Various Artists" workaround on <b>all</b> tracks? Откатить обходное решение «несколько исполнителей» для <b>всех</b> треков? Revert "Various Artists" workaround Откатить обходное решение «несколько исполнителей» <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> <i>Там, где значение «Исполнитель альбома» равно значению «Исполнитель», и название имеет формат "TrackArtist - TrackTitle", значение «Исполнитель» будет получаться из «Названия», а само значение «Название» будет равно названию композиции, т.е. <br/><br/>Скажем, если «Название» имеет формат "Wibble - Wobble", тогда «Исполнитель» будет иметь значение «Wibble», а «Название» — «Wobble»</i> Revert Откатить Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? Установить значение «Исполнитель альбома» из значения «Исполнитель» (при пустом значении «Исполнитель альбома») для <b>всех</b> треков? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? Установить значение «Исполнитель альбома» из значения «Исполнитель» (при пустом значении «Исполнитель альбома») ? Album Artist from Artist Исполнитель альбома как Исполнитель Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Сделать начальную букву текстовых полей (напр.«Название», «Исполнитель» и т.д.) заглавной для <b>всех</b> треков? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Сделать начальную букву текстовых полей (напр.«Название», «Исполнитель» и т.д.) заглавной? Adjust the value of each track number by: Настроить значение номера каждого трека по: Adjust track number by: Настроить номера треков по: Read ratings for all tracks from the music files? Прочитать рейтинги всех треков из файлов композиций? Read rating from music file? Прочитать рейтинг из файла композиции? Ratings Рейтинги Read Ratings Прочитать рейтинги Read Rating Прочитать рейтинг Read, and updated, ratings from the following tracks: Считать и обновить рейтинги из следующих треков: Not all Song ratings have been read from MPD! Не все рейтинги композиций были прочитаны из MPD Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Рейтинги песен хранятся не в файлах композиций, а в базе данных MPD 'sticker'. Чтобы сохранить их в действительный файл, Cantata сначала должна считать их из MPD. Song rating has not been read from MPD! Рейтинги композиций не были считаны из MPD Write ratings for all tracks to the music files? Записать рейтинги всех треков в файлы композиций? Write rating to music file? Записать рейтинг в файл композиции? Write Ratings Записать рейтинги Write Rating Записать рейтинг Failed to write ratings of the following tracks: Не удалось записать рейтинги для следующих треков: Failed to write rating to music file! Не удалось записать рейтинг в файл композиции All tracks [modified] Все треки [изменено] %1 [modified] %1 [изменено] %1 (Corrupt tags?) filename (Corrupt tags?) %1 (испорченные теги?) Failed to update the tags of the following tracks: Сбой обновления тегов для следующих треков: Would you also like to rename your song files, so as to match your tags? Переименовать также и файлы песен, для соответствия тегам? Rename Files Переименовать файлы Rename Переименовывать Device has been removed! Устройство было удалено. Device is not connected. Устройство не подключено Device is busy? Устройство занято? TagSpinBox (Various) (Несколько) ThinSplitter Reset Spacing Перенастроить отступ TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Доступно: Selected: Выбрано: TrackOrganiser Filenames Имена файлов: Filename scheme: Схема имени файла: VFAT safe безопасный VFAT Use only ASCII characters Использовать только символы ASCII Replace spaces with underscores Заменить пробелы на подчеркивания Append 'The' to artist names Добавлять артикль 'The' к именам исполнителей на английском. Original Name Исходное название New Name Новое название Ratings will be lost if a file is renamed. При переименовании файла композиции, её рейтинг будет потерян. Organize Files Систематизировать файлы Rename Переименовывать Remove From List Удалить из списка Abort renaming of files? Прервать переименование файлов? Abort Прервать Source file does not exist! Файл-источник не существует Skip Пропустить Auto Skip Авто-пропуск Destination file already exists! Целевой файл уже существует Failed to create destination folder! Не удалось создать папку назначения Failed to rename '%1' to '%2' Не удалось переименовать «%1» в «%2» Remove the selected tracks from the list? Удалить выбранные треки из списка? Remove Tracks Удалить треки Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Рейтинги песен хранятся не в файлах композиций, а в базе данных MPD 'sticker'. При переименовании файла (или папки, в которой он хранится), рейтинг композиции будет потерян. Device has been removed! Устройство было удалено. Device is not connected. Устройство не подключено Device is busy? Устройство занято? TrayItem Cantata Cantata Now playing Проигрывается UltimateLyricsProvider (Polish Translations) (Переводы на польский) (Portuguese Translations) (Переводы на португальский) UmsDevice Not Scanned Не просканировано Not Connected Не подключено %1 free %1 свободно ValueSlider (recommended) (рекомендуется) View Cancel Отмена VolumeSlider Mute Заглушить звук Unmute Включить звук Volume %1% (Muted) Громкость %1% (звук отключён) Volume %1% Громкость %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | исполнитель|группа|певец|вокалист|музыкант album|score|soundtrack Search pattern for an album, separated by | альбом|рейтинг|саундтрек WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Выбрать языки Википедии, на которых будет искаться информация об исполнителе и об альбоме. Reload Обновить cantata-2.2.0/translations/cantata_zh_CN.ts000066400000000000000000016345231316350454000207320ustar00rootroot00000000000000 Album 专辑 Tracks i18n: file: devices/albumdetails.ui:136 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) 音轨 Artist i18n: file: devices/albumdetails.ui:163 i18n: ectx: property (text), widget (QTreeWidget, tracks) 艺术家 Albums 专辑 Web Links Web 链接 Similar Artists 相似艺术家 Title: i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) i18n: file: devices/albumdetails.ui:63 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: dynamic/dynamicrule.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:54 i18n: ectx: property (text), widget (StateLabel, titleLabel) 标题: Artist: i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) i18n: file: devices/albumdetails.ui:37 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: dynamic/dynamicrule.ui:50 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: tags/tageditor.ui:67 i18n: ectx: property (text), widget (StateLabel, artistLabel) 艺术家: Search For Lyrics 搜索歌词 Choose the websites you want to use when searching for lyrics. 选择歌词搜索网站。 Images (*.png *.jpg) 图片 Images (*.png *.jpg) Lyrics 歌词 Information 信息 Edit Lyrics 编辑歌词 Delete Lyrics File 删除歌词文件 Cancel 取消 Track i18n: file: devices/albumdetails.ui:158 i18n: ectx: property (text), widget (QTreeWidget, tracks) 音轨 Current playing song has changed, still perform search? 当前播放的歌曲已改变,继续搜索? Delete lyrics file? 确定删除歌词? Genre i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) i18n: file: devices/filenameschemedialog.ui:184 i18n: ectx: property (text), widget (QPushButton, genre) i18n: file: dynamic/dynamicrule.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_5) 流派 Date 日期 Encoder 编码 Bitrate 比特率 Year i18n: file: devices/filenameschemedialog.ui:174 i18n: ectx: property (text), widget (QPushButton, year) 年份 (Polish Translations) (波兰语翻译) (Portuguese Translations) (葡萄牙语翻译) <b>INVALID</b> <b>无效</b> <i>(When different)</i> <i>(当不同的时候)</i> %1 free 剩余 %1 Local Music Library 本地音乐库 Audio CD 音乐 CD Copy Songs 复制歌曲 Delete Songs 删除歌曲 Stop 停止 Device has been removed! 设备已移除! Device is not connected! 设备未连接! Device is busy? 设备正忙? Device has been changed? 设备已改变? Clearing unused folders 清除不用的文件夹 ReplayGain 播放增益 Local Music Library Properties 本地音乐库属性 Error 错误 Skip 跳过 Auto Skip 自动跳过 Retry 重试 Album: i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) i18n: file: dynamic/dynamicrule.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:106 i18n: ectx: property (text), widget (StateLabel, albumLabel) 专辑: Track: i18n: file: tags/tageditor.ui:34 i18n: ectx: property (text), widget (StateLabel, trackNameLabel) 音轨: Calculating... 计算中... Saving cache 正在保存缓存 Apply "Various Artists" Workaround 使用"多个艺术家" 环境 Revert "Various Artists" Workaround 恢复 "多个艺术家" 环境 Capitalize 转为大写 Adjust Track Numbers 调整音轨号 Tools 工具 Apply "Various Artists" workaround? 确定使用"多个艺术家" 环境? Adjust track number by: 调整音轨号为: Reading disc 正在读碟 CDDB CCDB MusicBrainz MusicBrainz Data Track 数据音轨 Failed to open CD device 打开 CD 设备失败 Failed to create CDDB connection 创建 CDDB 连接失败 No matches found in CDDB CDDB 中没有发现匹配数据 CDDB error: %1 CDDB 错误: %1 Multiple matches were found. Please choose the relevant one from below: 发现多个匹配项目,请在下面选择合适的: Title i18n: file: devices/albumdetails.ui:168 i18n: ectx: property (text), widget (QTreeWidget, tracks) 标题 Disc Selection 选择碟片 Updating (%1)... 正在更新 (%1)... Updating (%1%)... 正在更新 (%1%)... Device Properties 设备属性 Don't copy covers 不复制封面 Embed cover within each file 嵌入文件的封面 No maximum size 没有最大值 400 pixels 400 像素 300 pixels 300 像素 200 pixels 200 像素 100 pixels 100 像素 Do not transcode 不转码 Transcode to %1 转码成 %1 %1 (%2 free) name (size free) %1 (剩余 %2) Copy To Library 复制到库 Forget Device 清除设备历史 Add Device 添加设备 Lookup album and track details? 查看专辑和音轨信息? Refresh 刷新 Via CDDB 通过 CDDB Via MusicBrainz 通过 MusicBrainz Partial 部分 Full 完全 Are you sure you wish to forget '%1'? 确定要清除历史 '%1'? Disconnect 连接断开 Please close other dialogs first. 请先关闭其他对话框. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>高级音频编码 AAC </a>是一种有专利授权的无损压缩数字音频格式.<br> 它的音质比同比特率的 MP3 更好.它是 iPod 或者其他移动播放设备上的一个较好选择.限于非商业合同使用 Expected average bitrate for variable bitrate encoding 可变比特率编码使用平均比特率 Smaller file 较小文件 Better sound quality 较好音质 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://zh.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) 是一种有损压缩的专利音频编码格式.<br>尽管有缺陷,但它仍然市易奏常见的音频存储格式,几乎所有的移动播放设备都支持 MP3. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://zh.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a>是一种开放的自由的 无损压缩音频编码格式.<br>它的文件比同比特率的 MP3 更小. Ogg Vorbis是一种很不错的选择,特别是对支持它的播放器来说. Quality rating 评级 Apple Lossless Apple 无损 <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_%EB%AC%B4%EC%86%90%EC%8B%A4>苹果无损格式 苹果无损格式</a> (ALAC) 是一种无损压缩的数字音乐格式.<br>推荐在苹果播放器或者其他不支持 FLAC 的播放器上使用. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC) 是一种开放的自由的无损压缩数字音乐格式.<br>如果想要确保音乐品质, FLAC 是最好的选择. Compression level 压缩级别 Faster compression 快速压缩 Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. Feel free to redirect the english Wikipedia link to a local version, if it exists. <a href=http://ko.wikipedia.org/wiki/%EC%9C%88%EB%8F%84_%EB%AF%B8%EB%94%94%EC%96%B4_%EC%98%A4%EB%94%94%EC%98%A4>Windows Media Audio</a> (WMA)是一种微软开发的有损压缩音频编码格式.<br>推荐用在不支持 Ogg Vorbis的设备上 Filename Scheme 文件名框架 Various Artists Example album artist 多个艺术家 Wibble Example artist Wibble Now 5001 Example album 现在 5001 Dance Example genre 舞曲 Updating... 更新... Reading cache 读取缓存 Connecting to device... 正在连接到设备 ... No devices found 没有发现设备 Connected to device 连接到设备 Disconnected from device 设备连接断开 Updating folders... 更新文件夹... Updating files... 更新文件... Updating tracks... 更新音轨... Not Connected 未连接 %1 (Disc %2) %1 (碟 %2) No matches found in MusicBrainz 没有在 MusicBrainz 中找到匹配数据 Connection 连接 Music Library 音乐库 Samba Share Samba 共享 Samba Share (Auto-discover host and port) Samba 共享(自动设置主机名和端口) Secure Shell (sshfs) 安全 Shell (sshfs) Locally Mounted Folder 本地挂载的文件夹 Available 可用 Not Available 不可用 Failed to resolve connection details for %1 解析连接 %1 的信息失败 Connecting... 连接中... Password prompting does not work when cantata is started from the commandline. Cantata 从命令行启动时无法使用密码. No suitable ssh-askpass application installed! This is required for entering passwords. 匹配的 ssh-askpass 程序未安装! 输入密码必须安装它. Mount point ("%1") is not empty! 挂载点 ("%1") 非空! "sshfs" is not installed! "sshfs"未安装! Disconnecting... 正在断开连接... "fusermount" is not installed! "fusermount"未安装! Failed to connect to "%1" "%1" 连接失败 Failed to disconnect from "%1" "%1" 的连接无法断开 Capacity Unknown 容量未知 Search i18n: file: gui/coverdialog.ui:23 i18n: ectx: property (text), widget (QPushButton, search) 搜索 Synchronize 同步 Device and library are in sync. 设备和库已同步 Not Scanned 未扫描 (recommended) (推荐) Failed to delete rules file. (%1) 删除 (%1) 失败. Start Dynamic Playlist 开始动态列表 Stop Dynamic Mode 停止动态模式 Failed to locate rules file - %1 无法加载配置 - %1 Failed to remove previous rules file - %1 无法删除前一个配置 - %1 Failed to install rules file - %1 -> %2 无法安装配置 - %1 到 %2 Saving rule 正在保存规则 Deleting rule 删除规则 Awaiting response for previous command. (%1) 等待响应前一个命令. (%1) Failed to control dynamizer state. (%1) 设置动态 (%1) 失败 Failed to set the current dynamic rules. (%1) 无法设置当前动态规则. (%1) Add i18n: file: dynamic/dynamicrules.ui:71 i18n: ectx: property (text), widget (QPushButton, addBtn) 添加 Edit i18n: file: dynamic/dynamicrules.ui:78 i18n: ectx: property (text), widget (QPushButton, editBtn) 编辑 Remove i18n: file: dynamic/dynamicrules.ui:85 i18n: ectx: property (text), widget (QPushButton, removeBtn) 删除 Remove Dynamic Rules 删除动态配置 Dynamic Rule 动态规则 <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>错误</b>: 原有的年份应在更改后的年份之前</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>错误:</b> 日期范围过大 (最多不超过 %1 年)</i> SimilarArtists 相似艺术家 AlbumArtist 专辑艺术家 Include 包含 Exclude 排除 (Exact) (解压) Dynamic Rules 动态规则 None i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) i18n: file: context/othersettings.ui:32 i18n: ectx: property (text), widget (QRadioButton, contextBackdrop_none) i18n: file: gui/interfacesettings.ui:213 i18n: ectx: property (text), widget (QRadioButton, playQueueBackground_none) i18n: file: gui/playbacksettings.ui:48 i18n: ectx: property (specialValueText), widget (QSpinBox, stopFadeDuration) i18n: file: gui/playbacksettings.ui:122 i18n: ectx: property (specialValueText), widget (QSpinBox, crossfading) i18n: file: support/shortcutssettingswidget.ui:78 i18n: ectx: property (text), widget (QLabel, defaultShortcut) Failed to save %1 %1 保存失败 Saving %1 正在保存 %1 Name 名称 Item Count 计数 Space Used 已用空间 Covers 封面 Artist Information 艺术家信息 Delete All 删除全部 Delete all '%1' items? 确定所有 '%1' 的项目? Delete Cache Items 确定删除缓存? %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) Image 图片 Downloading... 正在下载... Image (%1 x %2 %3%) Image (width x height zoom%) 图片 (%1 x %2 %3%) Failed to download image! 下载图片失败! Load Local Cover 加载本地封面 File is already in list! 文件已在列表中! Failed to read image! 读取图片失败! Display 显示 Name: i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) i18n: file: devices/devicepropertieswidget.ui:32 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: devices/remotedevicepropertieswidget.ui:42 i18n: ectx: property (text), widget (BuddyLabel, nameLabel) i18n: file: gui/serversettings.ui:63 i18n: ectx: property (text), widget (QLabel, label_2) 名称: Open In File Manager 在文件管理器里打开 Connection Failed 连接失败 Grouped Albums 分组专辑 Table 表单 Play Queue i18n: file: gui/interfacesettings.ui:128 i18n: ectx: attribute (title), widget (QWidget, tab_5) 播放队列 Library Folders 文件夹 Playlists 播放列表 Large Small Configure Cantata... 设置 Cantata... Quit 退出 About Cantata... Qt-only 关于 Cantata... Show Window 显示窗口 Server information... 服务器信息... Refresh Database 刷新数据 Connect i18n: file: gui/initialsettingswizard.ui:472 i18n: ectx: property (text), widget (QPushButton, connectButton) 连接 Outputs 输出 Stop After Track 音轨后停止 Add To Stored Playlist 添加到已有列表 Add Stream URL 添加流媒体 Clear 清除 Expanded Interface 展开界面 Full Screen 全屏 Random 随机 Repeat 重复 Single 单曲 When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. '单曲'启用时, 将在当前歌曲完成后停止播放. 或者 '重复'启用时, 将反复播放. Consume 省资源 When consume is activated, a song is removed from the play queue after it has been played. 当节省资源打开时,播放过的歌曲将被从队列中移除. Play Stream 播放流媒体 Locate In Library 在库中定位 Expand All 展开所有 Collapse All 收起所有 Devices 设备 Info 信息 &Settings 设置(&S) &Help 帮助(&H) Failed to locate any songs matching the dynamic playlist rules. 无法给歌曲匹配动态设置. Connecting to %1 连接到 %1 About Cantata Qt-only 关于 Cantata Server Information 服务器信息 Priority 优先级 Enter priority (0..255): 输入优先级 (0到255) Playlist Name 播放列表名称 Enter a name for the playlist: 输入播放列表名称: Existing Playlist 已有同名播放列表 Rename 重命名 Rename Playlist 重命名播放列表 Enter new name for playlist: 输入播放列表新名称: 1 Track 1 音轨 %1 Tracks 1 Track (%2) %1 音轨 (%2) %1 Tracks (%2) 1 Album 专辑封面 %1 %1 Albums 1 Artist 艺术家 %1 %1 Artists 1 Entry 接口 %1 %1 Entries 1 Rule %1 规则 %1 Rules Playback i18n: file: gui/playbacksettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, stopPlaybackBox) 回放 Playback Settings 回放设置 Interface 界面 Interface Settings 界面设置 Audio CD Settings 音乐 CD 设置 Proxy Settings Qt-only 代理设置 Shortcuts Qt-only 快捷 Keyboard Shortcut Settings Qt-only 快捷键设定 Cache 缓存 Cached Items 缓存项目 Configure i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) i18n: file: devices/actiondialog.ui:196 i18n: ectx: property (toolTip), widget (QToolButton, configureSourceButton) i18n: file: devices/actiondialog.ui:249 i18n: ectx: property (toolTip), widget (QToolButton, configureDestButton) i18n: file: gui/coverdialog.ui:37 i18n: ectx: property (toolTip), widget (MenuButton, configureButton) 设置 Genre: i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) i18n: file: devices/albumdetails.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: tags/tageditor.ui:145 i18n: ectx: property (text), widget (StateLabel, genreLabel) 流派: Host: i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) i18n: file: devices/remotedevicepropertieswidget.ui:72 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:289 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/initialsettingswizard.ui:378 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: gui/serversettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, hostLabel) i18n: file: network/proxysettings.ui:63 i18n: ectx: property (text), widget (BuddyLabel, proxyHostLabel) 主机名: Default 默认 Previous Track 上一个 Next Track 下一个 Play/Pause 播放/暂停 Stop After Current Track 当前音轨后停止 Increase Volume 增大音量 Decrease Volume 减小音量 Save As 另存为 Add With Priority 添加时设定优先级 Set Priority 设置优先级 Highest Priority (255) 最高优先级 (255) High Priority (200) 高优先级 (200) Medium Priority (125) 中等优先级 (125) Low Priority (50) 低优先级 (50) Default Priority (0) 默认优先级 (0) Custom Priority... 自定义优先级... Add To Playlist 添加到播放列表 Organize Files 组织文件 Add To Play Queue 添加到正在播放 Now playing 正在播放 Cue Sheet Cue 表 Playlist 播放列表 Configure Device 设置设备 Refresh Device 刷新设备 Connect Device 连接设备 Disconnect Device 断开设备 Edit CD Details 编辑 CD 详细信息 No Devices Attached 没有关联设备 %1 by %2 Album by Artist %2 的 %1 New Playlist... 新播放列表... Length 长度 Disc 碟片 Album Artist i18n: file: devices/filenameschemedialog.ui:82 i18n: ectx: property (text), widget (QPushButton, albumArtist) 专辑艺术家 Track Title i18n: file: devices/filenameschemedialog.ui:122 i18n: ectx: property (text), widget (QPushButton, trackTitle) 音轨标题 Not Loaded 未加载 Streams 流媒体 Unknown 未知 Connection to %1 failed 连接 %1 失败 Connection to %1 failed - incorrect password 连接 %1 失败 - 密码错误 Failed to send command to %1 - not connected 发送到 %1 失败 - 未能连接 Failed to load. Please check user "mpd" has read permission. 无法加载. 请检查 "mpd" 是否有读取权限. Failed to load. MPD can only play local files if connected via a local socket. 无法加载. MPD 只能从本地 Socket 播放本地文件. Failed to send command. Disconnected from %1 发送失败. %1 已断开 Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b> 重命名为 <b>%2</b> 失败 Failed to save <b>%1</b> <b>%1</b> 保存失败 Single Tracks i18n: file: gui/interfacesettings.ui:540 i18n: ectx: property (title), widget (QGroupBox, groupBox_3) 单个音轨 Various Artists 多个艺术家 Use the system proxy settings 使用系统代理 Manual proxy configuration 手动代理设置 Jamendo Settings Jamendo 设置 MP3 MP3 Streaming format: 流媒体格式: Streaming 流媒体 Magnatune Settings Magnatune 设置 Username: i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: network/proxysettings.ui:96 i18n: ectx: property (text), widget (BuddyLabel, proxyUsernameLabel) i18n: file: scrobbling/scrobblingsettings.ui:58 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: streams/digitallyimportedsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, label_2) 用户名: Password: i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:128 i18n: ectx: property (text), widget (BuddyLabel, label_3) i18n: file: devices/remotedevicepropertieswidget.ui:225 i18n: ectx: property (text), widget (BuddyLabel, label_3x) i18n: file: gui/initialsettingswizard.ui:411 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: gui/serversettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, passwordLabel) i18n: file: network/proxysettings.ui:109 i18n: ectx: property (text), widget (BuddyLabel, proxyPasswordLabel) i18n: file: scrobbling/scrobblingsettings.ui:71 i18n: ectx: property (text), widget (BuddyLabel, passLabel) i18n: file: streams/digitallyimportedsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, label_3) 密码: Downloads: 下载: Failed to parse 操作失败 Failed to download 下载失败 ms i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) i18n: file: context/othersettings.ui:180 i18n: ectx: property (suffix), widget (QSpinBox, contextSwitchTime) i18n: file: gui/playbacksettings.ui:51 i18n: ectx: property (suffix), widget (QSpinBox, stopFadeDuration) 毫秒 Copy songs from: i18n: file: devices/actiondialog.ui:180 i18n: ectx: property (text), widget (QLabel, label) 从复制歌曲: (Needs configuring) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) i18n: file: devices/actiondialog.ui:211 i18n: ectx: property (text), widget (QLabel, configureSourceLabel) i18n: file: devices/actiondialog.ui:264 i18n: ectx: property (text), widget (QLabel, configureDestLabel) (需要设置) Copy songs to: i18n: file: devices/actiondialog.ui:233 i18n: ectx: property (text), widget (QLabel, label_3) 复制歌曲到: Destination format: i18n: file: devices/actiondialog.ui:296 i18n: ectx: property (text), widget (QLabel, codecLabel) 目标格式: Album Details i18n: file: devices/albumdetails.ui:26 i18n: ectx: property (title), widget (QGroupBox, groupBox) 专辑详情 Year: i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) i18n: file: devices/albumdetails.ui:89 i18n: ectx: property (text), widget (BuddyLabel, label_4) i18n: file: tags/tageditor.ui:158 i18n: ectx: property (text), widget (StateLabel, yearLabel) 年份: Disc: i18n: file: devices/albumdetails.ui:102 i18n: ectx: property (text), widget (BuddyLabel, label_6) 碟片: Album and Track Information Retrieval i18n: file: devices/audiocdsettings.ui:29 i18n: ectx: property (title), widget (QGroupBox, groupBox) 专辑音轨信息 Initially look up via: i18n: file: devices/audiocdsettings.ui:38 i18n: ectx: property (text), widget (BuddyLabel, cdLookupLabel) 通过下列查找: CDDB Host: i18n: file: devices/audiocdsettings.ui:51 i18n: ectx: property (text), widget (BuddyLabel, cddbHostLabel) CDDB 主机名: CDDB Port: i18n: file: devices/audiocdsettings.ui:64 i18n: ectx: property (text), widget (BuddyLabel, cddbPortLabel) CDDB 端口: Audio Extraction i18n: file: devices/audiocdsettings.ui:94 i18n: ectx: property (title), widget (QGroupBox, groupBox_2) 抽取音频 Never skip on read error i18n: file: devices/audiocdsettings.ui:107 i18n: ectx: property (text), widget (QCheckBox, paranoiaNeverSkip) 从不跳过读取错误 Music folder: i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: devices/devicepropertieswidget.ui:45 i18n: ectx: property (text), widget (BuddyLabel, musicFolderLabel) i18n: file: gui/initialsettingswizard.ui:428 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/initialsettingswizard.ui:569 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) i18n: file: gui/serversettings.ui:126 i18n: ectx: property (text), widget (BuddyLabel, dirLabel) i18n: file: gui/serversettings.ui:230 i18n: ectx: property (text), widget (BuddyLabel, dirLabel_2) 音乐文件夹: Copy album covers as: i18n: file: devices/devicepropertieswidget.ui:58 i18n: ectx: property (text), widget (BuddyLabel, albumCoversLabel) 复制专辑封面为: Maximum cover size: i18n: file: devices/devicepropertieswidget.ui:75 i18n: ectx: property (text), widget (BuddyLabel, coverMaxSizeLabel) 最大专辑封面大小: Default volume: i18n: file: devices/devicepropertieswidget.ui:95 i18n: ectx: property (text), widget (QLabel, defaultVolumeLabel) 默认音量: Filenames i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) i18n: file: devices/devicepropertieswidget.ui:125 i18n: ectx: property (title), widget (QGroupBox, filenamesGroupBox) i18n: file: tags/trackorganiser.ui:25 i18n: ectx: property (title), widget (QGroupBox, optionsBox) 文件名 Filename scheme: i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: devices/devicepropertieswidget.ui:134 i18n: ectx: property (text), widget (BuddyLabel, label_6) i18n: file: tags/trackorganiser.ui:61 i18n: ectx: property (text), widget (BuddyLabel, label_6) 文件名框架 Transcoding i18n: file: devices/devicepropertieswidget.ui:205 i18n: ectx: property (title), widget (QGroupBox, transcoderFrame) 正在转码 Example: i18n: file: devices/filenameschemedialog.ui:38 i18n: ectx: property (text), widget (QLabel, label_album_example) 例如: About filename schemes i18n: file: devices/filenameschemedialog.ui:67 i18n: ectx: property (text), widget (UrlLabel, help) 关于文件名框架 Album Title i18n: file: devices/filenameschemedialog.ui:92 i18n: ectx: property (text), widget (QPushButton, albumTitle) 专辑标题 Track Artist i18n: file: devices/filenameschemedialog.ui:112 i18n: ectx: property (text), widget (QPushButton, trackArtist) 音轨艺术家 Track Title (+Artist) i18n: file: devices/filenameschemedialog.ui:144 i18n: ectx: property (text), widget (QPushButton, trackArtistAndTitle) 音轨标题(+艺术家) Track # i18n: file: devices/filenameschemedialog.ui:154 i18n: ectx: property (text), widget (QPushButton, trackNo) 音轨 # CD # i18n: file: devices/filenameschemedialog.ui:164 i18n: ectx: property (text), widget (QPushButton, cdNo) CD # Type: i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) i18n: file: devices/remotedevicepropertieswidget.ui:29 i18n: ectx: property (text), widget (BuddyLabel, typeLabel) i18n: file: dynamic/dynamicrule.ui:26 i18n: ectx: property (text), widget (BuddyLabel, label_9) i18n: file: network/proxysettings.ui:39 i18n: ectx: property (text), widget (BuddyLabel, proxyTypeLabel) 类型: Options i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) i18n: file: devices/remotedevicepropertieswidget.ui:57 i18n: ectx: property (title), widget (QGroupBox, groupBox) i18n: file: gui/interfacesettings.ui:71 i18n: ectx: property (title), widget (QGroupBox, optionsGroup) 选项 Port: i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) i18n: file: devices/remotedevicepropertieswidget.ui:85 i18n: ectx: property (text), widget (BuddyLabel, portLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:302 i18n: ectx: property (text), widget (BuddyLabel, portLabel) i18n: file: network/proxysettings.ui:76 i18n: ectx: property (text), widget (BuddyLabel, proxyPortLabel) 端口: User: i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) i18n: file: devices/remotedevicepropertieswidget.ui:102 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:199 i18n: ectx: property (text), widget (BuddyLabel, userLabel_2x) i18n: file: devices/remotedevicepropertieswidget.ui:319 i18n: ectx: property (text), widget (BuddyLabel, userLabel) 用户: Domain: i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) i18n: file: devices/remotedevicepropertieswidget.ui:115 i18n: ectx: property (text), widget (BuddyLabel, label_2) i18n: file: devices/remotedevicepropertieswidget.ui:212 i18n: ectx: property (text), widget (BuddyLabel, label_2x) 域名 Share: i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) i18n: file: devices/remotedevicepropertieswidget.ui:145 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_2) i18n: file: devices/remotedevicepropertieswidget.ui:242 i18n: ectx: property (text), widget (BuddyLabel, folderLabel_x2) 共享: Service name: i18n: file: devices/remotedevicepropertieswidget.ui:186 i18n: ectx: property (text), widget (BuddyLabel, hostLabel_2x) 服务名称: Folder: i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) i18n: file: devices/remotedevicepropertieswidget.ui:332 i18n: ectx: property (text), widget (BuddyLabel, folderLabel) i18n: file: devices/remotedevicepropertieswidget.ui:390 i18n: ectx: property (text), widget (BuddyLabel, label) 文件夹: Extra Options: i18n: file: devices/remotedevicepropertieswidget.ui:345 i18n: ectx: property (text), widget (BuddyLabel, sshExtraLabel) 额外选项: Name of Dynamic Rules i18n: file: dynamic/dynamicrules.ui:39 i18n: ectx: property (placeholderText), widget (LineEdit, nameText) 动态配置名称 seconds i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) i18n: file: dynamic/dynamicrules.ui:153 i18n: ectx: property (suffix), widget (QSpinBox, minDuration) i18n: file: dynamic/dynamicrules.ui:173 i18n: ectx: property (suffix), widget (QSpinBox, maxDuration) About Rules i18n: file: dynamic/dynamicrules.ui:209 i18n: ectx: property (text), widget (UrlLabel, aboutLabel) 关于规则 Artists similar to: i18n: file: dynamic/dynamicrule.ui:63 i18n: ectx: property (text), widget (BuddyLabel, similarArtistsText_label) 相似艺术家: Album Artist: i18n: file: dynamic/dynamicrule.ui:76 i18n: ectx: property (text), widget (BuddyLabel, label_2) 专辑艺术家: From Year: i18n: file: dynamic/dynamicrule.ui:141 i18n: ectx: property (text), widget (BuddyLabel, label_6) 年份: Any i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) i18n: file: dynamic/dynamicrule.ui:157 i18n: ectx: property (specialValueText), widget (QSpinBox, dateFromSpin) i18n: file: dynamic/dynamicrule.ui:180 i18n: ectx: property (specialValueText), widget (QSpinBox, dateToSpin) 任意 To Year: i18n: file: dynamic/dynamicrule.ui:164 i18n: ectx: property (text), widget (BuddyLabel, label_6x) 年份: Exact match i18n: file: dynamic/dynamicrule.ui:213 i18n: ectx: property (text), widget (QCheckBox, exactCheck) 精确匹配 Save downloaded lyrics in music folder i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/filesettings.ui:39 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) i18n: file: gui/initialsettingswizard.ui:682 i18n: ectx: property (text), widget (QCheckBox, storeLyricsInMpdDir) 保存下载的歌词到音乐文件夹 Cantata First Run i18n: file: gui/initialsettingswizard.ui:14 i18n: ectx: property (windowTitle), widget (QWizard, InitialSettingsWizard) 首次运行 Cantata Welcome to Cantata i18n: file: gui/initialsettingswizard.ui:46 i18n: ectx: property (text), widget (QLabel, label) 欢迎来到 Cantata Connection details i18n: file: gui/initialsettingswizard.ui:321 i18n: ectx: property (text), widget (QLabel, label_3) 连接详情 Style: i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) i18n: file: gui/interfacesettings.ui:82 i18n: ectx: property (text), widget (BuddyLabel, sbStyleLabel) i18n: file: gui/interfacesettings.ui:137 i18n: ectx: property (text), widget (BuddyLabel, playQueueViewLabel) 视图: External i18n: file: gui/interfacesettings.ui:398 i18n: ectx: attribute (title), widget (QWidget, tab_7) 外部 General i18n: file: gui/interfacesettings.ui:609 i18n: ectx: attribute (title), widget (QWidget, tab_4) 流派 [Dynamic] i18n: file: gui/mainwindow.ui:181 i18n: ectx: property (text), widget (QLabel, dynamicLabel) [动态] Stop playback on exit i18n: file: gui/playbacksettings.ui:58 i18n: ectx: property (text), widget (QCheckBox, stopOnExit) 退出时停止回放 Output i18n: file: gui/playbacksettings.ui:88 i18n: ectx: property (title), widget (QGroupBox, outputBox) 输出 Cover filename: i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) i18n: file: gui/serversettings.ui:139 i18n: ectx: property (text), widget (BuddyLabel, coverNameLabel) i18n: file: gui/serversettings.ui:243 i18n: ectx: property (text), widget (BuddyLabel, basicCoverNameLabel) 封面名称: HTTP Proxy i18n: file: network/proxysettings.ui:50 i18n: ectx: property (text), item, widget (QComboBox, proxyType) HTTP 代理 SOCKS Proxy i18n: file: network/proxysettings.ui:55 i18n: ectx: property (text), item, widget (QComboBox, proxyType) SOCKS 代理 Search: i18n: file: support/shortcutssettingswidget.ui:22 i18n: ectx: property (text), widget (BuddyLabel, label_2) 搜索: Shortcut for Selected Action i18n: file: support/shortcutssettingswidget.ui:65 i18n: ectx: property (title), widget (QGroupBox, actionBox) 选定动作的快捷键 Default: i18n: file: support/shortcutssettingswidget.ui:71 i18n: ectx: property (text), widget (QRadioButton, useDefault) 默认: Custom: i18n: file: support/shortcutssettingswidget.ui:85 i18n: ectx: property (text), widget (QRadioButton, useCustom) 自定义: Album artist: i18n: file: tags/tageditor.ui:80 i18n: ectx: property (text), widget (StateLabel, albumArtistLabel) 专辑艺术家: Track number: i18n: file: tags/tageditor.ui:119 i18n: ectx: property (text), widget (StateLabel, trackLabel) 音轨号: Disc number: i18n: file: tags/tageditor.ui:132 i18n: ectx: property (text), widget (StateLabel, discLabel) 碟片序号: Original Name i18n: file: tags/trackorganiser.ui:115 i18n: ectx: property (text), widget (QTreeWidget, files) 原始名称 New Name i18n: file: tags/trackorganiser.ui:120 i18n: ectx: property (text), widget (QTreeWidget, files) 新名称 Your names NAME OF TRANSLATORS 姓名 Your emails EMAIL OF TRANSLATORS 你的 E-mail Album Gain 专辑增益 Track Gain 音轨增益 Album Peak 专辑峰值 Track Peak 音轨峰值 Scan 扫描 Untagged Tracks 音轨无标签 All Tracks 所有音轨 Reading existing tags... 读取标签... Failed to update the tags of the following tracks: 下列歌曲标签更新失败: Device is not connected. 设备未连接. %1 dB %1 dB Failed 失败 Add Stream 添加流媒体 Edit Stream 编辑流媒体 <i><b>ERROR:</b> Invalid protocol</i> <i><b>错误:</b>协议无效</i> Import Streams 导入流媒体 Export Streams 导出流媒体 Are you sure you wish to remove the %1 selected streams? 确定移除选择的流媒体 %1 ? Are you sure you wish to remove '%1'? 确定要移除 '%1'? Password 密码 Please enter password: 请输入密码: Warning 警告 Question 问题 Select Folder 选择文件夹 Select File 选择文件 %1 B %1 B %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Tags 标签 All tracks 所有音轨 Apply "Various Artists" workaround to <b>all</b> tracks? 应用"多个艺术家" 环境到 <b>所有</b> 音轨? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>'专辑艺人' 和 '艺术家'将被设置成 '多个艺术家','标题'将被设置为'音轨艺人 - 音轨标题'。</i> Revert "Various Artists" workaround on <b>all</b> tracks? 确定恢复 "多个艺术家" 环境到 <b>所有</b>音轨? Revert "Various Artists" workaround 恢复 "多个艺术家" 环境 Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 获取"专辑艺人"标签并应用到"艺术家(如果为空)" 环境到 <b>所有</b> 音轨? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? 获取"专辑艺人"标签并应用到"艺术家(如果为空)" ? Adjust the value of each track number by: 调整每个音轨号按: All tracks [modified] 所有音轨 [已修改] %1 [modified] %1 [已修改] <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>艺术家:</b></td><td>%1</td></tr><tr><td align="right"><b>专辑:</b></td><td>%2</td></tr><tr><td align="right"><b>年:</b></td><td>%3</td></tr> All Genres 所有流派 (Stream) (流媒体) Simple Tree 简单树形 Detailed Tree 详细信息 List 列表 (Various) (多个) Mute 静音 Volume %1% 音量 %1% 1 Track Singular 1 音轨 %1 Tracks Plural (N!=1) %1 音轨 1 Track (%1) Singular 1 音轨 (%1) %1 Tracks (%2) Plural (N!=1) %1 音轨 (%2) 1 Album Singular 1 专辑 %1 Albums Plural (N!=1) %1 专辑 1 Artist Singular 1 艺术家 %1 Artists Plural (N!=1) %1 艺术家 1 Stream Singular 1 流媒体 %1 Streams Plural (N!=1) %1 流媒体 1 Entry Singular 1 项 %1 Entries Plural (N!=1) %1 项 1 Rule Singular 1 配置 %1 Rules Plural (N!=1) %1 规则 ActionDialog Calculating size of files to be copied, please wait... Copy songs from: 从复制歌曲: Configure 设置 (Needs configuring) (需要设置) Copy songs to: 复制歌曲到: Destination format: 目标格式: Overwrite songs To copy: <b>INVALID</b> <b>无效</b> <i>(When different)</i> <i>(当不同的时候)</i> Artists:%1, Albums:%2, Songs:%3 %1 free 剩余 %1 Local Music Library 本地音乐库 Audio CD 音乐 CD There is insufficient space left on the destination device. The selected songs consume %1, but there is only %2 left. The songs will need to be transcoded to a smaller filesize in order to be successfully copied. There is insufficient space left on the destination. The selected songs consume %1, but there is only %2 left. Copy Songs To Library Copy Songs To Device Copy Songs 复制歌曲 Delete Songs 删除歌曲 You have not configured the destination device. Continue with the default settings? Not Configured Use Defaults You have not configured the source device. Continue with the default settings? Are you sure you wish to stop? Stop 停止 Device has been removed! 设备已移除! Device is not connected! 设备未连接! Device is busy? 设备正忙? Device has been changed? 设备已改变? Clearing unused folders 清除不用的文件夹 Calculate ReplayGain for ripped tracks? ReplayGain 播放增益 Calculate The destination filename already exists! Song already exists! Song does not exist! Failed to create destination folder!<br/>Please check you have sufficient permissions. Source file no longer exists? Failed to copy. Failed to delete. Not connected to device. Selected codec is not available. Transcoding failed. Failed to create temporary file.<br/>(Required for transcoding to MTP devices.) Failed to read source file. Failed to write to destination file. No space left on device. Failed to update metadata. Failed to download track. Failed to lock device. Local Music Library Properties 本地音乐库属性 Error 错误 Skip 跳过 Auto Skip 自动跳过 Retry 重试 Artist: 艺术家: Album: 专辑: Track: 音轨: Source file: Destination file: File: Saving cache 正在保存缓存 Calculating... 计算中... AlbumDetails Album Details 专辑详情 Artist: 艺术家: Composer: Title: 标题: Genre: 流派: Year: 年份: Disc: 碟片: Single artist Tracks 音轨 Track 音轨 Artist 艺术家 Title 标题 AlbumDetailsDialog Audio CD 音乐 CD Apply "Various Artists" Workaround 使用"多个艺术家" 环境 Revert "Various Artists" Workaround 恢复 "多个艺术家" 环境 Capitalize 转为大写 Adjust Track Numbers 调整音轨号 Tools 工具 Apply "Various Artists" workaround? 确定使用"多个艺术家" 环境? This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle" Revert "Various Artists" workaround? Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble" Revert Capitalize the first letter of 'Title', 'Artist', 'Album artist', and 'Album'? Adjust track number by: 调整音轨号为: AlbumView Refresh Album Information Album 专辑 Tracks 音轨 ArtistView Refresh Artist Information Artist 艺术家 Albums 专辑 Web Links Web 链接 Similar Artists 相似艺术家 AudioCdDevice Reading disc 正在读碟 %n Tracks (%1) %n 音轨 (%1) AudioCdSettings Album and Track Information Retrieval 专辑音轨信息 Initially look up via: 通过下列查找: CDDB Host: CDDB 主机名: CDDB Port: CDDB 端口: Lookup information as soon as CD is inserted Audio Extraction 抽取音频 Full paranoia mode (best quality) Never skip on read error 从不跳过读取错误 CDDB CCDB MusicBrainz MusicBrainz BrowseModel Cue Sheet Cue 表 Playlist 播放列表 CacheItem Deleting... Calculating... 计算中... CacheSettings Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's current cache usage. Covers 封面 Scaled Covers Backdrops Lyrics 歌词 Artist Information 艺术家信息 Album Information Track Information Stream Listings Podcast Directories Wikipedia Languages Scrobble Tracks Delete All 删除全部 Delete all '%1' items? 确定所有 '%1' 的项目? Delete Cache Items 确定删除缓存? Delete items from all selected categories? CacheTree Name 名称 Item Count 计数 Space Used 已用空间 CddbInterface Data Track 数据音轨 Failed to open CD device 打开 CD 设备失败 Track %1 Failed to create CDDB connection 创建 CDDB 连接失败 Failed to contact CDDB server, please check CDDB and network settings No matches found in CDDB CDDB 中没有发现匹配数据 CDDB error: %1 CDDB 错误: %1 CddbSelectionDialog Multiple matches were found. Please choose the relevant one from below: 发现多个匹配项目,请在下面选择合适的: Artist 艺术家 Title 标题 Disc Selection 选择碟片 %1 - %2 Disc %3 (%4) artist - album Disc disc (year) %1 - %2 (%3) artist - album (year) ContextSettings Lyrics Providers Wikipedia Languages Other ContextWidget &Artist Al&bum &Track CoverDialog Search 搜索 Add a local file Configure 设置 This can only be used to change the file used for covers, it will not alter any embedded covers you may have in your song files. CoverArt Archive An image already exists for this artist, and the file is not writeable. A cover already exists for this album, and the file is not writeable. '%1' Artist Image '%1 - %2' Album Cover 'Artist - Album' Album Cover Failed to set cover! Could not download to temporary file! Failed to download image! 下载图片失败! Load Local Cover 加载本地封面 Images (*.png *.jpg) 图片 Images (*.png *.jpg) File is already in list! 文件已在列表中! Failed to read image! 读取图片失败! Display 显示 Remove 删除 Failed to set cover! Could not make copy! Failed to set cover! Could not backup original! Failed to set cover! Could not copy file to '%1'! Searching... CoverLabel <tr><td align="right"><b>Composer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Performer:</b></td><td>%1</td></tr> <tr><td align="right"><b>Artist:</b></td><td>%1</td></tr><tr><td align="right"><b>Album:</b></td><td>%2</td></tr><tr><td align="right"><b>Year:</b></td><td>%3</td></tr> <tr><td align="right"><b>艺术家:</b></td><td>%1</td></tr><tr><td align="right"><b>专辑:</b></td><td>%2</td></tr><tr><td align="right"><b>年:</b></td><td>%3</td></tr> CoverPreview Image 图片 Downloading... 正在下载... Image (%1 x %2 %3%) Image (width x height zoom%) 图片 (%1 x %2 %3%) CustomActionDialog Name: 名称: Command: In the command line above, %f will be replaced with the file list and %d with the folder list. If neither are supplied, the the list of files will be appended to the command. Add New Command Edit Command CustomActions Custom Actions CustomActionsSettings To have Cantata call external commands (e.g. to edit tags with another application), add an entry for the command below. When at least one command command is defined, a 'Custom Actions' entry will be added to the context menus in the Library, Folders, and Playlists views. Add 添加 Edit 编辑 Remove 删除 Name 名称 Command Remove the selected commands? Device Updating (%1)... 正在更新 (%1)... Updating (%1%)... 正在更新 (%1%)... DevicePropertiesDialog Device Properties 设备属性 DevicePropertiesWidget These settings are only valid, and editable, when the device is connected. Name: 名称: Music folder: 音乐文件夹: Copy album covers as: 复制专辑封面为: Maximum cover size: 最大专辑封面大小: Default volume: 默认音量: 'Various Artists' workaround Automatically scan music when attached Use cache Filenames 文件名 Filename scheme: 文件名框架 VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names If an artist name begins with 'The', then prepend this in the folder name. e.g. 'The Beatles' becomes 'Beatles, The' Transcoding 正在转码 Only transcode if source file is of a different format Only transcode if source is FLAC/WAV Don't copy covers 不复制封面 Embed cover within each file 嵌入文件的封面 No maximum size 没有最大值 400 pixels 400 像素 300 pixels 300 像素 200 pixels 200 像素 100 pixels 100 像素 <p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it will attempt to extract the real artist from the 'Title' tag, and remove the artist name from the 'Title' tag.</p> <p>If you enable this, then Cantata will create a cache of the device's music library. This will help to speed up subsequent library scans (as the cache file will be used instead of having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update the device's library, then this cache will become out-of-date. To rectify this, simply click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and the contents of the device re-scanned.</p> Do not transcode 不转码 Encoder 编码 Transcode to %1 转码成 %1 %1 (%2 free) name (size free) %1 (剩余 %2) DevicesModel Configure Device 设置设备 Refresh Device 刷新设备 Connect Device 连接设备 Disconnect Device 断开设备 Edit CD Details 编辑 CD 详细信息 Not Connected 未连接 No Devices Attached 没有关联设备 DevicesPage Copy To Library 复制到库 Synchronise Forget Device 清除设备历史 Add Device 添加设备 Lookup album and track details? 查看专辑和音轨信息? Refresh 刷新 Via CDDB 通过 CDDB Via MusicBrainz 通过 MusicBrainz Which type of refresh do you wish to perform? Partial - Only new songs are scanned (quick) Full - All songs are rescanned (slow) Partial 部分 Full 完全 Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs 删除歌曲 Are you sure you wish to forget '%1'? 确定要清除历史 '%1'? Are you sure you wish to eject Audio CD '%1 - %2'? Eject Are you sure you wish to disconnect '%1'? Disconnect 连接断开 Please close other dialogs first. 请先关闭其他对话框. DigitallyImported Not logged in Logged in Unknown error No subscriptions You do not have an active subscription Logged in (expiry:%1) Session expired DigitallyImportedSettings You can listen for free without an account, but Premium members can listen to higher quality streams without advertisements. Visit <a href="http://www.di.fm/premium/">http://www.di.fm/premium/</a> to upgrade to a premium account. Premium Account Username: 用户名: Password: 密码: Stream type: Status: Login Session expiry: These settings apply to Digitally Imported, JazzRadio.com, RockRadio.com, and Sky.fm If you enter account details, then a 'DI' status item will appear under the list of streams. This will indicate if you are logged in or not. Digitally Imported Settings MP3 256k MP3 256k AAC 64k AAC 128k Not Authenticated Authenticating... Authenticated Logout DockMenu Play Pause DynamicPlaylists Start Dynamic Playlist 开始动态列表 Stop Dynamic Mode 停止动态模式 Dynamic Playlists Dynamically generated playlists You need to install "perl" on your system in order for Cantata's dynamic mode to function. Failed to locate rules file - %1 无法加载配置 - %1 Failed to remove previous rules file - %1 无法删除前一个配置 - %1 Failed to install rules file - %1 -> %2 无法安装配置 - %1 到 %2 Dynamizer has been terminated. Awaiting response for previous command. (%1) 等待响应前一个命令. (%1) Saving rule 正在保存规则 Deleting rule 删除规则 Failed to save %1. (%2) Failed to delete rules file. (%1) 删除 (%1) 失败. Failed to control dynamizer state. (%1) 设置动态 (%1) 失败 Failed to set the current dynamic rules. (%1) 无法设置当前动态规则. (%1) DynamicPlaylistsPage Add 添加 Edit 编辑 Remove 删除 Remote dynamizer is not running. Are you sure you wish to remove the selected rules? This cannot be undone. Remove Dynamic Rules 删除动态配置 FileSettings Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder 保存下载的歌词到音乐文件夹 Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. FilenameSchemeDialog Example: 例如: About filename schemes 关于文件名框架 The artist of the album. For most albums, this will be the same as the <i>Track Artist.</i> For compilations, this will often be <i>Various Artists.</i> Album Artist 专辑艺术家 The name of the album. Album Title 专辑标题 The composer. Composer The artist of each track. Track Artist 音轨艺术家 The track title (without <i>Track Artist</i>). Track Title 音轨标题 The track title (with <i>Track Artist</i>, if different to <i>Album Artist</i>). Track Title (+Artist) 音轨标题(+艺术家) The track number. Track # 音轨 # The album number of a multi-album album. Often compilations consist of several albums. CD # CD # The year of the album's release. Year 年份 The genre of the album. Genre 流派 Filename Scheme 文件名框架 Various Artists Example album artist 多个艺术家 Wibble Example artist Wibble Vivaldi Example composer Now 5001 Example album 现在 5001 Wobble Example song name Dance Example genre 舞曲 The following variables will be replaced with their corresponding meaning for each track name. <tr><th><em>Variable</em></th><th><em>Button</em></th><th><em>Description</em></th></tr> FolderPage Open In File Manager 在文件管理器里打开 Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs 删除歌曲 FsDevice Updating... 更新... Reading cache 读取缓存 Saving cache 正在保存缓存 %1 %2% Message percent GenreCombo Filter On Genre All Genres 所有流派 GroupedViewDelegate Audio CD 音乐 CD Streams 流媒体 %n Track(s) %n 音轨 InitialSettingsWizard Cantata First Run 首次运行 Cantata Welcome to Cantata 欢迎来到 Cantata <html><head/><body><p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music.</p><p>For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a></p><p>This 'wizard' will guide you through the basic settings required for Cantata to function correctly.</p></body></html> <html><head/><body><p>Welcome to Cantata</p></body></html> <p>Cantata is a feature-rich and user friendly client for Music Player Daemon (MPD). MPD is a flexible, powerful, server-side application for playing music. MPD may be started either system-wide, or on a per-user basis.<br/><br/>Please select how you would like to have Cantata initially connect to (or startup) MPD:</p> Standard multi-user/server setup <i>Select this option if your music collection is shared between users, your MPD instance is running on another machine, you already have a personal MPD setup, or you wish to enable access from other clients (e.g. MPDroid). If you select this option then Cantata itself cannot control the starting and stopping of the MPD server. You will therfore need to ensure that MPD is already configured and running.</i> Basic single user setup <i>Select this option if your music collection is not shared with others, and you wish Cantata to configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients (e.g. MPDroid)</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' For more information on MPD itself, please refer to the MPD website <a href="http://www.musicpd.org"><span style=" text-decoration: underline; color:#0000ff;">http://www.musicpd.org</span></a><br/><br/>This 'wizard' will guide you through the basic settings required for Cantata to function correctly. Connection details 连接详情 The settings below are the basic settings required by Cantata. Please enter the relevant details, and use the 'Connect' button to test the connection. Host: 主机名: Password: 密码: Music folder: 音乐文件夹: Connect 连接 The 'Music folder' setting is used to lookup cover-art, lyrics, etc. If your MPD instance is on a remote host, you may set this to a HTTP URL. Music folder Please choose the folder containing your music collection. Covers and Lyrics <p>Cantata will download missing covers, and lyrics, from the internet.</p><p>For each of these, please confirm whether you wish Cantata to store the relevant files within the music folder, or within your personal cache/config folders.</p> Save downloaded covers, artist, and composer images, in music folder Save downloaded lyrics in music folder 保存下载的歌词到音乐文件夹 Save downloaded backdrops in music folder If you elect to have Cantata store covers, lyrics, or backdrops, within the music folder, and you do not have write access to this folder, then Cantata will revert to saving the files in your personal cache folder. Cantata can only save backdrops, artist, and composer images within the music folder hierarchy if this is 2 levels deep. i.e. 'Artist/Album/Tracks'. The 'Music folder' is set to a HTTP address, and Cantata currently cannot upload files to external HTTP servers. Therefore, the above settings should be left un-checked. Finished! Cantata is now configured!<br/><br/>Cantata's configuration dialog maybe used to customise Cantata's appearance, as well as to add extra MPD hosts, etc. Cantata will groups tracks into albums by using the 'AlbumArtist' tag if it is set, otherwise it will fallback to the 'Artist' tag. If you have albums with multiple artists, you <b>must</b> set the 'AlbumArtist' tag for the grouping to function correctly. It is suggested to use 'Various Artists' in this scenario. <b>Warning:</b> You are not currently a member of the 'users' group. Cantata will function better (saving of album covers, lyrics, etc. with the correct permissions) if you (or your administrator) add yourself to this group. If you do add yourself you will need to logout and back in for this to take effect. Not Connected 未连接 Connection Established Connection Failed 连接失败 Cantata will now terminate InputDialog Password 密码 Please enter password: 请输入密码: InterfaceSettings Sidebar Views Use the checkboxes below to configure which views will appear in the sidebar. If 'Play Queue' is not checked above, then it will appear to the side of the other views. If 'Info' is not checked above, then a button will be added to the toolbar allowing you to access song information. Options 选项 Style: 视图: Position: Only show icons, no text Auto-hide Play Queue 播放队列 Initially collapse albums Automatically expand current album Scroll to current track Prompt before clearing Separate action (and shortcut) for play queue search Background Image None Current album cover Custom image: Blur: 10px Opacity: 40% Toolbar Show stop button Show cover of current track Show track rating External 外部 Enable MPRIS D-BUS interface Show popup messages when changing tracks Show icon in notification area Minimize to notification area when closed On Start-up Show main window Hide main window Restore previous state Tweaks Artist && Album Sorting Enter a (comma separated) list of prefixes to ignore when sorting artist and albums. e.g. if set to 'The' then 'The Beatles' would be sorted by 'Beatles' Enter comma separated list of prefixes... Composer Support By default, Cantata uses the 'Album Artist' tag (or 'Artist' tag if a song has no 'Album Artist') to group songs and albums. For certain genres, e.g 'Classical', it may be preferable to use the 'Composer' tag (if set) to perform this grouping. Please enter a (comma separated) list of the genres with which you would like Cantata to use the 'Composer' tag. Enter comma separated list of genres... Single Tracks 单个音轨 If you have a lot of artists in your collection that only contain a single track, then it can be cumbersome for each of these to have their own entry in the list of artists. As a work-around for this, if you place these tracks into a separate folder, and enter this folder name below, then Cantata will group these under an album named 'Single Tracks' with an album artist of 'Various Artists' Folder that contains single track files... CUE Files A cue file is a metadata file which describes how the tracks of a CD are laid out. Changing any of the above will require a DB refresh (and possibly restarting Cantata) in order to take affect. General 流派 Fetch missing covers from Last.fm Show delete action in context menus Enforce single-click activation of items Show song information tooltips Language: Changing the 'Enforce single-click activation of items' setting will require a re-start of Cantata. Changing the language setting will require a re-start of Cantata. Changing the style setting will require a re-start of Cantata. Library Folders 文件夹 Playlists 播放列表 Internet - Streams, Jamendo, Maganatune, SoundCloud, and Podcasts Devices - UMS, MTP (e.g. Android), and AudioCDs Search (via MPD) Info - Current song information (artist, album, and lyrics) Large Small Tab-bar Left Right Top Bottom Images (*.png *.jpg) 图片 Images (*.png *.jpg) 10px pixels Notifications English (en) System default %1% value% %1 px pixels ItemView Go Back Updating... 更新... JamendoService The world's largest digital service for free music JamendoSettingsDialog Jamendo Settings Jamendo 设置 MP3 MP3 Ogg Streaming format: 流媒体格式: KeySequenceButton The key you just pressed is not supported by Qt. Unsupported Key KeySequenceWidget Click on the button, then enter the shortcut like you would in the program. Example for Ctrl+a: hold the Ctrl key and press a. Meta Meta key Ctrl Ctrl key Alt Alt key Shift Shift key Input What the user inputs now will be taken as the new shortcut None No shortcut defined Shortcut Conflict The "%1" shortcut is already in use, and cannot be configured. Please choose another one. The "%1" shortcut is ambiguous with the shortcut for the following action: Do you want to reassign this shortcut to the selected action? Reassign LastFmEngine Read more on last.fm LibraryDb Database error - please check Qt SQLite driver is installed LibraryPage Show Artist Images Sort Albums Name 名称 Year 年份 Album, Artist, Year Album, Year, Artist Artist, Album, Year Artist, Year, Album Year, Album, Artist Year, Artist, Album Modified Date Group By Genre 流派 Artist 艺术家 Album 专辑 Are you sure you wish to delete the selected songs? This cannot be undone. Delete Songs 删除歌曲 LyricSettings Choose the websites you want to use when searching for lyrics. 选择歌词搜索网站。 LyricsDialog If Cantata has failed to find lyrics, or has found the wrong ones, use this dialog to enter new search details. For example, the current song may actually be a cover-version - if so, then searching for lyrics by the original artist might help. If this search does find new lyrics, these will still be associated with the original song title and artist as displayed in Cantata. Title: 标题: Artist: 艺术家: Search For Lyrics 搜索歌词 MPDConnection Unknown 未知 Connection to %1 failed 连接 %1 失败 Connection to %1 failed - please check your proxy settings Connection to %1 failed - incorrect password 连接 %1 失败 - 密码错误 Connecting to %1 连接到 %1 Failed to send command to %1 - not connected 发送到 %1 失败 - 未能连接 Failed to load. Please check user "mpd" has read permission. 无法加载. 请检查 "mpd" 是否有读取权限. Failed to load. MPD can only play local files if connected via a local socket. 无法加载. MPD 只能从本地 Socket 播放本地文件. MPD reported the following error: %1 Failed to send command. Disconnected from %1 发送失败. %1 已断开 Failed to rename <b>%1</b> to <b>%2</b> <b>%1</b> 重命名为 <b>%2</b> 失败 Failed to save <b>%1</b> <b>%1</b> 保存失败 You cannot add parts of a cue sheet to a playlist! You cannot add a playlist to another playlist! Failed to send '%1' to %2. Please check %2 is registered with MPD. Cannot store ratings, as the 'sticker' MPD command is not supported. MagnatuneService None Streaming 流媒体 MP3 128k MP3 128k MP3 VBR Ogg Vorbis Ogg Vorbis FLAC FLAC WAV Online music from magnatune.com MagnatuneSettingsDialog Magnatune Settings Magnatune 设置 Username: 用户名: Password: 密码: Membership: Downloads: 下载: MainWindow [Dynamic] [动态] Exit Full Screen Configure Cantata... 设置 Cantata... Preferences Quit 退出 About Cantata... 关于 Cantata... Show Window 显示窗口 Server information... 服务器信息... Refresh Database 刷新数据 Refresh 刷新 Connect 连接 Collection Outputs 输出 Stop After Track 音轨后停止 Seek forward (%1 seconds) Seek backward (%1 seconds) Add To Stored Playlist 添加到已有列表 Crop Others Add Stream URL 添加流媒体 Clear 清除 Center On Current Track Expanded Interface 展开界面 Show Current Song Information Full Screen 全屏 Random 随机 Repeat 重复 Single 单曲 When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled. '单曲'启用时, 将在当前歌曲完成后停止播放. 或者 '重复'启用时, 将反复播放. Consume 省资源 When consume is activated, a song is removed from the play queue after it has been played. 当节省资源打开时,播放过的歌曲将被从队列中移除. Find in Play Queue Play Stream 播放流媒体 Locate In Library 在库中定位 Play next Edit Track Information (Play Queue) Expand All 展开所有 Collapse All 收起所有 Cancel 取消 Play Queue 播放队列 Library Folders 文件夹 Playlists 播放列表 Internet Devices 设备 Search 搜索 Info 信息 &Music &Edit &View &Queue &Settings 设置(&S) &Help 帮助(&H) Set Rating No Rating Failed to locate any songs matching the dynamic playlist rules. 无法给歌曲匹配动态设置. Connecting to %1 连接到 %1 Refresh MPD Database? About Cantata 关于 Cantata <b>Cantata %1</b><br/><br/>MPD client.<br/><br/>&copy; 2011-2017 Craig Drummond<br/>Released under the <a href="http://www.gnu.org/licenses/gpl.html">GPLv3</a> Based upon <a href="http://lowblog.nl">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/> Context view backdrops courtesy of <a href="http://www.fanart.tv">FanArt.tv</a> Context view metadata courtesy of <a href="http://www.wikipedia.org">Wikipedia</a> and <a href="http://www.last.fm">Last.fm</a> Please consider uploading your own music fan-art to <a href="http://www.fanart.tv">FanArt.tv</a> A Podcast is currently being downloaded Quiting now will abort the download. Abort download and quit Please close other dialogs first. 请先关闭其他对话框. Enabled: %1 Disabled: %1 Server Information 服务器信息 <tr><td colspan="2"><b>Server</b></td></tr><tr><td align="right">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr><tr><td align="right">Uptime:&nbsp;</td><td>%4</td></tr><tr><td align="right">Playing:&nbsp;</td><td>%5</td></tr><tr><td align="right">Handlers:&nbsp;</td><td>%6</td></tr><tr><td align="right">Tags:&nbsp;</td><td>%7</td></tr> <tr><td colspan="2"><b>Database</b></td></tr><tr><td align="right">Artists:&nbsp;</td><td>%1</td></tr><tr><td align="right">Albums:&nbsp;</td><td>%2</td></tr><tr><td align="right">Songs:&nbsp;</td><td>%3</td></tr><tr><td align="right">Duration:&nbsp;</td><td>%4</td></tr><tr><td align="right">Updated:&nbsp;</td><td>%5</td></tr> Cantata (%1) MPD reported the following error: %1 Cantata Playback stopped Remove all songs from play queue? Priority 优先级 Enter priority (0..255): 输入优先级 (0到255) Decrease priority for each subsequent track Playlist Name 播放列表名称 Enter a name for the playlist: 输入播放列表名称: '%1' is used to store favorite streams, please choose another name. A playlist named '%1' already exists! Add to that playlist? Existing Playlist 已有同名播放列表 %n Track(s) %n 音轨 %n Tracks (%1) %n 音轨 (%1) MenuButton Menu MessageOverlay Cancel 取消 Mpris (Stream) (流媒体) MtpConnection Connecting to device... 正在连接到设备 ... No devices found 没有发现设备 Connected to device 连接到设备 Disconnected from device 设备连接断开 Updating folders... 更新文件夹... Updating files... 更新文件... Updating tracks... 更新音轨... MtpDevice Not Connected 未连接 %1 free 剩余 %1 MusicBrainz Failed to open CD device 打开 CD 设备失败 Track %1 %1 (Disc %2) %1 (碟 %2) No matches found in MusicBrainz 没有在 MusicBrainz 中找到匹配数据 MusicLibraryModel Cue Sheet Cue 表 Playlist 播放列表 %n Track(s) %n 音轨 %n Artist(s) %n 艺术家 %n Album(s) %n 专辑" %n Tracks (%1) %n 音轨 (%1) %1 by %2 Album by Artist %2 的 %1 NoteLabel <i><b>NOTE:</b> %1</i> NowPlayingWidget (Stream) (流媒体) OSXStyle &Window Close Minimize Zoom OnlineDbService Downloading...%1% Parsing music list.... Failed to download 下载失败 %n Artist(s) %n 艺术家 OnlineDbWidget Group By Genre 流派 Artist 艺术家 Configure 设置 The music listing needs to be downloaded, this can consume over %1Mb of disk space Dowload music listing? Download Re-download music listing? OnlineSearchService Searching... OnlineSearchWidget No tracks found. %n Tracks (%1) %n 音轨 (%1) OnlineSettings Use the checkboxes below to configure the list of active services. Configure Service OnlineView Song Information OnlineXmlParser Failed to parse 操作失败 OpmlBrowsePage Reload Failed to download directory listing Failed to parse directory listing OtherSettings Background Image None Artist image Custom image: Blur: 10px Opacity: 40% Automatically switch to view after: Do not auto-switch ms 毫秒 Dark background Darken background, and use white text, regardless of current color palette. Always collapse into a single pane Only show 'Artist', 'Album', or 'Track' even if sufficient width to show all three. Only show basic wikipedia text Cantata only shows a trimmed down version of wikipedia pages (no images, links, etc). This trimming is not always 100% accurate, which is why Cantata defaults to only showing the introduction. If you elect to show the full article, then there may be parsing errors. You will also need to remove any currently cached articles (using the 'Cache' page). Images (*.png *.jpg) 图片 Images (*.png *.jpg) 10px pixels %1% value% %1 px pixels PathRequester Select Folder 选择文件夹 Select File 选择文件 PlayQueueModel Title 标题 Artist 艺术家 Album 专辑 # Track number Length 长度 Disc 碟片 Year 年份 Original Year Genre 流派 Priority 优先级 Composer Performer Rating Remove Duplicates Undo Redo Shuffle Tracks 音轨 Albums 专辑 Sort By Album Artist 专辑艺术家 Track Title 音轨标题 Track Number # (Track Number) PlayQueueView Remove 删除 PlaybackSettings Playback 回放 Fa&deout on stop: None ms 毫秒 Stop playback on exit 退出时停止回放 Inhibit suspend whilst playing If you press and hold the stop button, then a menu will be shown allowing you to choose whether to stop playback now, or after the current track. (The stop button can be enabled in the Interface/Toolbar section) Output 输出 <i>Not Connected!<br/>The entries below cannot be modified, as Cantata is not connected to MPD.</i> &Crossfade between tracks: s Replay &gain: About replay gain Use the checkboxes below to control the active outputs. Track 音轨 Album 专辑 Auto <i>Connected to %1<br/>The entries below apply to the currently connected MPD collection.</i> Replay Gain is a proposed standard published in 2001 to normalize the perceived loudness of computer audio formats such as MP3 and Ogg Vorbis. It works on a track/album basis, and is now supported in a growing number of players.<br/><br/>The following ReplayGain settings may be used:<ul><li><i>None</i> - No ReplayGain is applied.</li><li><i>Track</i> - Volume will be adjusted using the track's ReplayGain tags.</li><li><i>Album</i> - Volume will be adjusted using the albums's ReplayGain tags.</li><li><i>Auto</i> - Volume will be adjusted using the track's ReplayGain tags if random play is activated, otherwise the album's tags will be used.</li></ul> PlaylistRule Type: 类型: Include songs that match the following: Exclude songs that match the following: Artist: 艺术家: Artists similar to: 相似艺术家: Album Artist: 专辑艺术家: Composer: Album: 专辑: Title: 标题: Genre 流派 From Year: 年份: Any 任意 To Year: 年份: Comment: Filename / path: Exact match 精确匹配 Only enter values for the tags you wish to be search on. For genre, end string with an asterisk to match various genres. e.g 'rock*' matches 'Hard Rock' and 'Rock and Roll'. PlaylistRuleDialog Dynamic Rule 动态规则 Smart Rule Add 添加 <i><b>ERROR</b>: 'From Year' should be less than 'To Year'</i> <i><b>错误</b>: 原有的年份应在更改后的年份之前</i> <i><b>ERROR:</b> Date range is too large (can only be a maximum of %1 years)</i> <i><b>错误:</b> 日期范围过大 (最多不超过 %1 年)</i> <i><b>ERROR:</b> You can only match on filename / path if 'Exact match' is <b>not</b> checked</i> PlaylistRules Name of Dynamic Rules 动态配置名称 Add 添加 Edit 编辑 Remove 删除 Songs with ratings between: - Songs with duration between: seconds Number of songs in play queue: Order songs: About Rules 关于规则 PlaylistRulesDialog Dynamic Rules 动态规则 None No Limit About dynamic rules Smart Rules Ascending Descending Name of Smart Rules Number of songs <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will randomly select songs to keep the play queue filled with specified number of entries (10 by default). If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> About smart rules <p>Cantata will query your library using all of the rules listed. The list of <i>Include</i> rules will be used to build a set of songs that can be used. The list of <i>Exclude</i> rules will be used to build a set of songs that cannot be used. If there are no <i>Include</i> rules, Cantata will assume that all songs (bar those from <i>Exclude</i>) can be used.</p><p>e.g. to have Cantata look for 'Rock songs by Wibble OR songs by Various Artists', you would need the following: <ul><li>Include AlbumArtist=Wibble Genre=Rock</li><li>Include AlbumArtist=Various Artists</li></ul> To have Cantata look for 'Songs by Wibble but not from album Abc', you would need the following: <ul><li>Include AlbumArtist=Wibble</li><li>Exclude AlbumArtist=Wibble Album=Abc</li></ul>After the set of usable songs has been created, Cantata will add the desired number of songs to the play queue. If a range of ratings has been specified, then only songs with a rating within this range will be used. Likewise, if a duration has been set.</p> Failed to save %1 %1 保存失败 A set of rules named '%1' already exists! Overwrite? Overwrite Rules Saving %1 正在保存 %1 PlaylistsModel New Playlist... 新播放列表... Stored Playlists Standard playlists %n Tracks (%1) %n 音轨 (%1) Smart Playlist Plurals %n Track(s) %n 音轨 %n Tracks (%1) %n 音轨 (%1) %n Album(s) %n 专辑" %n Artist(s) %n 艺术家 %n Stream(s) %n 流媒体 %n Entry(s) %n 项 %n Rule(s) %n 规则 PodcastPage RSS: Website: Podcast details Select a podcast to display its details PodcastSearchDialog Subscribe Enter URL Manual podcast URL Search %1 Search for podcasts on %1 Add Podcast Subscription Browse %1 Browse %1 podcasts You are already subscribed to this podcast! Subscription added PodcastSearchPage Enter search term... Search 搜索 Failed to fetch podcasts from %1 There was a problem parsing the response from %1 PodcastService Subscribe to RSS feeds %n Podcast(s) %1 (%2) podcast name (num unplayed episodes) %n Episode(s) (Downloading: %1%) Failed to parse %1 Cantata only supports audio podcasts! %1 contains only video podcasts. Failed to download %1 PodcastSettingsDialog Check for new episodes: Download episodes to: Download automatically: Podcast Settings Manually Every 15 minutes Every 30 minutes Every hour Every 2 hours Every 6 hours Every 12 hours Every day Every week Don't automatically download episodes Latest episode Latest %1 episodes All episodes PodcastUrlPage URL Enter podcast URL... Load Enter podcast URL below, and press 'Load' Invalid URL! Failed to fetch podcast! Failed to parse podcast. Cantata only supports audio podcasts! The URL entered contains only video podcasts. PodcastWidget Add Subscription Remove Subscription Download Episodes Delete Downloaded Episodes Cancel Download Mark Episodes As New Mark Episodes As Listened Show Unplayed Only Unsubscribe from '%1'? Do you wish to download the selected podcast episodes? Cancel podcast episode downloads (both current and any that are queued)? Do you wish to the delete downloaded files of the selected podcast episodes? Do you wish to mark the selected podcast episodes as new? Do you wish to mark the selected podcast episodes as listened? Refresh all subscriptions? Refresh 刷新 Refresh All Refresh all subscriptions, or only those selected? Refresh Selected PowerManagement Cantata is playing a track PreferencesDialog Collection Collection Settings Playback 回放 Playback Settings 回放设置 Downloaded Files Downloaded Files Settings Interface 界面 Interface Settings 界面设置 Info 信息 Info View Settings Scrobbling Scrobbling Settings Audio CD 音乐 CD Audio CD Settings 音乐 CD 设置 Proxy Proxy Settings 代理设置 Shortcuts 快捷 Keyboard Shortcut Settings 快捷键设定 Cache 缓存 Cached Items 缓存项目 Custom Actions Cantata Preferences Configure 设置 ProxySettings Mode: Type: 类型: HTTP Proxy HTTP 代理 SOCKS Proxy SOCKS 代理 Host: 主机名: Port: 端口: Username: 用户名: Password: 密码: No proxy Use the system proxy settings 使用系统代理 Manual proxy configuration 手动代理设置 QObject Track listing Read more on wikipedia Open in browser <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>Advanced Audio Coding</a> (AAC) is a patented lossy codec for digital audio.<br>AAC generally achieves better sound quality than MP3 at similar bit rates. It is a reasonable choice for the iPod and some other portable music players. <a href=http://en.wikipedia.org/wiki/Advanced_Audio_Coding>高级音频编码 AAC </a>是一种有专利授权的无损压缩数字音频格式.<br> 它的音质比同比特率的 MP3 更好.它是 iPod 或者其他移动播放设备上的一个较好选择.限于非商业合同使用 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>AAC</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate#Advantages_and_disadvantages_of_VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the <a href=http://www.ffmpeg.org/faq.html#SEC21>average bitrate</a> of the encoded track.<br><b>150kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>200kb/s</b> is probably overkill. Expected average bitrate for variable bitrate encoding 可变比特率编码使用平均比特率 Smaller file 较小文件 Better sound quality 较好音质 <a href=http://en.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) is a patented digital audio codec using a form of lossy data compression.<br>In spite of its shortcomings, it is a common format for consumer audio storage, and is widely supported on portable music players. <a href=http://zh.wikipedia.org/wiki/MP3>MPEG Audio Layer 3</a> (MP3) 是一种有损压缩的专利音频编码格式.<br>尽管有缺陷,但它仍然市易奏常见的音频存储格式,几乎所有的移动播放设备都支持 MP3. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>MP3</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/MP3#VBR>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>160kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>120kb/s</b> might be unsatisfactory for music and anything above <b>205kb/s</b> is probably overkill. Ogg Vorbis Ogg Vorbis <a href=http://en.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a> is an open and royalty-free audio codec for lossy audio compression.<br>It produces smaller files than MP3 at equivalent or higher quality. Ogg Vorbis is an all-around excellent choice, especially for portable music players that support it. <a href=http://zh.wikipedia.org/wiki/Vorbis>Ogg Vorbis</a>是一种开放的自由的 无损压缩音频编码格式.<br>它的文件比同比特率的 MP3 更小. Ogg Vorbis是一种很不错的选择,特别是对支持它的播放器来说. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Vorbis</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Vorbis#Technical_Encoder>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>The Vorbis encoder uses a quality rating between -1 and 10 to define a certain expected audio quality level. The bitrate measure in this slider is just a rough estimate (provided by Vorbis) of the average bitrate of the encoded track given a quality value. In fact, with newer and more efficient Vorbis versions the actual bitrate is even lower.<br><b>5</b> is a good choice for music listening on a portable player.<br/>Anything below <b>3</b> might be unsatisfactory for music and anything above <b>8</b> is probably overkill. Quality rating 评级 Opus <a href=http://en.wikipedia.org/wiki/Opus_(audio_format)>Opus</a> is a patent-free digital audio codec using a form of lossy data compression. The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>The <b>Opus</b> encoder used by Cantata supports a <a href=http://en.wikipedia.org/wiki/Variable_bitrate>variable bitrate (VBR)</a> setting, which means that the bitrate value fluctuates along the track based on the complexity of the audio content. More complex intervals of data are encoded with a higher bitrate than less complex ones; this approach yields overall better quality and a smaller file than having a constant bitrate throughout the track.<br>For this reason, the bitrate measure in this slider is just an estimate of the average bitrate of the encoded track.<br><b>128kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>100kb/s</b> might be unsatisfactory for music and anything above <b>256kb/s</b> is probably overkill. Bitrate 比特率 Apple Lossless Apple 无损 <a href=http://en.wikipedia.org/wiki/Apple_Lossless>Apple Lossless</a> (ALAC) is an audio codec for lossless compression of digital music.<br>Recommended only for Apple music players and players that do not support FLAC. <a href=http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_%EB%AC%B4%EC%86%90%EC%8B%A4>苹果无损格式 苹果无损格式</a> (ALAC) 是一种无损压缩的数字音乐格式.<br>推荐在苹果播放器或者其他不支持 FLAC 的播放器上使用. FLAC FLAC <a href=http://en.wikipedia.org/wiki/Free_Lossless_Audio_Codec>Free Lossless Audio Codec</a> (FLAC) is an open and royalty-free codec for lossless compression of digital music.<br>If you wish to store your music without compromising on audio quality, FLAC is an excellent choice. <a href=http://ko.wikipedia.org/wiki/FLAC>Free Lossless Audio Codec</a> (FLAC) 是一种开放的自由的无损压缩数字音乐格式.<br>如果想要确保音乐品质, FLAC 是最好的选择. The <a href=http://flac.sourceforge.net/documentation_tools_flac.html>compression level</a> is an integer value between 0 and 8 that represents the tradeoff between file size and compression speed while encoding with <b>FLAC</b>.<br/> Setting the compression level to <b>0</b> yields the shortest compression time but generates a comparably big file.<br/>On the other hand, a compression level of <b>8</b> makes compression quite slow but produces the smallest file.<br/>Note that since FLAC is by definition a lossless codec, the audio quality of the output is exactly the same regardless of the compression level.<br/>Also, levels above <b>5</b> dramatically increase compression time but create an only slightly smaller file, and are not recommended. Compression level 压缩级别 Faster compression 快速压缩 Windows Media Audio Windows Media Audio <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio>Windows Media Audio</a> (WMA) is a proprietary codec developed by Microsoft for lossy audio compression.<br>Recommended only for portable music players that do not support Ogg Vorbis. <a href=http://ko.wikipedia.org/wiki/%EC%9C%88%EB%8F%84_%EB%AF%B8%EB%94%94%EC%96%B4_%EC%98%A4%EB%94%94%EC%98%A4>Windows Media Audio</a> (WMA)是一种微软开发的有损压缩音频编码格式.<br>推荐用在不支持 Ogg Vorbis的设备上 The bitrate is a measure of the quantity of data used to represent a second of the audio track.<br>Due to the limitations of the proprietary <b>WMA</b> format and the difficulty of reverse-engineering a proprietary encoder, the WMA encoder used by Cantata sets a <a href=http://en.wikipedia.org/wiki/Windows_Media_Audio#Windows_Media_Audio>constant bitrate (CBR)</a> setting.<br>For this reason, the bitrate measure in this slider is a pretty accurate estimate of the bitrate of the encoded track.<br><b>136kb/s</b> is a good choice for music listening on a portable player.<br/>Anything below <b>112kb/s</b> might be unsatisfactory for music and anything above <b>182kb/s</b> is probably overkill. Empty filename. Invalid filename. (%1) Failed to save %1. %1. 保存失败 Failed to delete rules file. (%1) 删除 (%1) 失败. Invalid command. (%1) Could not remove active rules link. Active rules is not a link. Could not create active rules link. Rules file, %1, does not exist. Incorrect arguments supplied. Unknown method called. Unknown error Artist 艺术家 SimilarArtists 相似艺术家 AlbumArtist 专辑艺术家 Composer Comment Album 专辑 Title 标题 Genre 流派 Date 日期 File Include 包含 Exclude 排除 (Exact) (解压) %1 %2 x %3 (%4) name width x height (file size) %1 %2 x %3 (%4) %1 %2 x %3 name width x height Current Cover CoverArt Archive Grouped Albums 分组专辑 Table 表单 Parse in Library view, and show in Folders view Only show in Folders view Do not list 1 Track Singular 1 音轨 %1 Tracks Plural (N!=1) %1 音轨 1 Track (%1) Singular 1 音轨 (%1) %1 Tracks (%2) Plural (N!=1) %1 音轨 (%2) %1 Albums Plural (N!=1) %1 专辑 %1 Artists Plural (N!=1) %1 艺术家 1 Stream Singular 1 流媒体 %1 Streams Plural (N!=1) %1 流媒体 %1 Entries Plural (N!=1) %1 项 %1 Rules Plural (N!=1) %1 规则 Previous Track 上一个 Next Track 下一个 Play/Pause 播放/暂停 Stop 停止 Stop After Current Track 当前音轨后停止 Stop After Track 音轨后停止 Increase Volume 增大音量 Decrease Volume 减小音量 Save As 另存为 Append Append To Play Queue Append And Play Add And Play Append To Play Queue And Play Insert After Current Append Random Album Play Now (And Replace Play Queue) Add With Priority 添加时设定优先级 Set Priority 设置优先级 Highest Priority (255) 最高优先级 (255) High Priority (200) 高优先级 (200) Medium Priority (125) 中等优先级 (125) Low Priority (50) 低优先级 (50) Default Priority (0) 默认优先级 (0) Custom Priority... 自定义优先级... Add To Playlist 添加到播放列表 Organize Files 组织文件 Edit Track Information ReplayGain 播放增益 Copy Songs To Device Delete Songs 删除歌曲 Set Image Remove 删除 Find Add To Play Queue 添加到正在播放 Parse error loading cache file, please check your songs tags. Other Default 默认 "%1" (%2:%3) name (host:port) Single Tracks 单个音轨 Personal Unknown 未知 Various Artists 多个艺术家 Album artist Performer Track number Disc number Year 年份 Orignal Year Length 长度 <b>%1</b> on <b>%2</b> Song on Album <b>%1</b> by <b>%2</b> on <b>%3</b> Song by Artist on Album Invalid service Invalid method Authentication failed Invalid format Invalid parameters Invalid resource specified Operation failed Invalid session key Invalid API key Service offline Last.fm is currently busy, please try again in a few minutes Rate-limit exceeded General 流派 Digitally Imported Local and National Radio (ListenLive) &OK &Cancel &Yes &No &Discard &Save &Apply &Close &Help 帮助(&H) &Overwrite &Reset &Continue &Delete &Stop &Remove &Previous &Next Close Error 错误 Information 信息 Warning 警告 Question 问题 %1 B %1 B %1 kB %1 MB %1 MB %1 GB %1 GB %1 KiB %1 KiB %1 MiB %1 MiB %1 GiB %1 GiB Basic Tree (No Icons) Simple Tree 简单树形 Detailed Tree 详细信息 List 列表 Grid %n Track(s) %n 音轨 %n Tracks (%1) %n 音轨 (%1) %n Album(s) %n 专辑" %n Artist(s) %n 艺术家 %n Stream(s) %n 流媒体 %n Entry(s) %n 项 %n Rule(s) %n 规则 RemoteDevicePropertiesDialog Device Properties 设备属性 Connection 连接 Music Library 音乐库 Add Device 添加设备 A remote device named '%1' already exists! Please choose a different name. RemoteDevicePropertiesWidget These settings are only editable when the device is not connected. Type: 类型: Name: 名称: Options 选项 Host: 主机名: Port: 端口: User: 用户: Domain: 域名 Password: 密码: Share: 共享: If you enter a password here, it will be stored <b>unencrypted</b> in Cantata's config file. To have Cantata prompt for the password before accessing the share, set the password to '-' Service name: 服务名称: Folder: 文件夹: Extra Options: 额外选项: Due to the way sshfs works, a suitable ssh-askpass application (ksshaskpass, ssh-askpass-gnome, etc.) will be required to enter the password. This dialog is only used to add remote devices (e.g. via Samba), or to access locally mounted folders. For normal media players, attached via USB, Cantata will automatically display the device when it is attached. Samba Share Samba 共享 Samba Share (Auto-discover host and port) Samba 共享(自动设置主机名和端口) Secure Shell (sshfs) 安全 Shell (sshfs) Locally Mounted Folder 本地挂载的文件夹 RemoteFsDevice Available 可用 Not Available 不可用 Failed to resolve connection details for %1 解析连接 %1 的信息失败 Connecting... 连接中... Password prompting does not work when cantata is started from the commandline. Cantata 从命令行启动时无法使用密码. No suitable ssh-askpass application installed! This is required for entering passwords. 匹配的 ssh-askpass 程序未安装! 输入密码必须安装它. Mount point ("%1") is not empty! 挂载点 ("%1") 非空! "sshfs" is not installed! "sshfs"未安装! Disconnecting... 正在断开连接... "fusermount" is not installed! "fusermount"未安装! Failed to connect to "%1" "%1" 连接失败 Failed to disconnect from "%1" "%1" 的连接无法断开 Updating tracks... 更新音轨... Not Connected 未连接 Capacity Unknown 容量未知 %1 free 剩余 %1 RgDialog ReplayGain 播放增益 Show All Tracks Show Untagged Tracks Remove From List Artist 艺术家 Album 专辑 Title 标题 Album Gain 专辑增益 Track Gain 音轨增益 Album Peak 专辑峰值 Track Peak 音轨峰值 Scan 扫描 Update ReplayGain tags in tracks? Update Tags Abort scanning of tracks? Abort Abort reading of existing tags? Scan <b>all</b> tracks?<br/><br/><i>All tracks have existing ReplayGain tags.</i> Do you wish to scan all tracks, or only tracks without existing tags? Untagged Tracks 音轨无标签 All Tracks 所有音轨 Scanning tracks... Reading existing tags... 读取标签... %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: 下列歌曲标签更新失败: Device has been removed! 设备已移除! Device is not connected. 设备未连接. Device is busy? 设备正忙? %1 dB %1 dB Failed 失败 Original: %1 dB Original: %1 Remove the selected tracks from the list? Remove Tracks RulesPlaylists Album Artist 专辑艺术家 Artist 艺术家 Album 专辑 Composer Date 日期 Genre 流派 Rating File Age Random 随机 %n Rule(s) %n 规则 , Rating: %1..%2 Ascending Descending Scrobbler %1 error: %2 ScrobblingLove %1: Loved Current Track %1: Love Current Track ScrobblingSettings Scrobble using: Username: 用户名: Password: 密码: Status: Login Scrobble tracks Show 'Love' button %1 (via MPD) scrobbler name (via MPD) If you use a scrobbler which is marked as '(via MPD)' (such as %1), then you will need to have this already started and running. Cantata can only 'Love' tracks via this, and cannot enable/disable scrobbling. Authenticating... Authenticated Not Authenticated ScrobblingStatus %1: Scrobble Tracks SearchModel # (Track Number) SearchPage Locate In Library 在库中定位 Artist: 艺术家: Composer: Performer: Album: 专辑: Title: 标题: Genre: 流派: Comment: Date: Find songs be searching the 'Date' tag.<br/><br/>Usually just entering the year should suffice. Original Date: Find songs be searching the 'Original Date' tag.<br/><br/>Usually just entering the year should suffice. Modified: Enter date (YYYY/MM/DD - e.g. 2015/01/31) to search for files modified since that date.<br/><br>Or enter a number of days to find files that were modified in the previous number of days. File: Any: No tracks found. %n Tracks (%1) %n 音轨 (%1) SearchWidget Search... Close Search Bar ServerSettings Collection: Name: 名称: Host: 主机名: Password: 密码: Music folder: 音乐文件夹: Cover filename: 封面名称: <p>Filename (without extension) to save downloaded covers as.<br/>If left blank 'cover' will be used.<br/><br/><i>%artist% will be replaced with album artist of the current song, and %album% will be replaced with the album name.</i></p> HTTP stream URL: The 'Music folder' setting is used to lookup cover-art. It may be set to a HTTP URL if your MPD is on another machine, and covers are accessible via HTTP. If it is not set to a HTTP URL, and you also have write permissions to this folder (and it's sub-folders), then Cantata will save any downloaded covers into the respective album folder. If no setting is specified for 'Cover filename', then Cantata will use a default of <code>cover</code> 'HTTP Stream URL' is only of use if you have MPD configured to output to a HTTP stream, and you wish Cantata to be able to play that stream. If you change the 'Music folder' setting, then you will need to manually update the music database. This can be performed by pressing the 'Refresh Database' button in the 'Artists' or 'Albums' views. This folder will also be used to locate music files for tag-editing, replay gain, and transferring to (and from) devices. This folder will also be used to locate music files for tag-editing, replay gain, etc. Which type of collection do you wish to connect to? Standard - music collection may be shared, is on another machine, is already setup, or you wish to enable access from other clients (e.g. MPDroid) Basic - music collection is not shared with others, and Cantata will configure and control the MPD instance. This setup will be exclusive to Cantata, and will <b>not</b> be accessible to other MPD clients. <i><b>NOTE:</b> %1</i> If you wish to have an advanced MPD setup (e.g. multiple audio outputs, full DSD support, etc) then you <b>must</b> choose 'Standard' Add Collection Standard Basic Delete '%1'? Delete New Collection %1 Default 默认 ServiceStatusLabel Logged into %1 <b>NOT</b> logged into %1 ShortcutsModel Action Shortcut ShortcutsSettingsWidget Search: 搜索: Shortcut for Selected Action 选定动作的快捷键 Default: 默认: None Custom: 自定义: SinglePageWidget Refresh 刷新 View SmartPlaylists Smart Playlists Rules based playlists SmartPlaylistsPage Add 添加 Edit 编辑 Remove 删除 Are you sure you wish to remove the selected rules? This cannot be undone. Remove Smart Rules Failed to locate any matching songs SongDialog Cannot access song files! Please check Cantata's "Music folder" setting, and MPD's "music_directory" setting. Cannot access song files! Please check that the device is still attached. SongView Lyrics 歌词 Information 信息 Metadata Scroll Lyrics Refresh Lyrics Edit Lyrics 编辑歌词 Delete Lyrics File 删除歌词文件 Refresh Track Information Cancel 取消 Track 音轨 Reload lyrics? Reload from disk, or delete disk copy and download? Reload Reload From Disk Download Current playing song has changed, still perform search? 当前播放的歌曲已改变,继续搜索? Song Changed Perform Search Delete lyrics file? 确定删除歌词? Delete File Artist 艺术家 Album artist Composer Lyricist Conductor Remixer Album 专辑 Subtitle Track number Disc number Genre 流派 Date 日期 Original date Comment Copyright Label Catalogue number Title sort Artist sort Album artist sort Album sort Encoded by Encoder 编码 Mood Media Bitrate 比特率 Sample rate Channels Tagging time Performer (%1) %1 kb/s %1 Hz Bits Performer Year 年份 Filename Fetching lyrics via %1 SoundCloudService Search for tracks from soundcloud.com SpaceLabel Calculating... 计算中... Total space used: %1 SqlLibraryModel %n Artist(s) %n 艺术家 %n Album(s) %n 专辑" %n Tracks (%1) %n 音轨 (%1) Cue Sheet Cue 表 Playlist 播放列表 StoredPlaylistsPage Rename 重命名 Remove Duplicates Initially Collapse Albums Are you sure you wish to remove the selected playlists? This cannot be undone. Remove Playlists Playlist Name 播放列表名称 Enter a name for the playlist: 输入播放列表名称: A playlist named '%1' already exists! Overwrite? Overwrite Playlist Rename Playlist 重命名播放列表 Enter new name for playlist: 输入播放列表新名称: Cannot add songs from '%1' to '%2' StreamDialog Add stream to favourites Name: 名称: URL: Add Stream 添加流媒体 Edit Stream 编辑流媒体 <i><b>ERROR:</b> Invalid protocol</i> <i><b>错误:</b>协议无效</i> StreamFetcher Loading %1 StreamProviderListDialog Installed Update available Check the providers you wish to install/update. Install/Update Stream Providers Downloading list... Failed to download list of stream providers! Installing/updating %1 Failed to install '%1' Failed to download '%1' Install/update the selected stream providers? Install the selected stream providers? Update the selected stream providers? Install/Update Abort installation/update? Abort %n Update(s) available Downloading %1 Update all updateable providers StreamSearchModel TuneIn ShoutCast Dirble Stream Search Search for radio streams Enter string to search Not Loaded 未加载 Loading... %n Entry(s) %n 项 StreamSearchPage Added '%1'' to favorites StreamsBrowsePage Import Streams Into Favorites Export Favorite Streams Add New Stream To Favorites Edit 编辑 Seatch For Streams Configure 设置 Digitally Imported Service name Import Streams 导入流媒体 XML Streams (*.xml *.xml.gz *.cantata) Export Streams 导出流媒体 XML Streams (*.xml.gz) Failed to create '%1'! Stream '%1' already exists! A stream named '%1' already exists! Bookmark added Already bookmarked Already in favorites Reload '%1' streams? Are you sure you wish to remove bookmark to '%1'? Are you sure you wish to remove all '%1' bookmarks? Are you sure you wish to remove the %1 selected streams? 确定移除选择的流媒体 %1 ? Are you sure you wish to remove '%1'? 确定要移除 '%1'? Added '%1'' to favorites StreamsModel Bookmarks TuneIn IceCast ShoutCast Dirble Favorites Bookmark Category Add Stream To Favorites Configure Digitally Imported Reload Streams 流媒体 Radio stations Not Loaded 未加载 Loading... %n Entry(s) %n 项 StreamsSettings Use the checkboxes below to configure the list of active providers. Built-in categories are shown in italic, and these cannot be removed. Configure Streams From File... Download... Configure Provider Install Remove 删除 Install Streams Cantata Streams (*.streams) A category named '%1' already exists! Overwrite? Failed top open package file. Invalid file format! Failed to create stream category folder! Failed to save stream list! Are you sure you wish to remove '%1'? 确定要移除 '%1'? Failed to remove streams folder! SyncCollectionWidget Search 搜索 Check Items Uncheck Items SyncDialog Library: Device: Loading all songs from library, please wait... <code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists songs that are only on the device.<br/>Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. Then press the <code>Synchronize</code> button. Synchronize 同步 Device and library are in sync. 设备和库已同步 Loading all songs from library, please wait...%1%... Local Music Library Properties 本地音乐库属性 Device has been removed! 设备已移除! Device has been changed? 设备已改变? Device is busy? 设备正忙? TableView Stretch Columns To Fit Window Left Center Right Alignment TagEditor Track: 音轨: Title: 标题: Artist: 艺术家: Album artist: 专辑艺术家: Composer: Album: 专辑: Track number: 音轨号: Disc number: 碟片序号: Genre: 流派: Year: 年份: Rating: <i>(Various)</i> Comment: Multiple genres should be separated via a comma (e.g. 'Rock,Hard Rock') Ratings are stored in an external database, and <b>not</b> in the song's file. Tags 标签 Tools 工具 Apply "Various Artists" Workaround 使用"多个艺术家" 环境 Revert "Various Artists" Workaround 恢复 "多个艺术家" 环境 Set 'Album Artist' from 'Artist' Capitalize 转为大写 Adjust Track Numbers 调整音轨号 Read Ratings from File Write Ratings to File All tracks 所有音轨 Apply "Various Artists" workaround to <b>all</b> tracks? 应用"多个艺术家" 环境到 <b>所有</b> 音轨? Apply "Various Artists" workaround? 确定使用"多个艺术家" 环境? <i>This will set 'Album artist' and 'Artist' to "Various Artists", and set 'Title' to "TrackArtist - TrackTitle"</i> <i>'专辑艺人' 和 '艺术家'将被设置成 '多个艺术家','标题'将被设置为'音轨艺人 - 音轨标题'。</i> Revert "Various Artists" workaround on <b>all</b> tracks? 确定恢复 "多个艺术家" 环境到 <b>所有</b>音轨? Revert "Various Artists" workaround 恢复 "多个艺术家" 环境 <i>Where the 'Album artist' is the same as 'Artist' and the 'Title' is of the format "TrackArtist - TrackTitle", 'Artist' will be taken from 'Title' and 'Title' itself will be set to just the title. e.g. <br/><br/>If 'Title' is "Wibble - Wobble", then 'Artist' will be set to "Wibble" and 'Title' will be set to "Wobble"</i> Revert Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty) for <b>all</b> tracks? 获取"专辑艺人"标签并应用到"艺术家(如果为空)" 环境到 <b>所有</b> 音轨? Set 'Album Artist' from 'Artist' (if 'Album Artist' is empty)? 获取"专辑艺人"标签并应用到"艺术家(如果为空)" ? Album Artist from Artist Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc) of <b>all</b> tracks? Capitalize the first letter of text fields (e.g. 'Title', 'Artist', etc)? Adjust the value of each track number by: 调整每个音轨号按: Adjust track number by: 调整音轨号为: Read ratings for all tracks from the music files? Read rating from music file? Ratings Read Ratings Read Rating Read, and updated, ratings from the following tracks: Not all Song ratings have been read from MPD! Song ratings are not stored in the song files, but within MPD's 'sticker' database. In order to save these into the actual file, Cantata must first read them from MPD. Song rating has not been read from MPD! Write ratings for all tracks to the music files? Write rating to music file? Write Ratings Write Rating Failed to write ratings of the following tracks: Failed to write rating to music file! All tracks [modified] 所有音轨 [已修改] %1 [modified] %1 [已修改] %1 (Corrupt tags?) filename (Corrupt tags?) Failed to update the tags of the following tracks: 下列歌曲标签更新失败: Would you also like to rename your song files, so as to match your tags? Rename Files Rename 重命名 Device has been removed! 设备已移除! Device is not connected. 设备未连接. Device is busy? 设备正忙? TagSpinBox (Various) (多个) ThinSplitter Reset Spacing TitleWidget Click to go back Add All To Play Queue Add All And Replace Play Queue ToggleList Available: Selected: TrackOrganiser Filenames 文件名 Filename scheme: 文件名框架 VFAT safe Use only ASCII characters Replace spaces with underscores Append 'The' to artist names Original Name 原始名称 New Name 新名称 Ratings will be lost if a file is renamed. Organize Files 组织文件 Rename 重命名 Remove From List Abort renaming of files? Abort Source file does not exist! Skip 跳过 Auto Skip 自动跳过 Destination file already exists! Failed to create destination folder! Failed to rename '%1' to '%2' Remove the selected tracks from the list? Remove Tracks Song ratings are not stored in the song files, but within MPD's 'sticker' database. If you rename a file (or the folder it is within), then the rating associated with the song will be lost. Device has been removed! 设备已移除! Device is not connected. 设备未连接. Device is busy? 设备正忙? TrayItem Cantata Now playing 正在播放 UltimateLyricsProvider (Polish Translations) (波兰语翻译) (Portuguese Translations) (葡萄牙语翻译) UmsDevice Not Scanned 未扫描 Not Connected 未连接 %1 free 剩余 %1 ValueSlider (recommended) (推荐) View Cancel 取消 VolumeSlider Mute 静音 Unmute Volume %1% (Muted) Volume %1% 音量 %1% WikipediaEngine artist|band|singer|vocalist|musician Search pattern for an artist or band, separated by | album|score|soundtrack Search pattern for an album, separated by | WikipediaSettings Choose the wikipedia languages you want to use when searching for artist and album information. Reload cantata-2.2.0/translations/update.sh000077500000000000000000000005521316350454000174730ustar00rootroot00000000000000#!/bin/sh PATH=.:$PATH for app in lupdate lconvert sed grep ; do which $app > /dev/null 2>&1 if [ $? -ne 0 ] ; then echo "ERROR: Could not find $app" exit fi done find .. -name '*.cpp' -o -name '*.h' -o -name '*.c' -o -name '*.ui' | grep -v "solid-lite" | grep -v "3rdparty" | sort > filelist lupdate @filelist -ts *.ts rm filelist cantata-2.2.0/widgets/000077500000000000000000000000001316350454000145755ustar00rootroot00000000000000cantata-2.2.0/widgets/actionitemdelegate.cpp000066400000000000000000000201011316350454000211220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "actionitemdelegate.h" #include "models/roles.h" #include "support/icon.h" #include "support/utils.h" #include "config.h" #include "models/actionmodel.h" #include "groupedview.h" #include #include #include #include #include #include #include int ActionItemDelegate::constBorder = 1; int ActionItemDelegate::constActionBorder = 4; int ActionItemDelegate::constActionIconSize = 16; int ActionItemDelegate::constLargeActionIconSize = 22; void ActionItemDelegate::setup() { int height=QApplication::fontMetrics().height(); if (height>17) { constActionIconSize=Icon::stdSize(((int)(height/4))*4); constLargeActionIconSize=Icon::stdSize(((int)(height/3))*3); constBorder=constActionIconSize>22 ? 2 : 1; constActionBorder=constActionIconSize>32 ? 6 : 4; } else { constActionBorder=4; constActionIconSize=16; constLargeActionIconSize=22; constBorder=1; } } QRect ActionItemDelegate::calcActionRect(bool rtl, ActionPos actionPos, const QRect &o) const { int iconSize=largeIcons ? constLargeActionIconSize : constActionIconSize; QRect rect=AP_HBottom==actionPos ? QRect(o.x(), o.y()+(o.height()/2), o.width(), o.height()/2) : o; return rtl ? AP_VTop==actionPos ? QRect(rect.x()+(constBorder*4)+4, rect.y()+(constBorder*4)+4, iconSize, iconSize) : QRect(rect.x()+constActionBorder, rect.y()+((rect.height()-iconSize)/2), iconSize, iconSize) : AP_VTop==actionPos ? QRect(rect.x()+rect.width()-(iconSize+(constBorder*4))-4, rect.y()+(constBorder*4)+4, iconSize, iconSize) : QRect(rect.x()+rect.width()-(iconSize+constActionBorder), rect.y()+((rect.height()-iconSize)/2), iconSize, iconSize); } void ActionItemDelegate::adjustActionRect(bool rtl, ActionPos actionPos, QRect &rect, int iconSize) { if (rtl) { if (AP_VTop==actionPos) { rect.adjust(0, iconSize+constActionBorder, 0, iconSize+constActionBorder); } else { rect.adjust(iconSize+constActionBorder, 0, iconSize+constActionBorder, 0); } } else { if (AP_VTop==actionPos) { rect.adjust(0, iconSize+constActionBorder, 0, iconSize+constActionBorder); } else { rect.adjust(-(iconSize+constActionBorder), 0, -(iconSize+constActionBorder), 0); } } } static void drawBgnd(QPainter *painter, const QRect &rx, bool light) { QRectF r(rx.x()-0.5, rx.y()-0.5, rx.width()+1, rx.height()+1); QPainterPath p(Utils::buildPath(r, 1.0)); QColor c(light ? Qt::white : Qt::black); painter->setRenderHint(QPainter::Antialiasing, true); c.setAlphaF(0.75); painter->fillPath(p, c); c.setAlphaF(0.95); painter->setPen(c); painter->drawPath(p); painter->setRenderHint(QPainter::Antialiasing, false); } ActionItemDelegate::ActionItemDelegate(QObject *p) : QStyledItemDelegate(p) , largeIcons(false) , underMouse(false) { } void ActionItemDelegate::drawIcons(QPainter *painter, const QRect &r, bool mouseOver, bool rtl, ActionPos actionPos, const QModelIndex &index) const { QColor textCol=QApplication::palette().color(QPalette::Normal, QPalette::WindowText); bool lightBgnd=textCol.red()<=128 && textCol.green()<=128 && textCol.blue()<=128; int iconSize=largeIcons ? constLargeActionIconSize : constActionIconSize; double opacity=painter->opacity(); bool adjustOpacity=!mouseOver; if (adjustOpacity) { painter->setOpacity(opacity*0.25); } QRect actionRect=calcActionRect(rtl, actionPos, r); QList actions=index.data(Cantata::Role_Actions).value >(); foreach (const QPointer &a, actions) { QPixmap pix=a->icon().pixmap(QSize(iconSize, iconSize)); QSize pixSize = pix.isNull() ? QSize(0, 0) : (pix.size() / pix.DEVICE_PIXEL_RATIO()); if (!pix.isNull() && actionRect.width()>=pixSize.width()/* && r.x()>=0 && r.y()>=0*/) { drawBgnd(painter, actionRect, lightBgnd); painter->drawPixmap(actionRect.x()+(actionRect.width()-pixSize.width())/2, actionRect.y()+(actionRect.height()-pixSize.height())/2, pix); } if (largeIcons && 2==actions.count() && AP_VTop==actionPos) { adjustActionRect(rtl, actionPos, actionRect, iconSize>>4); } adjustActionRect(rtl, actionPos, actionRect, iconSize); } if (adjustOpacity) { painter->setOpacity(opacity); } } bool ActionItemDelegate::helpEvent(QHelpEvent *e, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) { if (QEvent::ToolTip==e->type()) { QAction *act=getAction(index); if (act) { QToolTip::showText(e->globalPos(), act->toolTip(), view); return true; } } return QStyledItemDelegate::helpEvent(e, view, option, index); } QAction * ActionItemDelegate::getAction(const QModelIndex &index) const { QList actions=index.data(Cantata::Role_Actions).value >(); if (actions.isEmpty()) { return 0; } QAbstractItemView *view=(QAbstractItemView *)parent(); bool rtl = QApplication::isRightToLeft(); QListView *lv=qobject_cast(view); GroupedView *gv=lv ? 0 : qobject_cast(view); ActionPos actionPos=gv ? AP_HBottom : (lv && QListView::ListMode!=lv->viewMode() && (index.child(0, 0).isValid() || index.model()->canFetchMore(index)) ? AP_VTop : AP_HMiddle); QRect rect = view->visualRect(index); rect.moveTo(view->viewport()->mapToGlobal(QPoint(rect.x(), rect.y()))); bool showCapacity = !index.data(Cantata::Role_CapacityText).toString().isEmpty(); if (gv || lv || showCapacity) { if (AP_VTop==actionPos) { rect.adjust(ActionItemDelegate::constBorder, ActionItemDelegate::constBorder, -ActionItemDelegate::constBorder, -ActionItemDelegate::constBorder); } else { rect.adjust(ActionItemDelegate::constBorder+3, 0, -(ActionItemDelegate::constBorder+3), 0); } } if (showCapacity) { int textHeight=QFontMetrics(QApplication::font()).height(); rect.adjust(0, 0, 0, -(textHeight+8)); } int iconSize=largeIcons ? constLargeActionIconSize : constActionIconSize; QRect actionRect=calcActionRect(rtl, actionPos, rect); QRect actionRect2(actionRect); ActionItemDelegate::adjustActionRect(rtl, actionPos, actionRect2, iconSize); QPoint cursorPos=QCursor::pos(); foreach (const QPointer &a, actions) { actionRect=actionPos ? actionRect.adjusted(0, -2, 0, 2) : actionRect.adjusted(-2, 0, 2, 0); if (actionRect.contains(cursorPos)) { return a; } if (largeIcons && 2==actions.count() && AP_VTop==actionPos) { adjustActionRect(rtl, actionPos, actionRect, iconSize>>4); } ActionItemDelegate::adjustActionRect(rtl, actionPos, actionRect, iconSize); } return 0; } cantata-2.2.0/widgets/actionitemdelegate.h000066400000000000000000000040031316350454000205720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ACTIONITEMDELEGATE_H #define ACTIONITEMDELEGATE_H #include class QAction; class ActionItemDelegate : public QStyledItemDelegate { Q_OBJECT public: enum ActionPos { AP_HBottom, AP_VTop, AP_HMiddle }; static void setup(); ActionItemDelegate(QObject *p); virtual ~ActionItemDelegate() { } static int constBorder; static int constActionBorder; static int constActionIconSize; static int constLargeActionIconSize; void drawIcons(QPainter *painter, const QRect &r, bool mouseOver, bool rtl, ActionPos actionPos, const QModelIndex &index) const; void setUnderMouse(bool um) { underMouse=um; } void setLargeIcons(bool l) { largeIcons=l; } public Q_SLOTS: bool helpEvent(QHelpEvent *e, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index); public: QAction * getAction(const QModelIndex &index) const; private: QRect calcActionRect(bool rtl, ActionPos actionPos, const QRect &rect) const; static void adjustActionRect(bool rtl, ActionPos actionPos, QRect &rect, int iconSize); protected: bool largeIcons; bool underMouse; }; #endif cantata-2.2.0/widgets/actionlabel.cpp000066400000000000000000000060351316350454000175620ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "actionlabel.h" #include "icons.h" #include #include #include #include // Borrowed from kolourpaint... static QMatrix matrixWithZeroOrigin(const QMatrix &matrix, int width, int height) { QRect newRect(matrix.mapRect(QRect(0, 0, width, height))); return QMatrix(matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), matrix.dx() - newRect.left(), matrix.dy() - newRect.top()); } static QMatrix rotateMatrix(int width, int height, double angle) { QMatrix matrix; matrix.translate(width/2, height/2); matrix.rotate(angle); return matrixWithZeroOrigin(matrix, width, height); } static const int constNumIcons=8; static int theUsageCount; static QPixmap *theIcons[constNumIcons]; ActionLabel::ActionLabel(QWidget *parent) : QLabel(parent) { int iconSize=Icon::dlgIconSize(); int labelSize=Icon::stdSize((int)((iconSize*1.333333333)+0.5)); setMinimumSize(labelSize, labelSize); setMaximumSize(labelSize, labelSize); setAlignment(Qt::AlignCenter); if(0==theUsageCount++) { QImage img(Icons::self()->audioFileIcon.pixmap(iconSize, iconSize).toImage()); double increment=360.0/constNumIcons; for(int i=0; istart(2000/constNumIcons); } void ActionLabel::stopAnimation() { timer->stop(); count=0; setPixmap(*theIcons[count]); } void ActionLabel::rotateIcon() { if (++count==constNumIcons) { count=0; } setPixmap(*theIcons[count]); } cantata-2.2.0/widgets/actionlabel.h000066400000000000000000000023011316350454000172170ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ACTION_LABEL_H__ #define __ACTION_LABEL_H__ #include class QTimer; class QLabel; class ActionLabel : public QLabel { Q_OBJECT public: ActionLabel(QWidget *parent); ~ActionLabel(); void startAnimation(); void stopAnimation(); private Q_SLOTS: void rotateIcon(); protected: QTimer *timer; int count; }; #endif cantata-2.2.0/widgets/autohidingsplitter.cpp000066400000000000000000000365431316350454000212360ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * This file (c) 2012 Piotr Wicijowski * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "autohidingsplitter.h" #include "support/utils.h" #include #include #include #include #include #include #include #include static int splitterSize(const QWidget *w) { static int size=-1; if (-1==size || !w || !w->isVisible()) { #if defined Q_OS_MAC || defined Q_OS_WIN size=0; #else size=1; if (qApp->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { int spacing=qApp->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); if (spacing>0) { // Kvantum has -ve spacing! int splitterSize=qApp->style()->pixelMetric(QStyle::PM_SplitterWidth); size=qMin(spacing+2, splitterSize); } } #endif } return size; } QSize AutohidingSplitterHandle::sizeHint() const { int sz=splitterSize(this); return QSize(sz, sz); } class SplitterSizeAnimation:public QVariantAnimation { public: SplitterSizeAnimation(QObject *parent) : QVariantAnimation(parent) , splitter(0) { } void setSplitter(QSplitter *splitter) { this->splitter = splitter; } protected: virtual QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const { QList fromInt = from.value >(); QList toInt = to.value >(); QList returnValue; for (int i = 0; i < fromInt.count() ; ++i) { returnValue.append((int)((progress)*toInt.at(i) + (1-progress)*fromInt.at(i))); } return QVariant::fromValue(returnValue); } virtual void updateCurrentValue(const QVariant &value) { if (splitter) { splitter->setSizes(value.value >()); } } private: QSplitter *splitter; }; AutohidingSplitter::AutohidingSplitter(Qt::Orientation orientation, QWidget *parent) : QSplitter(orientation, parent) , autoHideEnabled(false) { haltModifications = false; autohideAnimation = new SplitterSizeAnimation(this); autohideAnimation->setSplitter(this); autohideAnimation->setDuration(100); autohideAnimation->setEasingCurve(QEasingCurve::Linear); //connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int))); setMinimumWidth(32); setHandleWidth(splitterSize(this)); } AutohidingSplitter::AutohidingSplitter(QWidget *parent) : QSplitter(parent) , autoHideEnabled(false) { haltModifications = false; autohideAnimation = new SplitterSizeAnimation(this); autohideAnimation->setSplitter(this); autohideAnimation->setDuration(100); autohideAnimation->setEasingCurve(QEasingCurve::Linear); //connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int))); setMinimumWidth(32); setHandleWidth(1); } AutohidingSplitter::~AutohidingSplitter() { foreach(QTimer *sat, animationDelayTimer) { sat->stop(); delete sat; } animationDelayTimer.clear(); autohideAnimation->stop(); delete autohideAnimation; // setSizes(expandedSizes); } QSplitterHandle * AutohidingSplitter::createHandle() { AutohidingSplitterHandle *sh = new AutohidingSplitterHandle(orientation(), this); connect(sh, SIGNAL(hoverStarted()), this, SLOT(handleHoverStarted())); connect(sh, SIGNAL(hoverFinished()), this, SLOT(handleHoverFinished())); return sh; } void AutohidingSplitter::addChild(QObject *pObject) { if (pObject && pObject->isWidgetType()) { pObject->installEventFilter(this); const QObjectList &childList = pObject->children(); foreach (QObject *obj, childList) { addChild(obj); } } QComboBox * combo = qobject_cast(pObject); if(combo){ popupsBlockingAutohiding.insert(combo->view()); // addChild(combo->view()); } } void AutohidingSplitter::removeChild(QObject *pObject) { if (pObject && (pObject->isWidgetType())) { pObject->removeEventFilter(this); const QObjectList& childList = pObject->children(); foreach (QObject *obj, childList) { removeChild(obj); } } } void AutohidingSplitter::childEvent(QChildEvent *e) { // if (!autoHideEnabled) { // QSplitter::childEvent(e); // return; // } if (e->child()->isWidgetType()) { if (QEvent::ChildAdded==e->type()) { addChild(e->child()); } else if (QEvent::ChildRemoved==e->type()) { removeChild(e->child()); } } QSplitter::childEvent(e); } bool AutohidingSplitter::eventFilter(QObject *target, QEvent *e) { if (!autoHideEnabled) { return QSplitter::eventFilter(target, e); } switch (e->type()) { case QEvent::ChildAdded: { QChildEvent *ce = (QChildEvent*)e; addChild(ce->child()); break; } case QEvent::ChildRemoved: { QChildEvent *ce = (QChildEvent*)e; removeChild(ce->child()); break; } default: break; } if (!autoHideEnabled) { return QSplitter::eventFilter(target, e); } switch (e->type()) { case QEvent::Enter: widgetHoverStarted(indexOf(qobject_cast(target))); if(popupsBlockingAutohiding.contains(qobject_cast(target))) { haltModifications = true; } break; case QEvent::Leave: widgetHoverFinished(indexOf(qobject_cast(target))); if(popupsBlockingAutohiding.contains(qobject_cast(target))) { haltModifications = false; } break; case QEvent::MouseButtonPress: if(qobject_cast(target)){ foreach(QTimer * timer, animationDelayTimer) { timer->stop(); } haltModifications = true; } break; case QEvent::MouseButtonRelease:{ if(qobject_cast(target)){ haltModifications = false; targetSizes.clear(); } break; } case QEvent::FocusIn:{ // QFocusEvent *ce = (QFocusEvent *)e; haltModifications = false; break; } case QEvent::FocusOut:{ // QFocusEvent *ce = (QFocusEvent *)e; haltModifications = true; break; } default: break; } return QSplitter::eventFilter(target, e); } void AutohidingSplitter::setAutoHideEnabled(bool ah) { if (ah==autoHideEnabled) { return; } autoHideEnabled=ah; if (autoHideEnabled) { expandedSizes = sizes(); connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int))); } else { for(int i = 0; i < widgetAutohidden.count() ; ++i) { widgetAutohidden[i]=false; } disconnect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int))); setSizes(expandedSizes); } } void AutohidingSplitter::setVisible(bool visible) { haltModifications=!visible; QSplitter::setVisible(visible); } void AutohidingSplitter::resizeEvent(QResizeEvent *event) { if (autoHideEnabled && !haltModifications && event->oldSize().width() > 0) { // int oldUsableSize = event->oldSize().width()/*-(count()-1)*handleWidth()*/; int oldUsableSize = 0; foreach(int size, expandedSizes) { oldUsableSize+=size; } int newUsableSize = event->size().width()/*-(count()-1)*handleWidth()*/; int leftToDistribute = newUsableSize-oldUsableSize; for (int i = 0; i < count()-1; ++ i) { expandedSizes[i]+=int(qreal(leftToDistribute)/(count()-i)); leftToDistribute-=int(qreal(leftToDistribute)/(count()-i)); } if (count()>0) { expandedSizes[count()-1]+=leftToDistribute; } } QSplitter::resizeEvent(event); } void AutohidingSplitter::addWidget(QWidget *widget) { QSplitter::addWidget(widget); expandedSizes.append(widget->size().width()); if (count()+1!=widgetAutohidable.count()) { QTimer *sat = new QTimer(this); sat->setSingleShot(true); sat->setInterval(500); connect(sat, SIGNAL(timeout()), this, SLOT(setWidgetForHiding())); animationDelayTimer.append(sat); widgetAutohidden.append(false); widgetAutohiddenPrev.append(false); widgetAutohidable.append(false); } } void AutohidingSplitter::setAutohidable(int index, bool autohidable) { widgetAutohidable[index]=autohidable; widgetAutohidden[index]=autohidable; // updateResizeQueue(); if (autoHideEnabled) { setSizes(getSizesAfterHiding()); } } bool AutohidingSplitter::restoreState(const QByteArray &state) { bool result = QSplitter::restoreState(state); expandedSizes = sizes(); return result; } QByteArray AutohidingSplitter::saveState() const { if (!autoHideEnabled) { return QSplitter::saveState(); } AutohidingSplitter *tmpSplitter = new AutohidingSplitter(/*qobject_cast(parent())*/); for (int i = 0; i < count(); ++ i) { QWidget *widget = new QWidget(/*tmpSplitter*/); tmpSplitter->addWidget(widget); } tmpSplitter->setSizes(expandedSizes); QByteArray result = tmpSplitter->QSplitter::saveState(); delete tmpSplitter; return result; } void AutohidingSplitter::widgetHoverStarted(int index) { if (!autoHideEnabled || index<0 || index > count()) { return; } if(!haltModifications){ if (animationDelayTimer.at(index)->isActive()) { animationDelayTimer.at(index)->stop(); } if (widgetAutohidden.at(index) && widgetAutohidable.at(index)) { widgetAutohiddenPrev[index] = widgetAutohidden[index]; widgetAutohidden[index] = false; updateResizeQueue(); } } } void AutohidingSplitter::widgetHoverFinished(int index) { if (!autoHideEnabled || index<0 || index > count()) { return; } if (!widgetAutohidden.at(index) && widgetAutohidable.at(index)) { animationDelayTimer.at(index)->start(); } } void AutohidingSplitter::handleHoverStarted() { if (!autoHideEnabled) { return; } if(!haltModifications){ int index = indexOf(qobject_cast(QObject::sender())); if (animationDelayTimer.at(index)->isActive()) { animationDelayTimer.at(index)->stop(); } if (widgetAutohidden.at(index) && widgetAutohidable.at(index)) { widgetAutohiddenPrev[index] = widgetAutohidden[index]; widgetAutohidden[index] = false; updateResizeQueue(); } if (index > 0 && animationDelayTimer.at(index-1)->isActive()) { animationDelayTimer.at(index-1)->stop(); } if (index > 0 && widgetAutohidden.at(index-1) && widgetAutohidable.at(index-1)) { widgetAutohiddenPrev[index-1] = widgetAutohidden[index-1]; widgetAutohidden[index-1] = false; updateResizeQueue(); } } } void AutohidingSplitter::handleHoverFinished() { if (!autoHideEnabled) { return; } if(!haltModifications){ int index = indexOf(qobject_cast(QObject::sender())); if (!widgetAutohidden.at(index) && widgetAutohidable.at(index)) { animationDelayTimer.at(index)->start(); } if (index>0 && !widgetAutohidden.at(index-1) && widgetAutohidable.at(index-1)) { animationDelayTimer.at(index-1)->start(); } } } void AutohidingSplitter::updateResizeQueue() { if (!autoHideEnabled) { return; } targetSizes.enqueue(getSizesAfterHiding()); if (autohideAnimation->state()==QAbstractAnimation::Stopped) { startAnimation(); } } QList AutohidingSplitter::getSizesAfterHiding() const { int toDistribute = 0; int numberOfExpanded = 0; QList result; result = sizes(); for (int i = 0 ; i nextSizes = targetSizes.dequeue(); autohideAnimation->setStartValue(QVariant::fromValue(sizes())); autohideAnimation->setCurrentTime(0); autohideAnimation->setEndValue(QVariant::fromValue(nextSizes)); connect(autohideAnimation,SIGNAL(finished()),this,SLOT(startAnimation())); autohideAnimation->start(); } } void AutohidingSplitter::setWidgetForHiding() { int index = animationDelayTimer.indexOf(qobject_cast(QObject::sender())); if(!haltModifications){ if (!widgetAutohidden.at(index)) { widgetAutohiddenPrev[index] = widgetAutohidden[index]; widgetAutohidden[index] = true; updateResizeQueue(); } } } void AutohidingSplitter::updateAfterSplitterMoved(int pos, int index) { Q_UNUSED(pos) if (!autoHideEnabled || index<=0 || index>count()) { return; } QList currentTemporarySizes = sizes(); QList previousTemporarySizes = getSizesAfterHiding(); expandedSizes[index-1]+=currentTemporarySizes.at(index-1)-previousTemporarySizes.at(index-1); expandedSizes[index]+=currentTemporarySizes.at(index)-previousTemporarySizes.at(index); } cantata-2.2.0/widgets/autohidingsplitter.h000066400000000000000000000065251316350454000207000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * This file (c) 2012 Piotr Wicijowski * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef AUTOHIDINGSPLITTER_H #define AUTOHIDINGSPLITTER_H #include "config.h" #include "thinsplitterhandle.h" #include #include #include #include #include Q_DECLARE_METATYPE(QList) class SplitterSizeAnimation; class AutohidingSplitterHandle : public ThinSplitterHandle { Q_OBJECT public: AutohidingSplitterHandle(Qt::Orientation orientation, QSplitter *parent) : ThinSplitterHandle(orientation, parent) { } virtual ~AutohidingSplitterHandle() { } QSize sizeHint() const; Q_SIGNALS: void hoverStarted(); void hoverFinished(); protected: virtual void enterEvent(QEvent *) { emit hoverStarted(); } virtual void leaveEvent(QEvent *) { emit hoverFinished(); } }; class AutohidingSplitter : public QSplitter { Q_OBJECT public: explicit AutohidingSplitter(Qt::Orientation orientation, QWidget *parent=0); explicit AutohidingSplitter(QWidget *parent=0); virtual ~AutohidingSplitter(); void setAutohidable(int index, bool autohidable = true); void addWidget(QWidget *widget); bool restoreState( const QByteArray &state); QByteArray saveState() const; bool eventFilter(QObject *watched, QEvent *event); bool isAutoHideEnabled() const { return autoHideEnabled; } public Q_SLOTS: void setAutoHideEnabled(bool en); void setVisible(bool visible); protected: virtual QSplitterHandle * createHandle(); void childEvent(QChildEvent *); void removeChild(QObject* pObject); void addChild(QObject *pObject); void resizeEvent(QResizeEvent *); private Q_SLOTS: void widgetHoverStarted(int index); void widgetHoverFinished(int index); void handleHoverStarted(); void handleHoverFinished(); void updateResizeQueue(); void setWidgetForHiding(); void startAnimation(); void updateAfterSplitterMoved(int pos, int index); void inhibitModifications(){haltModifications = true;} void resumeModifications(){haltModifications = false;} private: bool autoHideEnabled; bool haltModifications; QList getSizesAfterHiding()const; SplitterSizeAnimation *autohideAnimation; QList animationDelayTimer; QList widgetAutohidden; QList widgetAutohiddenPrev; QList widgetAutohidable; QList expandedSizes; QQueue > targetSizes; QSet popupsBlockingAutohiding; friend class AutohidingSplitterHandle; }; #endif cantata-2.2.0/widgets/basicitemdelegate.cpp000066400000000000000000000112061316350454000207340ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "basicitemdelegate.h" #include "support/gtkstyle.h" #include "support/utils.h" #include #include #include #include #include #include void BasicItemDelegate::drawLine(QPainter *p, const QRect &r, const QColor &color, bool fadeStart, bool fadeEnd, double alpha) { QColor col(color); QLinearGradient grad(r.bottomLeft(), r.bottomRight()); if (fadeStart || fadeEnd) { double fadeSize=(fadeStart && fadeEnd ? 64.0 : 32.0); if (r.width()<(2.2*fadeSize)) { fadeSize=r.width()/3.0; } double fadePos=fadeSize/r.width(); col.setAlphaF(fadeStart ? 0.0 : alpha); grad.setColorAt(0, col); col.setAlphaF(alpha); grad.setColorAt(fadePos, col); grad.setColorAt(1.0-fadePos, col); col.setAlphaF(fadeEnd ? 0.0 : alpha); grad.setColorAt(1, col); p->setPen(QPen(grad, 1)); } else { col.setAlphaF(alpha); p->setPen(QPen(col, 1)); } p->drawLine(r.bottomLeft(), r.bottomRight()); } BasicItemDelegate::BasicItemDelegate(QObject *p) : QStyledItemDelegate(p) , trackMouse(false) , underMouse(false) { if (GtkStyle::isActive() && qobject_cast(p)) { static_cast(p)->setAttribute(Qt::WA_MouseTracking); trackMouse=true; p->installEventFilter(this); } } BasicItemDelegate::~BasicItemDelegate() { } void BasicItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } bool selected=option.state&QStyle::State_Selected; bool active=option.state&QStyle::State_Active; if (GtkStyle::isActive()) { bool mouseOver=option.state&QStyle::State_MouseOver; QStyleOptionViewItem opt = option; initStyleOption(&opt, index); if (trackMouse && !underMouse) { mouseOver=false; } if (mouseOver) { opt.showDecorationSelected=true; GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); opt.showDecorationSelected=false; opt.state&=~(QStyle::State_MouseOver|QStyle::State_Selected); opt.backgroundBrush=QBrush(Qt::transparent); if (selected) { opt.palette.setBrush(QPalette::Text, opt.palette.highlightedText()); } } QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); } else { QStyledItemDelegate::paint(painter, option, index); } QColor col(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); switch (((QStyleOptionViewItem &)option).viewItemPosition) { case QStyleOptionViewItem::Beginning: drawLine(painter, option.rect, col, true, false); break; case QStyleOptionViewItem::Middle: drawLine(painter, option.rect, col, false, false); break; case QStyleOptionViewItem::End: drawLine(painter, option.rect, col, false, true); break; case QStyleOptionViewItem::Invalid: case QStyleOptionViewItem::OnlyOne: drawLine(painter, option.rect, col, true, true); } } bool BasicItemDelegate::eventFilter(QObject *object, QEvent *event) { if (object==parent()) { if (QEvent::Enter==event->type()) { underMouse=true; static_cast(parent())->viewport()->update(); } else if (QEvent::Leave==event->type()) { underMouse=false; static_cast(parent())->viewport()->update(); } } return QStyledItemDelegate::eventFilter(object, event); } cantata-2.2.0/widgets/basicitemdelegate.h000066400000000000000000000026401316350454000204030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BASIC_ITEM_DELEGATE_H #define BASIC_ITEM_DELEGATE_H #include class BasicItemDelegate : public QStyledItemDelegate { public: static void drawLine(QPainter *p, const QRect &r, const QColor &color, bool fadeStart=true, bool fadeEnd=true, double alpha=0.1); BasicItemDelegate(QObject *p); virtual ~BasicItemDelegate(); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; private: bool eventFilter(QObject *object, QEvent *event); protected: bool trackMouse; bool underMouse; }; #endif cantata-2.2.0/widgets/completioncombo.h000066400000000000000000000033001316350454000201330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COMPLETION_COMBO_H #define COMPLETION_COMBO_H #include "support/combobox.h" #include "support/lineedit.h" #include class CompletionCombo : public ComboBox { public: CompletionCombo(QWidget *p) : ComboBox(p) { setEditable(true); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); view()->setTextElideMode(Qt::ElideRight); } void setText(const QString &text) { if (lineEdit()) qobject_cast(lineEdit())->setText(text); } QString text() const { return lineEdit() ? qobject_cast(lineEdit())->text() : QString(); } void setPlaceholderText(const QString &text) { if (lineEdit()) qobject_cast(lineEdit())->setPlaceholderText(text); } }; #endif cantata-2.2.0/widgets/coverwidget.cpp000066400000000000000000000142701316350454000176270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "coverwidget.h" #include "gui/currentcover.h" #include "gui/covers.h" #include "gui/settings.h" #include "mpd-interface/song.h" #include "context/view.h" #include "support/gtkstyle.h" #include "support/utils.h" #include "online/onlineservice.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include #include #include #include #include #include #include static const int constBorder=1; CoverLabel::CoverLabel(QWidget *p) : QLabel(p) , pressed(false) { } bool CoverLabel::event(QEvent *event) { switch(event->type()) { case QEvent::ToolTip: { const Song ¤t=CurrentCover::self()->song(); if (current.isEmpty() || (current.isStream() && !current.isCantataStream() && !current.isCdda()) || OnlineService::showLogoAsCover(current)) { setToolTip(QString()); break; } QString toolTip=QLatin1String(""); const QImage &img=CurrentCover::self()->image(); if (!current.composer().isEmpty()) { toolTip+=tr("").arg(current.composer()); } if (!current.performer().isEmpty() && current.performer()!=current.albumArtist()) { toolTip+=tr("").arg(current.performer()); } toolTip+=tr("" "" "").arg(current.albumArtist()).arg(current.album).arg(QString::number(current.year)); toolTip+="
    Composer:%1
    Performer:%1
    Artist:%1
    Album:%2
    Year:%3
    "; if (!img.isNull()) { if (img.size().width()>Covers::constMaxSize.width() || img.size().height()>Covers::constMaxSize.height()) { toolTip+=QString("
    %1").arg(View::encode(img.scaled(Covers::constMaxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation))); } else if (CurrentCover::self()->fileName().isEmpty() || !QFile::exists(CurrentCover::self()->fileName())) { toolTip+=QString("
    %1").arg(View::encode(img)); } else { toolTip+=QString("
    ").arg(CurrentCover::self()->fileName()); } } setToolTip(toolTip); break; } case QEvent::MouseButtonPress: if (Qt::LeftButton==static_cast(event)->button() && Qt::NoModifier==static_cast(event)->modifiers()) { pressed=true; } break; case QEvent::MouseButtonRelease: if (pressed && Qt::LeftButton==static_cast(event)->button() && !QApplication::overrideCursor()) { static_cast(parentWidget())->emitClicked(); } pressed=false; break; default: break; } return QLabel::event(event); } void CoverLabel::paintEvent(QPaintEvent *) { if (pix.isNull()) { return; } QPainter p(this); QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO(); QRect r((width()-layoutSize.width())/2, (height()-layoutSize.height())/2, layoutSize.width(), layoutSize.height()); p.drawPixmap(r, pix); if (underMouse()) { #ifdef Q_OS_MAC QPen pen(OSXStyle::self()->viewPalette().color(QPalette::Highlight), 2); #else QPen pen(palette().color(QPalette::Highlight), 2); #endif pen.setJoinStyle(Qt::MiterJoin); p.setPen(pen); p.drawRect(r.adjusted(1, 1, -1, -1)); } } void CoverLabel::updatePix() { QImage img=CurrentCover::self()->image(); if (img.isNull()) { return; } int size=height(); double pixRatio=qApp->devicePixelRatio(); size*=pixRatio; img=img.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); img.setDevicePixelRatio(pixRatio); if (pix.isNull() || pix.size()!=img.size()) { pix=QPixmap(img.size()); pix.setDevicePixelRatio(pixRatio); } pix.fill(Qt::transparent); QPainter painter(&pix); painter.drawImage(0, 0, img); repaint(); } void CoverLabel::deletePix() { if (!pix.isNull()) { pix=QPixmap(); } } CoverWidget::CoverWidget(QWidget *parent) : QWidget(parent) { QBoxLayout *l=new QBoxLayout(QBoxLayout::LeftToRight, this); l->setMargin(0); l->setSpacing(0); l->addItem(new QSpacerItem(qMax(Utils::scaleForDpi(8), Utils::layoutSpacing(this)), 4, QSizePolicy::Fixed, QSizePolicy::Fixed)); label=new CoverLabel(this); l->addWidget(label); label->setStyleSheet(QString("QLabel {border: %1px solid transparent} QToolTip {background-color:#111111; color: #DDDDDD}").arg(constBorder)); label->setAttribute(Qt::WA_Hover, true); } CoverWidget::~CoverWidget() { } void CoverWidget::setSize(int min) { label->setFixedSize(min, min); } void CoverWidget::setEnabled(bool e) { if (e) { connect(CurrentCover::self(), SIGNAL(coverImage(QImage)), this, SLOT(coverImage(QImage))); coverImage(QImage()); } else { label->deletePix(); disconnect(CurrentCover::self(), SIGNAL(coverImage(QImage)), this, SLOT(coverImage(QImage))); } setVisible(e); label->setEnabled(e); } void CoverWidget::coverImage(const QImage &) { label->updatePix(); } cantata-2.2.0/widgets/coverwidget.h000066400000000000000000000027671316350454000173040ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COVERWIDGET_H #define COVERWIDGET_H #include #include class CoverLabel : public QLabel { Q_OBJECT public: CoverLabel(QWidget *p); bool event(QEvent *event); void paintEvent(QPaintEvent *); void updatePix(); void deletePix(); private: bool pressed; QPixmap pix; }; class CoverWidget : public QWidget { Q_OBJECT public: CoverWidget(QWidget *p); virtual ~CoverWidget(); void setSize(int min); void setEnabled(bool e); void emitClicked() { emit clicked(); } Q_SIGNALS: void clicked(); private Q_SLOTS: void coverImage(const QImage &); private: CoverLabel *label; }; #endif cantata-2.2.0/widgets/emptyspinbox.h000066400000000000000000000032451316350454000175130ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef EMPTYSPINBOX_H #define EMPTYSPINBOX_H #include #include class EmptySpinBox : public QSpinBox { public: EmptySpinBox(QWidget *parent) : QSpinBox(parent) { setKeyboardTracking(true); setMaximum(3000); } QSize sizeHint() const { return QSpinBox::sizeHint()+QSize(fontMetrics().height()/2, 0); } protected: virtual QValidator::State validate(QString &input, int &pos) const { return input.isEmpty() ? QValidator::Acceptable : QSpinBox::validate(input, pos); } virtual int valueFromText(const QString &text) const { return text.isEmpty() ? minimum() : QSpinBox::valueFromText(text); } virtual QString textFromValue(int val) const { return val==minimum() ? QString() : QSpinBox::textFromValue(val); } }; #endif cantata-2.2.0/widgets/genrecombo.cpp000066400000000000000000000064461316350454000174330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "genrecombo.h" #include "toolbutton.h" #include "support/actioncollection.h" #include "support/action.h" #include static Action *action=0; GenreCombo::GenreCombo(QWidget *p) : ComboBox(p) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); update(QSet()); setEditable(false); setFocusPolicy(Qt::NoFocus); if (!action) { action=ActionCollection::get()->createAction("genrefilter", tr("Filter On Genre"), 0); action->setShortcut(Qt::ControlModifier+Qt::Key_G); } addAction(action); connect(action, SIGNAL(triggered()), SLOT(showEntries())); } void GenreCombo::update(const QSet &g) { if (count() && g==genres) { return; } QSet mg=g; mg.remove(QString()); if (mg.count()!=g.count() && count() && mg==genres) { return; } genres=mg; QStringList entries=g.toList(); qSort(entries); entries.prepend(tr("All Genres")); if (count()==entries.count()) { bool noChange=true; for (int i=0; i1); // If we are 'hidden' then we need to ingore mouse events - so that these get passed to parent widget. // The Oxygen's window drag still functions... setAttribute(Qt::WA_TransparentForMouseEvents, count()<2); } void GenreCombo::showEntries() { if (isVisible()) { showPopup(); } } void GenreCombo::paintEvent(QPaintEvent *e) { if (count()>1) { ComboBox::paintEvent(e); } else { QWidget::paintEvent(e); } } bool GenreCombo::event(QEvent *event) { if (QEvent::ToolTip==event->type() && toolTip()!=action->toolTip()) { setToolTip(action->toolTip()); } return ComboBox::event(event); } cantata-2.2.0/widgets/genrecombo.h000066400000000000000000000024671316350454000170770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef GENRECOMBO_H #define GENRECOMBO_H #include "support/combobox.h" #include class GenreCombo : public ComboBox { Q_OBJECT public: GenreCombo(QWidget *p); virtual ~GenreCombo() { } const QSet & entries() const { return genres; } void paintEvent(QPaintEvent *e); bool event(QEvent *event); public Q_SLOTS: void update(const QSet &g); private Q_SLOTS: void showEntries(); private: QSet genres; }; #endif cantata-2.2.0/widgets/groupedview.cpp000066400000000000000000000773111316350454000176520ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gui/covers.h" #include "groupedview.h" #include "mpd-interface/mpdstatus.h" #include "mpd-interface/song.h" #include "basicitemdelegate.h" #include "itemview.h" #include "config.h" #include "widgets/icons.h" #include "widgets/ratingwidget.h" #include "support/gtkstyle.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include "support/utils.h" #include "models/roles.h" #include #include #include #include #include #include #include #include static int constCoverSize=32; static int constIconSize=16; static int constBorder=1; static double sizeAdjust=1.25; void GroupedView::setup() { int height=QApplication::fontMetrics().height(); sizeAdjust=1.25; if (height>17) { constCoverSize=(((int)((height*2)/4))*4); constIconSize=Icon::stdSize(((int)(height/4))*4); constBorder=constCoverSize>48 ? 2 : 1; } else { constCoverSize=32; constIconSize=16; constBorder=1; } constCoverSize*=sizeAdjust; } int GroupedView::coverSize() { return constCoverSize; } int GroupedView::borderSize() { return constBorder; } int GroupedView::iconSize() { return constIconSize; } void GroupedView::drawPlayState(QPainter *painter, const QStyleOptionViewItem &option, const QRect &r, int state) { if (state) { int size=constIconSize-7; int hSize=size/2; QRect ir(r.x()-(size+6), r.y()+(((r.height()-size)/2.0)+0.5), size, size); QColor inside(option.palette.color(QPalette::Text)); QColor border=inside.red()>100 && inside.blue()>100 && inside.green()>100 ? Qt::black : Qt::white; if (QApplication::isRightToLeft()) { ir.adjust(r.width()-size, 0, r.width()-size, 0); } switch (state) { case GroupedView::State_Stopped: painter->fillRect(ir, border); painter->fillRect(ir.adjusted(1, 1, -1, -1), inside); break; case GroupedView::State_StopAfter: { ir.adjust(2, 0, -2, 0); QPoint p1[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-hSize), ir.y()+hSize), QPoint(ir.x(), ir.y()+(ir.height())), QPoint(ir.x()-2, ir.y()+(ir.height())) }; QPoint p2[5]={ QPoint(ir.x()-1, ir.y()), QPoint(ir.x(), ir.y()), QPoint(ir.x()+(size-hSize)-1, ir.y()+hSize), QPoint(ir.x(), ir.y()+ir.height()-1), QPoint(ir.x()-1, ir.y()+ir.height()-1) }; QPoint p3[3]={ QPoint(ir.x(), ir.y()+1), QPoint(ir.x()+(size-hSize)-2, ir.y()+hSize), QPoint(ir.x(), ir.y()+ir.height()-2) }; painter->save(); painter->setPen(border); painter->drawPolygon(p1, 5); painter->drawPolygon(p3, 3); painter->setPen(inside); painter->drawPolygon(p2, 5); painter->restore(); break; } case GroupedView::State_StopAfterTrack: painter->setPen(border); painter->drawRect(ir.adjusted(0, 0, -1, -1)); painter->drawRect(ir.adjusted(2, 2, -3, -3)); painter->setPen(inside); painter->drawRect(ir.adjusted(1, 1, -2, -2)); break; case GroupedView::State_Paused: { int blockSize=hSize-1; painter->fillRect(ir, border); painter->fillRect(ir.x()+1, ir.y()+1, blockSize, size-2, inside); painter->fillRect(ir.x()+size-blockSize-1, ir.y()+1, blockSize, size-2, inside); break; } case GroupedView::State_Playing: { ir.adjust(2, 0, -2, 0); QPoint p1[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-hSize), ir.y()+hSize), QPoint(ir.x(), ir.y()+(ir.height()-1)), QPoint(ir.x()-2, ir.y()+(ir.height()-1)) }; QPoint p2[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-hSize), ir.y()+hSize), QPoint(ir.x(), ir.y()+ir.height()), QPoint(ir.x()-2, ir.y()+ir.height()) }; painter->save(); painter->setBrush(border); painter->setPen(border); painter->drawPolygon(p1, 5); painter->setBrush(inside); painter->drawPolygon(p2, 5); painter->restore(); break; } } } } enum Type { AlbumHeader, AlbumTrack }; static Type getType(const QModelIndex &index) { QModelIndex prev=index.row()>0 ? index.sibling(index.row()-1, 0) : QModelIndex(); quint16 thisKey=index.data(Cantata::Role_Key).toUInt(); quint16 prevKey=prev.isValid() ? prev.data(Cantata::Role_Key).toUInt() : (quint16)Song::Null_Key; return thisKey==prevKey ? AlbumTrack : AlbumHeader; } static bool isAlbumHeader(const QModelIndex &index) { return !index.data(Cantata::Role_IsCollection).toBool() && AlbumHeader==getType(index); } static QString streamText(const Song &song, const QString &trackTitle, bool useName=true) { if (song.album.isEmpty() && song.albumArtist().isEmpty()) { QString songName=song.name(); return song.title.isEmpty() && songName.isEmpty() ? song.file : !useName || songName.isEmpty() ? song.title : song.title.isEmpty() ? songName : (song.title + QString(" – ") + songName); } else if (!song.title.isEmpty() && !song.artist.isEmpty()) { QString name=song.name(); return song.artist + QString(" – ") + (!useName || name.isEmpty() ? song.title : song.title.isEmpty() ? name : (song.title + QString(" – ") + name)); } else { return trackTitle; } } GroupedViewDelegate::GroupedViewDelegate(GroupedView *p) : ActionItemDelegate(p) , view(p) , ratingPainter(0) { } GroupedViewDelegate::~GroupedViewDelegate() { delete ratingPainter; } QSize GroupedViewDelegate::sizeHint(int type, bool isCollection) const { int textHeight = QApplication::fontMetrics().height()*sizeAdjust; if (isCollection || AlbumHeader==type) { return QSize(64, qMax(constCoverSize, (qMax(constIconSize, textHeight)*2)+constBorder)+(2*constBorder)); } return QSize(64, qMax(constIconSize, textHeight)+(2*constBorder)); } QSize GroupedViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (0==index.column()) { return sizeHint(getType(index), index.data(Cantata::Role_IsCollection).toBool()); } return QStyledItemDelegate::sizeHint(option, index); } void GroupedViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } Type type=getType(index); bool isCollection=index.data(Cantata::Role_IsCollection).toBool(); Song song=index.data(Cantata::Role_SongWithRating).value(); int state=index.data(Cantata::Role_Status).toInt(); quint32 collection=index.data(Cantata::Role_CollectionId).toUInt(); bool selected=option.state&QStyle::State_Selected; bool mouseOver=underMouse && option.state&QStyle::State_MouseOver; bool gtk=mouseOver && GtkStyle::isActive(); bool rtl=QApplication::isRightToLeft(); if (!isCollection && AlbumHeader==type) { if (mouseOver && gtk) { GtkStyle::drawSelection(option, painter, (selected ? 0.75 : 0.25)*0.75); } else { painter->save(); painter->setOpacity(0.75); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, view); painter->restore(); } painter->save(); painter->setClipRect(option.rect.adjusted(0, option.rect.height()/2, 0, 0), Qt::IntersectClip); if (mouseOver && gtk) { GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, view); } painter->restore(); if (!state && !view->isExpanded(song.key, collection) && view->isCurrentAlbum(song.key)) { QVariant cs=index.data(Cantata::Role_CurrentStatus); if (cs.isValid()) { state=cs.toInt(); } } } else { if (mouseOver && gtk) { GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, view); } } QString title; QString track; QString duration=song.time>0 ? Utils::formatTime(song.time) : QString(); bool stream=!isCollection && song.isStandardStream(); bool isEmpty=song.title.isEmpty() && song.artist.isEmpty() && !song.file.isEmpty(); QString trackTitle=isEmpty ? song.file : song.diffArtist() ? song.artistSong() : song.title; QFont f(QApplication::font()); if (stream) { f.setItalic(true); } QFontMetrics fm(f); int textHeight=fm.height()*sizeAdjust; if (isCollection) { title=index.data(Qt::DisplayRole).toString(); } else if (AlbumHeader==type) { if (stream) { QModelIndex next=index.sibling(index.row()+1, 0); quint16 nextKey=next.isValid() ? next.data(Cantata::Role_Key).toUInt() : (quint16)Song::Null_Key; if (nextKey!=song.key && !song.name().isEmpty()) { title=song.name(); track=streamText(song, trackTitle, false); } else { title=song.isCdda() ? tr("Audio CD") : tr("Streams"); track=streamText(song, trackTitle); } } else if (isEmpty) { title=Song::unknown(); track=trackTitle; } else if (song.album.isEmpty()) { title=song.artistOrComposer(); track=song.trackAndTitleStr(); } else { if (song.isFromOnlineService()) { title=Song::displayAlbum(song.albumName(), Song::albumYear(song)); } else { title=song.artistOrComposer()+QString(" – ")+Song::displayAlbum(song.albumName(), Song::albumYear(song)); } track=song.trackAndTitleStr(); } } else { if (stream) { track=streamText(song, trackTitle); } else { track=song.trackAndTitleStr(); } } if (song.priority>0) { track=track+QLatin1String(" [")+QString::number(song.priority)+QChar(']'); } painter->save(); painter->setFont(f); #ifdef Q_OS_WIN QColor textColor(option.palette.color(QPalette::Text)); #else QColor textColor(option.palette.color(option.state&QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); #endif QTextOption textOpt(Qt::AlignVCenter); QRect r(option.rect.adjusted(constBorder+4, constBorder, -(constBorder+4), -constBorder)); if (state && GroupedView::State_StopAfterTrack!=state) { QRectF border(option.rect.x()+1, option.rect.y()+1, option.rect.width()-3, option.rect.height()-3); if (!title.isEmpty()) { border.adjust(0, (border.height()/2)+1, 0, 0); } #ifdef Q_OS_MAC QColor gradCol(OSXStyle::self()->viewPalette().color(QPalette::Highlight)); QColor borderCol(OSXStyle::self()->viewPalette().color(selected ? QPalette::HighlightedText : QPalette::Highlight)); #else QColor gradCol(QApplication::palette().color(QPalette::Highlight)); QColor borderCol(QApplication::palette().color(selected ? QPalette::HighlightedText : QPalette::Highlight)); #endif if (!selected) { borderCol.setAlphaF(0.5); } gradCol.setAlphaF(selected ? 0.4 : 0.25); painter->fillRect(border, gradCol); painter->setPen(QPen(borderCol, 1)); painter->drawRect(border); } painter->setPen(textColor); bool showTrackDuration=!duration.isEmpty(); if (isCollection || AlbumHeader==type) { QPixmap pix; // Draw cover... if (isCollection) { pix=index.data(Qt::DecorationRole).value().pixmap(constCoverSize, constCoverSize, selected && textColor==qApp->palette().color(QPalette::HighlightedText) ? QIcon::Selected : QIcon::Normal); } else { QPixmap *cover=/*stream ? 0 : */Covers::self()->get(song, constCoverSize); pix=cover ? *cover : (stream && !song.isCdda() ? Icons::self()->streamIcon : Icons::self()->albumIcon(constCoverSize)).pixmap(constCoverSize, constCoverSize); } int maxSize=constCoverSize*pix.DEVICE_PIXEL_RATIO(); if (pix.width()>maxSize) { pix=pix.scaled(maxSize, maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QSize pixSize = pix.isNull() ? QSize(0, 0) : (pix.size() / pix.DEVICE_PIXEL_RATIO()); if (rtl) { painter->drawPixmap(r.x()+r.width()-(pixSize.width()-constBorder), r.y()+((r.height()-pixSize.height())/2), pixSize.width(), pixSize.height(), pix); r.adjust(0, 0, -(constCoverSize+constBorder), 0); } else { painter->drawPixmap(r.x()-2, r.y()+((r.height()-pixSize.height())/2), pixSize.width(), pixSize.height(), pix); r.adjust(constCoverSize+constBorder, 0, 0, 0); } int td=index.data(Cantata::Role_AlbumDuration).toUInt(); QString totalDuration=td>0 ? Utils::formatTime(td) : QString(); QRect duratioRect(r.x(), r.y(), r.width(), textHeight); int totalDurationWidth=fm.width(totalDuration)+8; QRect textRect(r.x(), r.y(), r.width()-(rtl ? (4*constBorder) : totalDurationWidth), textHeight); QFont tf(f); tf.setBold(true); title = QFontMetrics(tf).elidedText(title, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->setFont(tf); painter->drawText(textRect, title, textOpt); if (!totalDuration.isEmpty()) { painter->drawText(duratioRect, totalDuration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); } BasicItemDelegate::drawLine(painter, r.adjusted(0, 0, 0, -r.height()/2), textColor, rtl, !rtl, 0.45); r.adjust(0, textHeight+constBorder, 0, 0); r=QRect(r.x(), r.y()+r.height()-(textHeight+1), r.width(), textHeight); painter->setFont(f); if (rtl) { r.adjust(0, 0, (constCoverSize-(constBorder*3)), 0); } if (isCollection || !view->isExpanded(song.key, collection)) { showTrackDuration=false; track=tr("%n Track(s)", "", index.data(Cantata::Role_SongCount).toUInt()); } } else if (rtl) { r.adjust(0, 0, -(constBorder*4), 0); } else { r.adjust(constCoverSize+constBorder, 0, 0, 0); } GroupedView::drawPlayState(painter, option, r, state); painter->setPen(textColor); int ratingsStart=rtl ? 0 : drawRatings(painter, song, r, fm, option.palette.color(QPalette::Active, QPalette::Text)); // TODO!!! int durationWidth=showTrackDuration ? fm.width(duration)+8 : 0; QRect duratioRect(r.x(), r.y(), r.width(), textHeight); QRect textRect(r.x(), r.y(), r.width()-durationWidth, textHeight); if (ratingsStart>0) { textRect.setWidth(ratingsStart-r.x()); } track = fm.elidedText(track, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->drawText(textRect, track, textOpt); if (showTrackDuration) { painter->drawText(duratioRect, duration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); } if (mouseOver) { drawIcons(painter, option.rect, mouseOver, rtl, AlbumHeader==type || isCollection ? AP_HBottom : AP_HMiddle, index); } BasicItemDelegate::drawLine(painter, option.rect, textColor); painter->restore(); } int GroupedViewDelegate::drawRatings(QPainter *painter, const Song &song, const QRect &r, const QFontMetrics &fm, const QColor &col) const { if (song.rating>0 && song.rating<=Song::Rating_Max) { if (!ratingPainter) { ratingPainter=new RatingPainter(r.height()-(constBorder*8)); ratingPainter->setColor(col); } painter->save(); painter->setOpacity(painter->opacity()*0.75); const QSize &ratingSize=ratingPainter->size(); int spacing=constBorder*2; int durationWidth=fm.width("0:00:00")+spacing; QRect ratingRect(r.x()+r.width()-(durationWidth+ratingSize.width()+spacing), r.y()+(r.height()-ratingSize.height())/2, ratingSize.width(), ratingSize.height()); ratingPainter->paint(painter, ratingRect, song.rating); painter->restore(); return ratingRect.x()-spacing; } return 0; } GroupedView::GroupedView(QWidget *parent, bool isPlayQueue) : TreeView(parent, isPlayQueue) , allowClose(true) , startClosed(allowClose) , autoExpand(true) , filterActive(false) , isMultiLevel(false) , currentAlbum(Song::Null_Key) { setContextMenuPolicy(Qt::CustomContextMenu); setAcceptDrops(true); setDragDropOverwriteMode(false); setDragDropMode(QAbstractItemView::DragDrop); setSelectionMode(QAbstractItemView::ExtendedSelection); setDropIndicatorShown(true); setHeaderHidden(true); setRootIsDecorated(false); setSelectionBehavior(SelectRows); setForceSingleColumn(true); connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); setStyleSheet("QTreeView::branch { border: 0px; }"); GroupedViewDelegate *delegate=new GroupedViewDelegate(this); setItemDelegate(delegate); if (isPlayQueue) { // 'underMouse' is used to work-around mouse over issues with some styles. // PlayQueue does not have mouse over - so we never need to check this. delegate->setUnderMouse(true); } } GroupedView::~GroupedView() { } void GroupedView::setModel(QAbstractItemModel *model) { TreeView::setModel(model); if (model) { if (startClosed) { updateCollectionRows(); } connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); } else { controlledAlbums.clear(); disconnect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); } } void GroupedView::setFilterActive(bool f) { if (f==filterActive) { return; } filterActive=f; if (filterActive && model()) { quint32 count=model()->rowCount(); for (quint32 i=0; iindex(i, 0); if (model()->hasChildren(idx)) { quint32 childCount=model()->rowCount(idx); for (quint32 c=0; cmodel()->rowCount(parent)) { return; } if (filterActive && model() && MPDState_Playing==MPDStatus::self()->state()) { if (scroll) { scrollTo(model()->index(row, 0, parent), QAbstractItemView::PositionAtCenter); } return; } updateRows(parent); if (scroll && (MPDState_Playing==MPDStatus::self()->state() || forceScroll)) { scrollTo(model()->index(row, 0, parent), QAbstractItemView::PositionAtCenter); } } void GroupedView::updateRows(const QModelIndex &parent) { if (!model()) { return; } qint32 count=model()->rowCount(parent); quint16 lastKey=Song::Null_Key; quint32 collection=parent.data(Cantata::Role_CollectionId).toUInt(); QSet keys; for (qint32 i=0; iindex(i, 0, parent).data(Cantata::Role_Key).toUInt(); keys.insert(key); bool hide=key==lastKey && !(key==currentAlbum && autoExpand) && ( ( startClosed && !controlledAlbums[collection].contains(key)) || ( !startClosed && controlledAlbums[collection].contains(key))); setRowHidden(i, parent, hide); lastKey=key; } // Check that 'controlledAlbums' only contains valid keys... controlledAlbums[collection].intersect(keys); } void GroupedView::updateCollectionRows() { if (!model()) { return; } currentAlbum=Song::Null_Key; qint32 count=model()->rowCount(); for (int i=0; iindex(i, 0)); } } void GroupedView::toggle(const QModelIndex &idx) { if (!allowClose) { return; } quint16 indexKey=idx.data(Cantata::Role_Key).toUInt(); if (indexKey==currentAlbum && autoExpand) { return; } quint32 collection=idx.data(Cantata::Role_CollectionId).toUInt(); bool toBeHidden=false; if (controlledAlbums[collection].contains(indexKey)) { controlledAlbums[collection].remove(indexKey); toBeHidden=startClosed; } else { controlledAlbums[collection].insert(indexKey); toBeHidden=!startClosed; } if (model()) { QModelIndex parent=idx.parent(); quint32 count=model()->rowCount(idx.parent()); for (quint32 i=0; iindex(i, 0, parent); quint16 key=index.data(Cantata::Role_Key).toUInt(); if (indexKey==key) { if (isAlbumHeader(index)) { dataChanged(index, index); } else { setRowHidden(i, parent, toBeHidden); } } } } } // Calculate list of selected indexes. If a collapsed album is selected, we also pretend all of its tracks // are selected. QModelIndexList GroupedView::selectedIndexes(bool sorted) const { QModelIndexList indexes = TreeView::selectedIndexes(sorted); QSet indexSet; QModelIndexList sel; foreach (const QModelIndex &idx, indexes) { if (!indexSet.contains(idx)) { indexSet.insert(idx); sel.append(idx); } if (!idx.data(Cantata::Role_IsCollection).toBool()) { quint16 key=idx.data(Cantata::Role_Key).toUInt(); quint32 collection=idx.data(Cantata::Role_CollectionId).toUInt(); if (!isExpanded(key, collection)) { quint32 rowCount=model()->rowCount(idx.parent()); for (quint32 i=idx.row()+1; irect().contains(event->pos())) { // Dont allow to drop on an already selected row - as this seems to cuase a crash!!! QModelIndex idx=TreeView::indexAt(event->pos()); if (idx.isValid() && selectionModel() && selectionModel()->isSelected(idx)) { return; } if (idx.isValid() && isAlbumHeader(idx)) { QRect rect(visualRect(idx)); if (event->pos().y()>(rect.y()+(rect.height()/2))) { quint16 key=idx.data(Cantata::Role_Key).toUInt(); quint32 collection=idx.data(Cantata::Role_CollectionId).toUInt(); if (!isExpanded(key, collection)) { parent=idx.parent(); quint32 rowCount=model()->rowCount(parent); for (quint32 i=idx.row()+1; isetData(parent, dropRowAdjust, Cantata::Role_DropAdjust); } } } } TreeView::dropEvent(event); model()->setData(parent, 0, Cantata::Role_DropAdjust); } void GroupedView::coverLoaded(const Song &song, int size) { if (filterActive || !isVisible() || size!=constCoverSize || song.isArtistImageRequest() || song.isComposerImageRequest()) { return; } quint32 count=model()->rowCount(); quint16 lastKey=Song::Null_Key; QString albumArtist=song.albumArtist(); QString album=song.album; for (quint32 i=0; iindex(i, 0); if (!index.isValid()) { continue; } if (isMultiLevel && model()->hasChildren(index)) { lastKey=Song::Null_Key; quint32 childCount=model()->rowCount(index); for (quint32 c=0; cindex(c, 0, index); if (!child.isValid()) { continue; } quint16 key=child.data(Cantata::Role_Key).toUInt(); if (key!=lastKey && !isRowHidden(c, index)) { Song song=child.data(Cantata::Role_Song).value(); if (song.albumArtist()==albumArtist && song.album==album) { dataChanged(child, child); } } lastKey=key; } } else { quint16 key=index.data(Cantata::Role_Key).toUInt(); if (key!=lastKey && !isRowHidden(i, QModelIndex())) { Song song=index.data(Cantata::Role_Song).value(); if (song.albumArtist()==albumArtist && song.album==album) { dataChanged(index, index); } } lastKey=key; } } } void GroupedView::collectionRemoved(quint32 key) { controlledAlbums.remove(key); } void GroupedView::itemClicked(const QModelIndex &idx) { if (isAlbumHeader(idx)) { QRect indexRect(visualRect(idx)); QRect icon(indexRect.x()+constBorder+4, indexRect.y()+constBorder+((indexRect.height()-constCoverSize)/2), constCoverSize, constCoverSize); QRect header(indexRect); header.setHeight(header.height()/2); header.moveTo(viewport()->mapToGlobal(QPoint(header.x(), header.y()))); icon.moveTo(viewport()->mapToGlobal(QPoint(icon.x(), icon.y()))); if (allowClose && icon.contains(QCursor::pos())) { toggle(idx); } else if (header.contains(QCursor::pos())) { QModelIndexList list; unsigned int key=idx.data(Cantata::Role_Key).toUInt(); QModelIndex i=idx.sibling(idx.row()+1, 0); // QModelIndexList sel=selectedIndexes(); QItemSelectionModel *selModel=selectionModel(); QModelIndexList unsel; while (i.isValid() && i.data(Cantata::Role_Key).toUInt()==key) { #if 0 // The following does not seem to work from the grouped playlist view - the 2nd row never get selected! if (!sel.contains(i)) { unsel.append(i); } #else unsel.append(i); #endif list.append(i); i=i.sibling(i.row()+1, 0); } if (list.count()) { #if 0 // Commendted out as (as noted below) unselection is not working, and we always add to 'unsel' above (because of playlists) if (unsel.isEmpty()) { // TODO: This is not working!!! CHECK selModel if re-add!!! foreach(const QModelIndex &i, list) { selModel->select(i, QItemSelectionModel::Deselect|QItemSelectionModel::Rows); } } else { foreach(const QModelIndex &i, unsel) { selModel->select(i, QItemSelectionModel::Select|QItemSelectionModel::Rows); } } #else if (!unsel.isEmpty() && selModel) { foreach(const QModelIndex &i, unsel) { selModel->select(i, QItemSelectionModel::Select|QItemSelectionModel::Rows); } } #endif } } } } void GroupedView::expand(const QModelIndex &idx, bool singleOnly) { if (allowClose && idx.isValid()) { if (idx.data(Cantata::Role_IsCollection).toBool()) { setExpanded(idx, true); if (!singleOnly) { quint32 count=model()->rowCount(idx); for (quint32 i=0; irowCount(idx); for (quint32 i=0; i * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef GROUPEDVIEW_H #define GROUPEDVIEW_H #include #include "treeview.h" #include "actionitemdelegate.h" struct Song; class RatingPainter; class GroupedView; class GroupedViewDelegate : public ActionItemDelegate { Q_OBJECT public: GroupedViewDelegate(GroupedView *p); virtual ~GroupedViewDelegate(); QSize sizeHint(int type, bool isCollection) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; int drawRatings(QPainter *painter, const Song &song, const QRect &r, const QFontMetrics &fm, const QColor &col) const; private: GroupedView *view; mutable RatingPainter *ratingPainter; }; class GroupedView : public TreeView { Q_OBJECT public: enum Status { State_Default, State_Playing, State_StopAfter, State_StopAfterTrack, State_Paused, State_Stopped }; static void setup(); static int coverSize(); static int borderSize(); static int iconSize(); static void drawPlayState(QPainter *painter, const QStyleOptionViewItem &option, const QRect &r, int state); GroupedView(QWidget *parent=0, bool isPlayQueue=false); virtual ~GroupedView(); void setModel(QAbstractItemModel *model); void setFilterActive(bool f); bool isFilterActive() const { return filterActive; } void setAutoExpand(bool ae) { autoExpand=ae; } bool isAutoExpand() const { return autoExpand; } void setStartClosed(bool sc); bool isStartClosed() const { return startClosed; } void setMultiLevel(bool ml) { isMultiLevel=ml; } void updateRows(qint32 row, quint16 curAlbum, bool scroll, const QModelIndex &parent=QModelIndex(), bool forceScroll=false); void updateCollectionRows(); bool isCurrentAlbum(quint16 key) const { return key==currentAlbum; } bool isExpanded(quint16 key, quint32 collection) const { return filterActive || (autoExpand && currentAlbum==key) || (startClosed && controlledAlbums[collection].contains(key)) || (!startClosed && !controlledAlbums[collection].contains(key)); } void toggle(const QModelIndex &idx); QModelIndexList selectedIndexes() const { return selectedIndexes(true); } QModelIndexList selectedIndexes(bool sorted) const; void dropEvent(QDropEvent *event); void collectionRemoved(quint32 key); void expand(const QModelIndex &idx, bool singleOnly=false); void collapse(const QModelIndex &idx, bool singleOnly=false); public Q_SLOTS: void updateRows(const QModelIndex &parent); void coverLoaded(const Song &song, int size); private Q_SLOTS: void itemClicked(const QModelIndex &index); private: bool allowClose; bool startClosed; bool autoExpand; bool filterActive; bool isMultiLevel; quint16 currentAlbum; QMap > controlledAlbums; }; #endif cantata-2.2.0/widgets/icons.cpp000066400000000000000000000164731316350454000164270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "icons.h" #include "gui/settings.h" #include "support/globalstatic.h" #include "support/utils.h" #include "support/pathrequester.h" #include "support/monoicon.h" #if !defined Q_OS_WIN && !defined Q_OS_MAC #include "support/gtkstyle.h" #endif #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include #include #include #include GLOBAL_STATIC(Icons, instance) #if defined Q_OS_MAC || defined Q_OS_WIN #define ALWAYS_USE_MONO_ICONS #endif Icons::Icons() { QColor stdColor=Utils::monoIconColor(); singleIcon=MonoIcon::icon(FontAwesome::ex_one, stdColor); consumeIcon=MonoIcon::icon(":consume.svg", stdColor); menuIcon=MonoIcon::icon(FontAwesome::bars, stdColor); QString iconFile=QString(CANTATA_SYS_ICONS_DIR+"stream.png"); if (QFile::exists(iconFile)) { streamIcon.addFile(iconFile); } if (streamIcon.isNull()) { streamIcon=Icon("applications-internet"); } podcastIcon=MonoIcon::icon(FontAwesome::podcast, stdColor); audioFileIcon=Icon("audio-x-generic"); folderIcon=Icon("inode-directory"); speakerIcon=Icon(QStringList() << "speaker" << "audio-speakers" << "gnome-volume-control"); repeatIcon=MonoIcon::icon(FontAwesome::refresh, stdColor); shuffleIcon=MonoIcon::icon(FontAwesome::random, stdColor); filesIcon=Icon(QStringList() << "folder-downloads" << "folder-download" << "folder" << "go-down"); albumIconSmall.addFile(":album32.svg"); albumIconLarge.addFile(":album.svg"); albumMonoIcon=MonoIcon::icon(":mono-album.svg", stdColor); artistIcon=MonoIcon::icon(":artist.svg", stdColor); genreIcon=MonoIcon::icon(":genre.svg", stdColor); appIcon=Icon("cantata"); lastFmIcon=MonoIcon::icon(FontAwesome::lastfmsquare, MonoIcon::constRed, MonoIcon::constRed); replacePlayQueueIcon=MonoIcon::icon(FontAwesome::play, stdColor); appendToPlayQueueIcon=MonoIcon::icon(FontAwesome::plus, stdColor); centrePlayQueueOnTrackIcon=MonoIcon::icon(Qt::RightToLeft==QApplication::layoutDirection() ? FontAwesome::chevronleft : FontAwesome::chevronright, stdColor); savePlayQueueIcon=MonoIcon::icon(FontAwesome::save, stdColor); cutIcon=MonoIcon::icon(FontAwesome::remove, MonoIcon::constRed, MonoIcon::constRed); addNewItemIcon=MonoIcon::icon(FontAwesome::plussquare, stdColor); editIcon=MonoIcon::icon(FontAwesome::edit, stdColor); stopDynamicIcon=MonoIcon::icon(FontAwesome::stop, MonoIcon::constRed, MonoIcon::constRed); searchIcon=MonoIcon::icon(FontAwesome::search, stdColor); addToFavouritesIcon=MonoIcon::icon(FontAwesome::heart, MonoIcon::constRed, MonoIcon::constRed); reloadIcon=MonoIcon::icon(FontAwesome::repeat, stdColor); configureIcon=MonoIcon::icon(FontAwesome::cogs, stdColor); connectIcon=MonoIcon::icon(FontAwesome::plug, stdColor); disconnectIcon=MonoIcon::icon(FontAwesome::eject, stdColor); downloadIcon=MonoIcon::icon(FontAwesome::download, stdColor); removeIcon=MonoIcon::icon(FontAwesome::minussquare, MonoIcon::constRed, MonoIcon::constRed); minusIcon=MonoIcon::icon(FontAwesome::minus, MonoIcon::constRed, MonoIcon::constRed); addIcon=MonoIcon::icon(FontAwesome::plus, stdColor); addBookmarkIcon=MonoIcon::icon(FontAwesome::bookmark, stdColor); audioListIcon=MonoIcon::icon(FontAwesome::music, stdColor); playlistListIcon=MonoIcon::icon(FontAwesome::list, stdColor); dynamicListIcon=MonoIcon::icon(FontAwesome::cube, stdColor); rssListIcon=MonoIcon::icon(FontAwesome::rss, stdColor); savedRssListIcon=MonoIcon::icon(FontAwesome::rsssquare, stdColor); clockIcon=MonoIcon::icon(FontAwesome::clocko, stdColor); folderListIcon=MonoIcon::icon(FontAwesome::foldero, stdColor); refreshIcon=MonoIcon::icon(FontAwesome::refresh, stdColor); streamListIcon=audioListIcon; streamCategoryIcon=folderListIcon; #ifdef ENABLE_HTTP_STREAM_PLAYBACK httpStreamIcon=MonoIcon::icon(FontAwesome::headphones, stdColor); #endif leftIcon=MonoIcon::icon(FontAwesome::chevronleft, stdColor); rightIcon=MonoIcon::icon(FontAwesome::chevronright, stdColor); upIcon=MonoIcon::icon(FontAwesome::chevronup, stdColor); downIcon=MonoIcon::icon(FontAwesome::chevrondown, stdColor); PathRequester::setIcon(folderListIcon); cancelIcon=MonoIcon::icon(FontAwesome::close, MonoIcon::constRed, MonoIcon::constRed); #if !defined Q_OS_WIN if (QLatin1String("gnome")==QIcon::themeName()) { QColor col=QApplication::palette().color(QPalette::Active, QPalette::WindowText); contextIcon=MonoIcon::icon(QLatin1String(":sidebar-info"), col); } else #endif contextIcon=Icon(QStringList() << "dialog-information" << "information"); } void Icons::initSidebarIcons() { #ifdef Q_OS_MAC QColor iconCol=OSXStyle::self()->monoIconColor(); #else QColor iconCol=Utils::monoIconColor(); #endif playqueueIcon=MonoIcon::icon(QLatin1String(":sidebar-playqueue"), iconCol); libraryIcon=MonoIcon::icon(QLatin1String(":sidebar-library"), iconCol); foldersIcon=MonoIcon::icon(QLatin1String(":sidebar-folders"), iconCol); playlistsIcon=MonoIcon::icon(QLatin1String(":sidebar-playlists"), iconCol); onlineIcon=MonoIcon::icon(QLatin1String(":sidebar-online"), iconCol); infoSidebarIcon=MonoIcon::icon(QLatin1String(":sidebar-info"), iconCol); #ifdef ENABLE_DEVICES_SUPPORT devicesIcon=MonoIcon::icon(QLatin1String(":sidebar-devices"), iconCol); #endif searchTabIcon=MonoIcon::icon(QLatin1String(":sidebar-search"), iconCol); } void Icons::initToolbarIcons(QColor toolbarText) { bool rtl=QApplication::isRightToLeft(); toolbarText=Utils::clampColor(toolbarText); toolbarPrevIcon=MonoIcon::icon(QLatin1String(rtl ? ":media-next" : ":media-prev"), toolbarText, toolbarText); toolbarPlayIcon=MonoIcon::icon(QLatin1String(rtl ? ":media-play-rtl" : ":media-play"), toolbarText, toolbarText); toolbarPauseIcon=MonoIcon::icon(QLatin1String(":media-pause"), toolbarText, toolbarText); toolbarStopIcon=MonoIcon::icon(QLatin1String(":media-stop"), toolbarText, toolbarText); toolbarNextIcon=MonoIcon::icon(QLatin1String(rtl ? ":media-prev" : ":media-next"), toolbarText, toolbarText); infoIcon=MonoIcon::icon(QLatin1String(":sidebar-info"), toolbarText, toolbarText); toolbarMenuIcon=MonoIcon::icon(FontAwesome::bars, toolbarText, toolbarText); } const Icon &Icons::albumIcon(int size, bool mono) const { return !mono || albumMonoIcon.isNull() ? size<48 ? albumIconSmall : albumIconLarge : albumMonoIcon; } cantata-2.2.0/widgets/icons.h000066400000000000000000000055011316350454000160620ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ICONS_H #define ICONS_H #include "support/icon.h" #include "config.h" class Icons { public: static Icons *self(); Icons(); void initSidebarIcons(); void initToolbarIcons(QColor toolbarText); Icon appIcon; Icon genreIcon; Icon artistIcon; const Icon &albumIcon(int size, bool mono=false) const; Icon albumIconLarge; Icon albumIconSmall; Icon albumMonoIcon; Icon podcastIcon; Icon folderIcon; Icon audioFileIcon; Icon singleIcon; Icon consumeIcon; Icon repeatIcon; Icon shuffleIcon; Icon streamIcon; Icon speakerIcon; Icon menuIcon; Icon filesIcon; Icon playqueueIcon; Icon libraryIcon; Icon foldersIcon; Icon playlistsIcon; Icon onlineIcon; Icon contextIcon; Icon searchTabIcon; Icon infoIcon; Icon infoSidebarIcon; #ifdef ENABLE_DEVICES_SUPPORT Icon devicesIcon; #endif QIcon lastFmIcon; Icon toolbarMenuIcon; Icon toolbarPrevIcon; Icon toolbarPlayIcon; Icon toolbarPauseIcon; Icon toolbarNextIcon; Icon toolbarStopIcon; QIcon replacePlayQueueIcon; QIcon appendToPlayQueueIcon; QIcon centrePlayQueueOnTrackIcon; QIcon savePlayQueueIcon; QIcon cutIcon; QIcon addNewItemIcon; QIcon editIcon; QIcon stopDynamicIcon; QIcon searchIcon; QIcon addToFavouritesIcon; QIcon reloadIcon; QIcon configureIcon; QIcon connectIcon; QIcon disconnectIcon; QIcon downloadIcon; QIcon removeIcon; QIcon minusIcon; QIcon addIcon; QIcon addBookmarkIcon; QIcon audioListIcon; QIcon playlistListIcon; QIcon dynamicListIcon; QIcon rssListIcon; QIcon savedRssListIcon; QIcon clockIcon; QIcon folderListIcon; QIcon streamListIcon; QIcon streamCategoryIcon; #ifdef ENABLE_HTTP_STREAM_PLAYBACK QIcon httpStreamIcon; #endif QIcon leftIcon; QIcon rightIcon; QIcon upIcon; QIcon downIcon; QIcon cancelIcon; QIcon refreshIcon; }; #endif cantata-2.2.0/widgets/itemview.cpp000066400000000000000000001450641316350454000171440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "itemview.h" #include "groupedview.h" #include "tableview.h" #include "messageoverlay.h" #include "models/roles.h" #include "gui/covers.h" #include "models/proxymodel.h" #include "actionitemdelegate.h" #include "basicitemdelegate.h" #include "models/actionmodel.h" #include "support/icon.h" #include "config.h" #include "support/gtkstyle.h" #include "support/proxystyle.h" #include "support/spinner.h" #include "support/action.h" #include "support/actioncollection.h" #include "support/configuration.h" #include "support/flattoolbutton.h" #include #include #include #include #include #include #include static int detailedViewDecorationSize=22; static int simpleViewDecorationSize=16; static int listCoverSize=22; static int gridCoverSize=22; static inline int adjust(int v) { if (v>48) { static const int constStep=4; return (((int)(v/constStep))*constStep)+((v%constStep) ? constStep : 0); } else { return Icon::stdSize(v); } } void ItemView::setup() { int height=QApplication::fontMetrics().height(); if (height>22) { detailedViewDecorationSize=Icon::stdSize(height*1.4); simpleViewDecorationSize=Icon::stdSize(height); } else { detailedViewDecorationSize=22; simpleViewDecorationSize=16; } listCoverSize=qMax(32, adjust(2*height)); gridCoverSize=qMax(128, adjust(8*height)); } KeyEventHandler::KeyEventHandler(QAbstractItemView *v, QAction *a) : QObject(v) , view(v) , deleteAct(a) , interceptBackspace(qobject_cast(view)) { } bool KeyEventHandler::eventFilter(QObject *obj, QEvent *event) { if (view->hasFocus()) { if (QEvent::KeyRelease==event->type()) { QKeyEvent *keyEvent=static_cast(event); if (deleteAct && Qt::Key_Delete==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) { deleteAct->trigger(); return true; } } else if (QEvent::KeyPress==event->type()) { QKeyEvent *keyEvent=static_cast(event); if (interceptBackspace && Qt::Key_Backspace==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) { emit backspacePressed(); } } } return QObject::eventFilter(obj, event); } ViewEventHandler::ViewEventHandler(ActionItemDelegate *d, QAbstractItemView *v) : KeyEventHandler(v, 0) , delegate(d) { } // HACK time. For some reason, IconView is not always re-drawn when mouse leaves the view. // We sometimes get an item that is left in the mouse-over state. So, work-around this by // keeping track of when mouse is over listview. bool ViewEventHandler::eventFilter(QObject *obj, QEvent *event) { if (delegate) { if (QEvent::Enter==event->type()) { delegate->setUnderMouse(true); view->viewport()->update(); } else if (QEvent::Leave==event->type()) { delegate->setUnderMouse(false); view->viewport()->update(); } } return KeyEventHandler::eventFilter(obj, event); } static const int constDevImageSize=32; static inline double subTextAlpha(bool selected) { return selected ? 0.7 : 0.5; } class ListDelegate : public ActionItemDelegate { public: ListDelegate(ListView *v, QAbstractItemView *p) : ActionItemDelegate(p) , view(v) { } virtual ~ListDelegate() { } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option) if (view && QListView::IconMode==view->viewMode()) { return QSize(gridCoverSize+8, gridCoverSize+(QApplication::fontMetrics().height()*2.5)); } else { int imageSize = index.data(Cantata::Role_ListImage).toBool() ? listCoverSize : 0; // TODO: Any point to checking one-line here? All models return sub-text... // Things will be quicker if we dont call SubText here... bool oneLine = false ; // index.data(Cantata::Role_SubText).toString().isEmpty(); bool showCapacity = !index.data(Cantata::Role_CapacityText).toString().isEmpty(); int textHeight = QApplication::fontMetrics().height()*(oneLine ? 1 : 2); if (showCapacity) { imageSize=constDevImageSize; } return QSize(qMax(64, imageSize) + (constBorder * 2), qMax(textHeight, imageSize) + (constBorder*2) + (int)((showCapacity ? textHeight*Utils::smallFontFactor(QApplication::font()) : 0)+0.5)); } } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } bool mouseOver=option.state&QStyle::State_MouseOver; bool gtk=mouseOver && GtkStyle::isActive(); bool selected=option.state&QStyle::State_Selected; bool active=option.state&QStyle::State_Active; bool drawBgnd=true; bool iconMode = view && QListView::IconMode==view->viewMode(); QStyleOptionViewItem opt(option); opt.showDecorationSelected=true; if (!underMouse) { if (mouseOver && !selected) { drawBgnd=false; } mouseOver=false; } if (drawBgnd) { if (mouseOver && gtk) { GtkStyle::drawSelection(opt, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, itemView()); } } QString capacityText=index.data(Cantata::Role_CapacityText).toString(); bool showCapacity = !capacityText.isEmpty(); QString text = iconMode ? index.data(Cantata::Role_BriefMainText).toString() : QString(); if (text.isEmpty()) { text=index.data(Cantata::Role_MainText).toString(); } if (text.isEmpty()) { text=index.data(Qt::DisplayRole).toString(); } #ifdef Q_OS_WIN QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif QRect r(option.rect); QRect r2(r); QString childText = index.data(Cantata::Role_SubText).toString(); QPixmap pix; if (showCapacity) { const_cast(index.model())->setData(index, iconMode ? gridCoverSize : listCoverSize, Cantata::Role_Image); pix=index.data(Cantata::Role_Image).value(); } if (pix.isNull() && (iconMode || index.data(Cantata::Role_ListImage).toBool())) { Song cSong=index.data(iconMode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value(); if (!cSong.isEmpty()) { QPixmap *cp=Covers::self()->get(cSong, iconMode ? gridCoverSize : listCoverSize, getCoverInUiThread(index)); if (cp) { pix=*cp; } } } if (pix.isNull()) { int size=iconMode ? gridCoverSize : detailedViewDecorationSize; pix=index.data(Qt::DecorationRole).value().pixmap(size, size, selected && textColor==qApp->palette().color(QPalette::HighlightedText) ? QIcon::Selected : QIcon::Normal); } bool oneLine = childText.isEmpty(); ActionPos actionPos = iconMode ? AP_VTop : AP_HMiddle; bool rtl = QApplication::isRightToLeft(); if (childText==QLatin1String("-")) { childText.clear(); } painter->save(); painter->setClipRect(r); QFont textFont(index.data(Qt::FontRole).value()); QFontMetrics textMetrics(textFont); int textHeight=textMetrics.height(); if (showCapacity) { r.adjust(2, 0, 0, -(textHeight+4)); } if (AP_VTop==actionPos) { r.adjust(constBorder, constBorder*4, -constBorder, -constBorder); r2=r; } else { r.adjust(constBorder, 0, -constBorder, 0); } if (!pix.isNull()) { QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO(); int adjust=qMax(layoutSize.width(), layoutSize.height()); if (AP_VTop==actionPos) { int xpos=r.x()+((r.width()-layoutSize.width())/2); painter->drawPixmap(xpos, r.y(), layoutSize.width(), layoutSize.height(), pix); QColor color(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); double alphas[]={0.25, 0.125, 0.061}; QRect border(xpos, r.y(), layoutSize.width(), layoutSize.height()); QRect shadow(border); for (int i=0; i<3; ++i) { shadow.adjust(1, 1, 1, 1); color.setAlphaF(alphas[i]); painter->setPen(color); painter->drawLine(shadow.bottomLeft()+QPoint(i+1, 0), shadow.bottomRight()+QPoint(-((i*2)+2), 0)); painter->drawLine(shadow.bottomRight()+QPoint(0, -((i*2)+2)), shadow.topRight()+QPoint(0, i+1)); if (1==i) { painter->drawPoint(shadow.bottomRight()-QPoint(2, 1)); painter->drawPoint(shadow.bottomRight()-QPoint(1, 2)); painter->drawPoint(shadow.bottomLeft()-QPoint(1, 1)); painter->drawPoint(shadow.topRight()-QPoint(1, 1)); } else if (2==i) { painter->drawPoint(shadow.bottomRight()-QPoint(4, 1)); painter->drawPoint(shadow.bottomRight()-QPoint(1, 4)); painter->drawPoint(shadow.bottomLeft()-QPoint(0, 1)); painter->drawPoint(shadow.topRight()-QPoint(1, 0)); painter->drawPoint(shadow.bottomRight()-QPoint(2, 2)); } } color.setAlphaF(0.4); painter->setPen(color); painter->drawRect(border.adjusted(0, 0, -1, -1)); r.adjust(0, adjust+3, 0, -3); } else { if (rtl) { painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(3, 0, -(3+adjust), 0); } else { painter->drawPixmap(r.x(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(adjust+3, 0, -3, 0); } } } QRect textRect; QTextOption textOpt(AP_VTop==actionPos ? Qt::AlignHCenter|Qt::AlignVCenter : Qt::AlignVCenter); textOpt.setWrapMode(QTextOption::NoWrap); if (oneLine) { textRect=QRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight); text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, text, textOpt); } else { QFont childFont(Utils::smallFont(textFont)); QFontMetrics childMetrics(childFont); QRect childRect; int childHeight=childMetrics.height(); int totalHeight=textHeight+childHeight; textRect=QRect(r.x(), r.y()+((r.height()-totalHeight)/2), r.width(), textHeight); childRect=QRect(r.x(), r.y()+textHeight+((r.height()-totalHeight)/2), r.width(), (iconMode ? childHeight-(2*constBorder) : childHeight)); text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, text, textOpt); if (!childText.isEmpty()) { childText = childMetrics.elidedText(childText, Qt::ElideRight, childRect.width(), QPalette::WindowText); textColor.setAlphaF(subTextAlpha(selected)); painter->setPen(textColor); painter->setFont(childFont); painter->drawText(childRect, childText, textOpt); } } if (showCapacity) { QColor col(Qt::white); col.setAlphaF(0.25); painter->fillRect(QRect(r2.x(), r2.bottom()-(textHeight+8), r2.width(), textHeight+8), col); QStyleOptionProgressBar opt; double capacity=index.data(Cantata::Role_Capacity).toDouble(); if (capacity<0.0) { capacity=0.0; } else if (capacity>1.0) { capacity=1.0; } opt.minimum=0; opt.maximum=1000; opt.progress=capacity*1000; opt.textVisible=true; opt.text=capacityText; opt.rect=QRect(r2.x()+4, r2.bottom()-(textHeight+4), r2.width()-8, textHeight); opt.state=QStyle::State_Enabled; opt.palette=option.palette; opt.direction=QApplication::layoutDirection(); opt.fontMetrics=textMetrics; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, 0L); } if (drawBgnd && mouseOver) { drawIcons(painter, AP_VTop==actionPos ? option.rect : r, mouseOver, rtl, actionPos, index); } if (!iconMode) { BasicItemDelegate::drawLine(painter, option.rect, textColor); } painter->restore(); } virtual bool getCoverInUiThread(const QModelIndex &) const { return false; } virtual QWidget * itemView() const { return view; } protected: ListView *view; }; class TreeDelegate : public ListDelegate { public: TreeDelegate(QAbstractItemView *p) : ListDelegate(0, p) , simpleStyle(true) , treeView(p) { } virtual ~TreeDelegate() { } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (noIcons) { return QStyledItemDelegate::sizeHint(option, index); } if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) { return ListDelegate::sizeHint(option, index); } QSize sz(QStyledItemDelegate::sizeHint(option, index)); if (index.data(Cantata::Role_ListImage).toBool()) { sz.setHeight(qMax(sz.height(), listCoverSize)); } int textHeight = QApplication::fontMetrics().height()*1.25; sz.setHeight(qMax(sz.height(), textHeight)+(constBorder*2)); return sz; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) { ListDelegate::paint(painter, option, index); return; } QStringList text=index.data(Qt::DisplayRole).toString().split(Song::constFieldSep); bool gtk=GtkStyle::isActive(); bool rtl = QApplication::isRightToLeft(); bool selected=option.state&QStyle::State_Selected; bool active=option.state&QStyle::State_Active; bool mouseOver=underMouse && option.state&QStyle::State_MouseOver; #ifdef Q_OS_WIN QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif if (mouseOver && gtk) { GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, itemView()); } QRect r(option.rect); r.adjust(4, 0, -4, 0); if (!noIcons) { QPixmap pix; if (index.data(Cantata::Role_ListImage).toBool()) { Song cSong=index.data(Cantata::Role_CoverSong).value(); if (!cSong.isEmpty()) { QPixmap *cp=Covers::self()->get(cSong, listCoverSize); if (cp) { pix=*cp; } } } if (pix.isNull()) { pix=index.data(Qt::DecorationRole).value().pixmap(simpleViewDecorationSize, simpleViewDecorationSize, selected && textColor==qApp->palette().color(QPalette::HighlightedText) ? QIcon::Selected : QIcon::Normal); } if (!pix.isNull()) { QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO(); int adjust=qMax(layoutSize.width(), layoutSize.height()); if (rtl) { painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(3, 0, -(3+adjust), 0); } else { painter->drawPixmap(r.x(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(adjust+3, 0, -3, 0); } } } if (text.count()>0) { QFont textFont(QApplication::font()); QFontMetrics textMetrics(textFont); int textHeight=textMetrics.height(); QTextOption textOpt(Qt::AlignVCenter); QRect textRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight); QString str=textMetrics.elidedText(text.at(0), Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->save(); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, str, textOpt); if (text.count()>1) { int mainWidth=textMetrics.width(str); text.takeFirst(); str=text.join(QString(" – ")); textRect=QRect(r.x()+(mainWidth+8), r.y()+((r.height()-textHeight)/2), r.width()-(mainWidth+8), textHeight); if (textRect.width()>4) { str = textMetrics.elidedText(str, Qt::ElideRight, textRect.width(), QPalette::WindowText); textColor.setAlphaF(subTextAlpha(selected)); painter->setPen(textColor); painter->drawText(textRect, str, textOpt/*QTextOption(Qt::AlignVCenter|Qt::AlignRight)*/); } } painter->restore(); } if (mouseOver) { drawIcons(painter, option.rect, mouseOver, rtl, AP_HMiddle, index); } #ifdef Q_OS_WIN BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif } void setSimple(bool s) { simpleStyle=s; } void setNoIcons(bool n) { noIcons=n; } virtual bool getCoverInUiThread(const QModelIndex &idx) const { // Want album covers in artists view to load quickly... return idx.isValid() && idx.data(Cantata::Role_LoadCoverInUIThread).toBool(); } virtual QWidget * itemView() const { return treeView; } bool simpleStyle; bool noIcons; QAbstractItemView *treeView; }; ItemView::Mode ItemView::toMode(const QString &str) { for (int i=0; icreateAction("itemview-goback", tr("Go Back")); backAction->setShortcut(Qt::AltModifier+(Qt::LeftToRight==layoutDirection() ? Qt::Key_Left : Qt::Key_Right)); } title->addAction(backAction); title->setVisible(false); Action::updateToolTip(backAction); QAction *sep=new QAction(this); sep->setSeparator(true); listView->addAction(sep); treeView->setPageDefaults(); // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview. treeView->setAttribute(Qt::WA_MouseTracking); iconGridSize=listGridSize=listView->gridSize(); ListDelegate *ld=new ListDelegate(listView, listView); TreeDelegate *td=new TreeDelegate(treeView); listView->setItemDelegate(ld); treeView->setItemDelegate(td); listView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); treeView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); ViewEventHandler *listViewEventHandler=new ViewEventHandler(ld, listView); ViewEventHandler *treeViewEventHandler=new ViewEventHandler(td, treeView); listView->installFilter(listViewEventHandler); treeView->installFilter(treeViewEventHandler); connect(searchWidget, SIGNAL(returnPressed()), this, SLOT(delaySearchItems())); connect(searchWidget, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems())); connect(searchWidget, SIGNAL(active(bool)), this, SLOT(searchActive(bool))); connect(treeView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(treeView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(treeView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); connect(listView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(activateItem(const QModelIndex &))); connect(listView, SIGNAL(itemDoubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(listView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); connect(backAction, SIGNAL(triggered()), this, SLOT(backActivated())); connect(listViewEventHandler, SIGNAL(backspacePressed()), this, SLOT(backActivated())); connect(title, SIGNAL(addToPlayQueue()), this, SLOT(addTitleButtonClicked())); connect(title, SIGNAL(replacePlayQueue()), this, SLOT(replaceTitleButtonClicked())); connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); searchWidget->setVisible(false); #ifdef Q_OS_MAC treeView->setAttribute(Qt::WA_MacShowFocusRect, 0); listView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif } ItemView::~ItemView() { } void ItemView::alwaysShowHeader() { title->setVisible(true); title->setProperty(constAlwaysShowProp, true); setTitle(); controlViewFrame(); } void ItemView::load(Configuration &config) { if (config.get(constSearchActiveKey, false)) { focusSearch(); } setMode(toMode(config.get(constViewModeKey, modeStr(mode)))); setStartClosed(config.get(constStartClosedKey, isStartClosed())); setSearchCategory(config.get(constSearchCategoryKey, searchCategory())); } void ItemView::save(Configuration &config) { config.set(constSearchActiveKey, searchWidget->isActive()); config.set(constViewModeKey, modeStr(mode)); if (groupedView) { config.set(constStartClosedKey, isStartClosed()); } TableView *tv=qobject_cast(view()); if (tv) { tv->saveHeader(); } QString cat=searchCategory(); if (!cat.isEmpty()) { config.set(constSearchCategoryKey, cat); } } void ItemView::allowGroupedView() { if (!groupedView) { groupedView=new GroupedView(stackedWidget); stackedWidget->addWidget(groupedView); groupedView->setProperty(constPageProp, stackedWidget->count()-1); // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview. groupedView->setAttribute(Qt::WA_MouseTracking, true); ViewEventHandler *viewHandler=new ViewEventHandler(qobject_cast(groupedView->itemDelegate()), groupedView); groupedView->installFilter(viewHandler); groupedView->setAutoExpand(false); groupedView->setMultiLevel(true); connect(groupedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(groupedView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(groupedView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); groupedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); #ifdef Q_OS_MAC groupedView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif } } void ItemView::allowTableView(TableView *v) { if (!tableView) { tableView=v; tableView->setParent(stackedWidget); stackedWidget->addWidget(tableView); tableView->setProperty(constPageProp, stackedWidget->count()-1); ViewEventHandler *viewHandler=new ViewEventHandler(0, tableView); tableView->installFilter(viewHandler); connect(tableView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(tableView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(tableView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); tableView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); #ifdef Q_OS_MAC tableView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif } } void ItemView::addAction(QAction *act) { treeView->addAction(act); listView->addAction(act); if (groupedView) { groupedView->addAction(act); } if (tableView) { tableView->addAction(act); } } void ItemView::addSeparator() { QAction *act=new QAction(this); act->setSeparator(true); addAction(act); } void ItemView::setMode(Mode m) { initialised=true; if (m<0 || m>=Mode_Count || (Mode_GroupedTree==m && !groupedView) || (Mode_Table==m && !tableView)) { m=Mode_SimpleTree; } if (m==mode) { return; } prevTopIndex.clear(); searchWidget->setText(QString()); if (!title->property(constAlwaysShowProp).toBool()) { title->setVisible(false); controlViewFrame(); } QIcon oldBgndIcon=bgndIcon; if (!bgndIcon.isNull()) { setBackgroundImage(QIcon()); } mode=m; int stackIndex=0; if (usingTreeView()) { listView->setModel(0); if (groupedView) { groupedView->setModel(0); } if (tableView) { tableView->saveHeader(); tableView->setModel(0); } treeView->setModel(itemModel); treeView->setHidden(false); static_cast(treeView->itemDelegate())->setSimple(Mode_SimpleTree==mode || Mode_BasicTree==mode); static_cast(treeView->itemDelegate())->setNoIcons(Mode_BasicTree==mode); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } treeView->reset(); } else if (Mode_GroupedTree==mode) { treeView->setModel(0); listView->setModel(0); if (tableView) { tableView->saveHeader(); tableView->setModel(0); } groupedView->setHidden(false); treeView->setHidden(true); groupedView->setModel(itemModel); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } stackIndex=groupedView->property(constPageProp).toInt(); } else if (Mode_Table==mode) { int w=view()->width(); treeView->setModel(0); listView->setModel(0); if (groupedView) { groupedView->setModel(0); } tableView->setHidden(false); treeView->setHidden(true); tableView->setModel(itemModel); tableView->initHeader(); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } tableView->resize(w, tableView->height()); stackIndex=tableView->property(constPageProp).toInt(); } else { stackIndex=1; treeView->setModel(0); if (groupedView) { groupedView->setModel(0); } if (tableView) { tableView->saveHeader(); tableView->setModel(0); } listView->setModel(itemModel); goToTop(); if (Mode_IconTop!=mode) { listView->setGridSize(listGridSize); listView->setViewMode(QListView::ListMode); listView->setResizeMode(QListView::Fixed); // listView->setAlternatingRowColors(true); listView->setWordWrap(false); } } stackedWidget->setCurrentIndex(stackIndex); if (spinner) { spinner->setWidget(view()); if (spinner->isActive()) { spinner->start(); } } if (msgOverlay) { msgOverlay->setWidget(view()); } if (!oldBgndIcon.isNull()) { setBackgroundImage(oldBgndIcon); } controlViewFrame(); } QModelIndexList ItemView::selectedIndexes(bool sorted) const { if (usingTreeView()) { return treeView->selectedIndexes(sorted); } else if (Mode_GroupedTree==mode) { return groupedView->selectedIndexes(sorted); } else if (Mode_Table==mode) { return tableView->selectedIndexes(sorted); } return listView->selectedIndexes(sorted); } void ItemView::goToTop() { setLevel(0); prevTopIndex.clear(); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } listView->setRootIndex(QModelIndex()); setTitle(); emit rootIndexSet(QModelIndex()); } void ItemView::setEnabled(bool en) { if (treeView) { treeView->setEnabled(en); } if (groupedView) { groupedView->setEnabled(en); } if (tableView) { tableView->setEnabled(en); } if (listView) { listView->setEnabled(en); } } void ItemView::setLevel(int l, bool haveChildren) { currentLevel=l; if (Mode_IconTop==mode) { if (0==currentLevel || haveChildren) { if (QListView::IconMode!=listView->viewMode()) { listView->setGridSize(iconGridSize); listView->setViewMode(QListView::IconMode); listView->setResizeMode(QListView::Adjust); // listView->setAlternatingRowColors(false); listView->setWordWrap(true); listView->setDragDropMode(QAbstractItemView::DragOnly); static_cast(listView->itemDelegate())->setLargeIcons(true); } } else if(QListView::ListMode!=listView->viewMode()) { listView->setGridSize(listGridSize); listView->setViewMode(QListView::ListMode); listView->setResizeMode(QListView::Fixed); // listView->setAlternatingRowColors(true); listView->setWordWrap(false); listView->setDragDropMode(QAbstractItemView::DragOnly); static_cast(listView->itemDelegate())->setLargeIcons(false); } } if (view()->selectionModel()) { view()->selectionModel()->clearSelection(); } if (!title->property(constAlwaysShowProp).toBool()) { title->setVisible(currentLevel>0); controlViewFrame(); } setTitle(); } QAbstractItemView * ItemView::view() const { if (usingTreeView()) { return treeView; } else if(Mode_GroupedTree==mode) { return groupedView; } else if(Mode_Table==mode) { return tableView; } else { return listView; } } void ItemView::setModel(QAbstractItemModel *m) { if (itemModel) { disconnect(itemModel, SIGNAL(modelReset()), this, SLOT(modelReset())); if (qobject_cast(itemModel)) { disconnect(static_cast(itemModel)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } else { disconnect(itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } } itemModel=m; if (!initialised) { mode=Mode_List; setMode(Mode_SimpleTree); } if (m) { connect(m, SIGNAL(modelReset()), this, SLOT(modelReset())); if (qobject_cast(m)) { connect(static_cast(m)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } else { connect(m, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } } view()->setModel(m); } bool ItemView::searchVisible() const { return searchWidget->isVisible(); } QString ItemView::searchText() const { return searchWidget->isVisible() ? searchWidget->text() : QString(); } QString ItemView::searchCategory() const { return searchWidget->category(); } void ItemView::clearSearchText() { return searchWidget->setText(QString()); } void ItemView::setUniformRowHeights(bool v) { treeView->setUniformRowHeights(v); } void ItemView::setAcceptDrops(bool v) { listView->setAcceptDrops(v); treeView->setAcceptDrops(v); if (groupedView) { groupedView->setAcceptDrops(v); } if (tableView) { tableView->setAcceptDrops(v); } } void ItemView::setDragDropOverwriteMode(bool v) { listView->setDragDropOverwriteMode(v); treeView->setDragDropOverwriteMode(v); if (groupedView) { groupedView->setDragDropOverwriteMode(v); } if (tableView) { tableView->setDragDropOverwriteMode(v); } } void ItemView::setDragDropMode(QAbstractItemView::DragDropMode v) { listView->setDragDropMode(v); treeView->setDragDropMode(v); if (groupedView) { groupedView->setDragDropMode(v); } if (tableView) { tableView->setDragDropMode(v); } } void ItemView::setGridSize(const QSize &sz) { iconGridSize=sz; if (Mode_IconTop==mode && 0==currentLevel) { listView->setGridSize(listGridSize); static_cast(listView->itemDelegate())->setLargeIcons(iconGridSize.width()>(ActionItemDelegate::constLargeActionIconSize*6)); } } void ItemView::update() { view()->update(); } void ItemView::setDeleteAction(QAction *act) { if (!listView->filter() || !qobject_cast(listView->filter())) { listView->installFilter(new KeyEventHandler(listView, act)); } else { static_cast(listView->filter())->setDeleteAction(act); } if (!treeView->filter() || !qobject_cast(treeView->filter())) { treeView->installEventFilter(new KeyEventHandler(treeView, act)); } else { static_cast(treeView->filter())->setDeleteAction(act); } if (groupedView) { if (!groupedView->filter() || !qobject_cast(groupedView->filter())) { groupedView->installEventFilter(new KeyEventHandler(groupedView, act)); } else { static_cast(groupedView->filter())->setDeleteAction(act); } } if (tableView) { if (!tableView->filter() || !qobject_cast(tableView->filter())) { tableView->installEventFilter(new KeyEventHandler(tableView, act)); } else { static_cast(tableView->filter())->setDeleteAction(act); } } } void ItemView::showIndex(const QModelIndex &idx, bool scrollTo) { if (usingTreeView() || Mode_GroupedTree==mode || Mode_Table==mode) { TreeView *v=static_cast(view()); QModelIndex i=idx; while (i.isValid()) { v->setExpanded(i, true); i=i.parent(); } if (scrollTo) { v->scrollTo(idx, QAbstractItemView::PositionAtTop); } } else { if (idx.parent().isValid()) { QList indexes; QModelIndex i=idx.parent(); QModelIndex p=idx; while (i.isValid()) { indexes.prepend(i); i=i.parent(); } setLevel(0); listView->setRootIndex(QModelIndex()); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } foreach (const QModelIndex &i, indexes) { activateItem(i, false); } if (p.isValid()) { emit rootIndexSet(p); } if (scrollTo) { listView->scrollTo(idx, QAbstractItemView::PositionAtTop); } setTitle(); } } if (view()->selectionModel()) { view()->selectionModel()->select(idx, QItemSelectionModel::Select|QItemSelectionModel::Rows); } } void ItemView::focusSearch(const QString &text) { if (isEnabled()) { performedSearch=false; searchWidget->activate(searchWidget->text().isEmpty() ? text : QString()); } } void ItemView::focusView() { view()->setFocus(); } void ItemView::setSearchVisible(bool v) { searchWidget->setVisible(v); } bool ItemView::isSearchActive() const { return searchWidget->isActive(); } void ItemView::closeSearch() { if (searchWidget->isActive()) { searchWidget->close(); } } void ItemView::setStartClosed(bool sc) { if (groupedView) { groupedView->setStartClosed(sc); } } bool ItemView::isStartClosed() { return groupedView ? groupedView->isStartClosed() : false; } void ItemView::updateRows() { if (groupedView) { groupedView->updateCollectionRows(); } } void ItemView::updateRows(const QModelIndex &idx) { if (groupedView) { groupedView->updateRows(idx); } } void ItemView::expandAll(const QModelIndex &index) { if (usingTreeView()) { treeView->expandAll(index); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->expandAll(index); } else if (Mode_Table==mode && tableView) { tableView->expandAll(index); } } void ItemView::expand(const QModelIndex &index, bool singleOnly) { if (usingTreeView()) { treeView->expand(index, singleOnly); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->expand(index, singleOnly); } else if (Mode_Table==mode && tableView) { tableView->expand(index, singleOnly); } } void ItemView::showMessage(const QString &message, int timeout) { if (!msgOverlay) { msgOverlay=new MessageOverlay(this); msgOverlay->setWidget(view()); } msgOverlay->setText(message, timeout, false); } void ItemView::setBackgroundImage(const QIcon &icon) { bgndIcon=icon; if (usingTreeView()) { treeView->setBackgroundImage(bgndIcon); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->setBackgroundImage(bgndIcon); } else if (Mode_Table==mode && tableView) { tableView->setBackgroundImage(bgndIcon); } else if (Mode_List==mode || Mode_IconTop==mode) { listView->setBackgroundImage(bgndIcon); } } bool ItemView::isAnimated() const { if (usingTreeView()) { return treeView->isAnimated(); } if (Mode_GroupedTree==mode && groupedView) { return groupedView->isAnimated(); } if (Mode_Table==mode && tableView) { return tableView->isAnimated(); } return false; } void ItemView::setAnimated(bool a) { if (usingTreeView()) { treeView->setAnimated(a); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->setAnimated(a); } else if (Mode_Table==mode && tableView) { tableView->setAnimated(a); } } void ItemView::setPermanentSearch() { searchWidget->setPermanent(); } void ItemView::hideSearch() { if (searchVisible()) { searchWidget->close(); } } void ItemView::setSearchCategories(const QList &categories) { searchWidget->setCategories(categories); } void ItemView::setSearchCategory(const QString &id) { searchWidget->setCategory(id); } void ItemView::showSpinner(bool v) { if (v) { if (!spinner) { spinner=new Spinner(this); } spinner->setWidget(view()); spinner->start(); } else { hideSpinner(); } } void ItemView::hideSpinner() { if (spinner) { spinner->stop(); } } void ItemView::updating() { showSpinner(); showMessage(tr("Updating..."), -1); } void ItemView::updated() { hideSpinner(); showMessage(QString(), 0); } void ItemView::collectionRemoved(quint32 key) { if (groupedView) { groupedView->collectionRemoved(key); } } void ItemView::backActivated() { if (!isVisible()) { return; } emit headerClicked(currentLevel); if (!usingListView() || 0==currentLevel) { return; } setLevel(currentLevel-1); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(listView->rootIndex().parent()); } listView->setRootIndex(listView->rootIndex().parent()); emit rootIndexSet(listView->rootIndex().parent()); setTitle(); if (prevTopIndex.isEmpty()) return; QModelIndex prevTop = prevTopIndex.takeLast(); if (qobject_cast(listView->model())) { QModelIndex idx=static_cast(listView->model())->mapFromSource(prevTop); if (idx.isValid()) { listView->scrollTo(idx, QAbstractItemView::PositionAtTop); } } else { listView->scrollTo(prevTop, QAbstractItemView::PositionAtTop); } } void ItemView::setExpanded(const QModelIndex &idx, bool exp) { if (usingTreeView()) { treeView->setExpanded(idx, exp); } } QAction * ItemView::getAction(const QModelIndex &index) { QAbstractItemDelegate *abs=view()->itemDelegate(); ActionItemDelegate *d=abs ? qobject_cast(abs) : 0; return d ? d->getAction(index) : 0; } void ItemView::itemClicked(const QModelIndex &index) { QAction *act=getAction(index); if (act) { act->trigger(); return; } if (TreeView::getForceSingleClick()) { activateItem(index); } } void ItemView::itemActivated(const QModelIndex &index) { if (!TreeView::getForceSingleClick()) { activateItem(index); } } void ItemView::activateItem(const QModelIndex &index, bool emitRootSet) { if (getAction(index)) { return; } if (usingTreeView()) { treeView->setExpanded(index, !treeView->isExpanded(index)); } else if (Mode_GroupedTree==mode) { if (!index.parent().isValid()) { groupedView->setExpanded(index, !groupedView->TreeView::isExpanded(index)); } } else if (Mode_Table==mode) { if (!index.parent().isValid()) { tableView->setExpanded(index, !tableView->TreeView::isExpanded(index)); } } else if (index.isValid() && (index.child(0, 0).isValid() || itemModel->canFetchMore(index)) && index!=listView->rootIndex()) { if (itemModel->canFetchMore(index)) { itemModel->fetchMore(index); } QModelIndex fistChild=index.child(0, 0); if (!fistChild.isValid()) { return; } QModelIndex curTop=listView->indexAt(QPoint(8, 8)); if (qobject_cast(listView->model())) { curTop=static_cast(listView->model())->mapToSource(curTop); } prevTopIndex.append(curTop); setLevel(currentLevel+1, itemModel->canFetchMore(fistChild) || fistChild.child(0, 0).isValid()); listView->setRootIndex(index); setTitle(); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(index); } if (emitRootSet) { emit rootIndexSet(index); } listView->scrollToTop(); } } void ItemView::modelReset() { if (Mode_List==mode || Mode_IconTop==mode) { goToTop(); } else if (usingTreeView() && !searchText().isEmpty()) { for (int r=0; rrowCount(); ++r) { treeView->expand(itemModel->index(r, 0, QModelIndex())); } } } void ItemView::dataChanged(const QModelIndex &tl, const QModelIndex &br) { if (!tl.isValid() && !br.isValid()) { setTitle(); } } void ItemView::addTitleButtonClicked() { if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) { emit updateToPlayQueue(view()->rootIndex(), false); } } void ItemView::replaceTitleButtonClicked() { if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) { emit updateToPlayQueue(view()->rootIndex(), true); } } void ItemView::coverLoaded(const Song &song, int size) { Q_UNUSED(song) if (Mode_BasicTree==mode || Mode_GroupedTree==mode || !isVisible() || (Mode_IconTop==mode && size!=gridCoverSize) || (Mode_IconTop!=mode && size!=listCoverSize)) { return; } view()->viewport()->update(); } void ItemView::delaySearchItems() { if (searchWidget->text().isEmpty()) { if (searchTimer) { searchTimer->stop(); } if (performedSearch) { performedSearch=false; } emit searchItems(); } else { if (!searchTimer) { searchTimer=new QTimer(this); searchTimer->setSingleShot(true); connect(searchTimer, SIGNAL(timeout()), this, SLOT(doSearch())); } int len=searchWidget->text().trimmed().length(); searchTimer->start(len<2 ? 1000 : len<4 ? 750 : 500); } } void ItemView::doSearch() { performedSearch=true; emit searchItems(); } void ItemView::searchActive(bool a) { emit searchIsActive(a); if (!a && performedSearch) { performedSearch=false; } if (!a && view()->isVisible()) { view()->setFocus(); } controlViewFrame(); } void ItemView::setTitle() { QModelIndex index=view()->rootIndex(); QAbstractItemModel *model=view()->model(); if (!model) { return; } title->update(model->data(index, Mode_IconTop==mode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value(), model->data(index, Qt::DecorationRole).value(), model->data(index, Cantata::Role_TitleText).toString(), model->data(index, Cantata::Role_SubText).toString(), model->data(index, Cantata::Role_TitleActions).toBool()); } void ItemView::controlViewFrame() { view()->setProperty(ProxyStyle::constModifyFrameProp, title->isVisible() || title->property(constAlwaysShowProp).toBool() ? ProxyStyle::VF_Side : (searchWidget->isActive() ? ProxyStyle::VF_Side : (ProxyStyle::VF_Side|ProxyStyle::VF_Top))); } cantata-2.2.0/widgets/itemview.h000066400000000000000000000143411316350454000166020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ITEMVIEW_H #define ITEMVIEW_H #include "config.h" #include "ui_itemview.h" #include "treeview.h" #include #include #include class Spinner; class QTimer; class GroupedView; class ActionItemDelegate; class MessageOverlay; class Icon; class TableView; class QMenu; class Configuration; class KeyEventHandler : public QObject { Q_OBJECT public: KeyEventHandler(QAbstractItemView *v, QAction *a=0); void setDeleteAction(QAction *a) { deleteAct=a; } Q_SIGNALS: void backspacePressed(); protected: bool eventFilter(QObject *obj, QEvent *event); protected: QAbstractItemView *view; QAction *deleteAct; bool interceptBackspace; }; class ViewEventHandler : public KeyEventHandler { public: ViewEventHandler(ActionItemDelegate *d, QAbstractItemView *v); protected: bool eventFilter(QObject *obj, QEvent *event); private: ActionItemDelegate *delegate; }; class ItemView : public QWidget, public Ui::ItemView { Q_OBJECT public: enum Mode { Mode_BasicTree, Mode_SimpleTree, Mode_DetailedTree, Mode_GroupedTree, Mode_Table, Mode_List, Mode_IconTop, Mode_Count }; static Mode toMode(const QString &str); static QString modeStr(Mode m); static void setup(); static const QLatin1String constSearchActiveKey; static const QLatin1String constViewModeKey; static const QLatin1String constStartClosedKey; static const QLatin1String constSearchCategoryKey; ItemView(QWidget *p=0); virtual ~ItemView(); void alwaysShowHeader(); void load(Configuration &config); void save(Configuration &config); void allowGroupedView(); void allowTableView(TableView *v); void addAction(QAction *act); void addSeparator(); void setMode(Mode m); Mode viewMode() const { return mode; } QAbstractItemView * view() const; void setModel(QAbstractItemModel *m); void clearSelection() { view()->selectionModel()->clearSelection(); } void setCurrentIndex(const QModelIndex &idx) { view()->setCurrentIndex(idx); } void select(const QModelIndex &idx); QModelIndexList selectedIndexes(bool sorted=true) const; bool searchVisible() const; QString searchText() const; QString searchCategory() const; void clearSearchText(); void setUniformRowHeights(bool v); void setAcceptDrops(bool v); void setDragDropOverwriteMode(bool v); void setDragDropMode(QAbstractItemView::DragDropMode v); void setGridSize(const QSize &sz); QSize gridSize() const { return listView->gridSize(); } void update(); void setDeleteAction(QAction *act); void setRootIsDecorated(bool v) { treeView->setRootIsDecorated(v); } void showIndex(const QModelIndex &idx, bool scrollTo); void setSearchVisible(bool v); bool isSearchActive() const; void setStartClosed(bool sc); bool isStartClosed(); void expandAll(const QModelIndex &index=QModelIndex()); void expand(const QModelIndex &index, bool singleOnly=false); void showMessage(const QString &message, int timeout); void setBackgroundImage(const QIcon &icon); bool isAnimated() const; void setAnimated(bool a); void setPermanentSearch(); void hideSearch(); void setSearchCategories(const QList &categories); void setSearchCategory(const QString &id); void setSearchResetLevel(int l) { searchResetLevel=l; } void goToTop(); void setOpenAfterSearch(bool o) { openFirstLevelAfterSearch=o; } void setEnabled(bool en); private: void setLevel(int level, bool haveChildren=true); bool usingTreeView() const { return mode<=Mode_DetailedTree; } bool usingListView() const { return mode>=Mode_List; } public Q_SLOTS: void focusSearch(const QString &text=QString()); void focusView(); void showSpinner(bool v=true); void hideSpinner(); void updating(); void updated(); void collectionRemoved(quint32 key); void updateRows(); void updateRows(const QModelIndex &idx); void backActivated(); void setExpanded(const QModelIndex &idx, bool exp=true); void closeSearch(); Q_SIGNALS: void searchItems(); void searchIsActive(bool); void itemsSelected(bool); void doubleClicked(const QModelIndex &); void rootIndexSet(const QModelIndex &); void headerClicked(int level); void updateToPlayQueue(const QModelIndex &idx, bool replace); private Q_SLOTS: void itemClicked(const QModelIndex &index); void itemActivated(const QModelIndex &index); void delaySearchItems(); void doSearch(); void searchActive(bool a); void activateItem(const QModelIndex &index, bool emitRootSet=true); void modelReset(); void dataChanged(const QModelIndex &tl, const QModelIndex &br); void addTitleButtonClicked(); void replaceTitleButtonClicked(); void coverLoaded(const Song &song, int size); private: QAction * getAction(const QModelIndex &index); void setTitle(); void controlViewFrame(); private: QTimer *searchTimer; QAbstractItemModel *itemModel; int currentLevel; Mode mode; QModelIndexList prevTopIndex; QSize iconGridSize; QSize listGridSize; GroupedView *groupedView; TableView *tableView; Spinner *spinner; MessageOverlay *msgOverlay; QIcon bgndIcon; bool performedSearch; int searchResetLevel; bool openFirstLevelAfterSearch; bool initialised; }; #endif cantata-2.2.0/widgets/itemview.ui000066400000000000000000000042361316350454000167720ustar00rootroot00000000000000 ItemView 0 0 261 198 0 0 0 0 0 0 TitleWidget QWidget
    widgets/titlewidget.h
    PaddedSqueezedTextLabel QLabel
    support/squeezedtextlabel.h
    SearchWidget QLineEdit
    widgets/searchwidget.h
    TreeView QTreeView
    widgets/treeview.h
    ListView QListView
    widgets/listview.h
    cantata-2.2.0/widgets/listview.cpp000066400000000000000000000113041316350454000171460ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "listview.h" #include "itemview.h" #include "treeview.h" #include "config.h" #include "icons.h" #include "support/utils.h" #include #include #include #include #include #include #include ListView::ListView(QWidget *parent) : QListView(parent) , eventFilter(0) , menu(0) { setDragEnabled(true); setContextMenuPolicy(Qt::NoContextMenu); setDragDropMode(QAbstractItemView::DragOnly); setSelectionMode(QAbstractItemView::ExtendedSelection); setAlternatingRowColors(false); setUniformItemSizes(true); setAttribute(Qt::WA_MouseTracking); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(showCustomContextMenu(const QPoint &))); connect(this, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(checkDoubleClick(const QModelIndex &))); } ListView::~ListView() { } void ListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QListView::selectionChanged(selected, deselected); bool haveSelection=haveSelectedItems(); setContextMenuPolicy(haveSelection ? Qt::ActionsContextMenu : (menu ? Qt::CustomContextMenu : Qt::NoContextMenu)); emit itemsSelected(haveSelection); } bool ListView::haveSelectedItems() const { // Dont need the sorted type of 'selectedIndexes' here... return selectionModel() && selectionModel()->selectedIndexes().count()>0; } bool ListView::haveUnSelectedItems() const { // Dont need the sorted type of 'selectedIndexes' here... return selectionModel() && model() && selectionModel()->selectedIndexes().count()!=model()->rowCount(); } void ListView::mouseReleaseEvent(QMouseEvent *event) { if (Qt::NoModifier==event->modifiers() && Qt::LeftButton==event->button()) { QListView::mouseReleaseEvent(event); } } QModelIndexList ListView::selectedIndexes(bool sorted) const { QModelIndexList indexes=selectionModel() ? selectionModel()->selectedIndexes() : QModelIndexList(); if (sorted) { qSort(indexes); } return indexes; } void ListView::setModel(QAbstractItemModel *m) { QAbstractItemModel *old=model(); QListView::setModel(m); if (old) { disconnect(old, SIGNAL(layoutChanged()), this, SLOT(correctSelection())); } if (m && old!=m) { connect(m, SIGNAL(layoutChanged()), this, SLOT(correctSelection())); } } void ListView::addDefaultAction(QAction *act) { if (!menu) { menu=new QMenu(this); } menu->addAction(act); } void ListView::setBackgroundImage(const QIcon &icon) { QPalette pal=parentWidget()->palette(); if (!icon.isNull()) { pal.setColor(QPalette::Base, Qt::transparent); } #ifndef Q_OS_MAC setPalette(pal); #endif viewport()->setPalette(pal); bgnd=TreeView::createBgndPixmap(icon); } void ListView::paintEvent(QPaintEvent *e) { if (!bgnd.isNull()) { QPainter p(viewport()); QSize sz=size(); p.fillRect(0, 0, sz.width(), sz.height(), QApplication::palette().color(QPalette::Base)); p.drawPixmap((sz.width()-bgnd.width())/2, (sz.height()-bgnd.height())/2, bgnd); } QListView::paintEvent(e); } // Workaround for https://bugreports.qt-project.org/browse/QTBUG-18009 void ListView::correctSelection() { if (!selectionModel()) { return; } QItemSelection s = selectionModel()->selection(); setCurrentIndex(currentIndex()); selectionModel()->select(s, QItemSelectionModel::SelectCurrent); } void ListView::showCustomContextMenu(const QPoint &pos) { if (menu) { menu->popup(mapToGlobal(pos)); } } void ListView::checkDoubleClick(const QModelIndex &idx) { if (!TreeView::getForceSingleClick() && idx.model() && idx.model()->rowCount(idx)) { return; } emit itemDoubleClicked(idx); } cantata-2.2.0/widgets/listview.h000066400000000000000000000042211316350454000166130ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef LISTVIEW_H #define LISTVIEW_H #include #include #include "treeview.h" class QIcon; class QMenu; class ListView : public QListView { Q_OBJECT public: ListView(QWidget *parent=0); virtual ~ListView(); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); bool haveSelectedItems() const; bool haveUnSelectedItems() const; void startDrag(Qt::DropActions supportedActions) { TreeView::drag(supportedActions, this, selectedIndexes()); } void mouseReleaseEvent(QMouseEvent *event); QModelIndexList selectedIndexes() const { return selectedIndexes(true); } QModelIndexList selectedIndexes(bool sorted) const; virtual void setModel(QAbstractItemModel *m); void addDefaultAction(QAction *act); void setBackgroundImage(const QIcon &icon); void paintEvent(QPaintEvent *e); void installFilter(QObject *f) { eventFilter=f; installEventFilter(f); } QObject * filter() const { return eventFilter; } private Q_SLOTS: void correctSelection(); void showCustomContextMenu(const QPoint &pos); void checkDoubleClick(const QModelIndex &idx); Q_SIGNALS: bool itemsSelected(bool); void itemDoubleClicked(const QModelIndex &idx); private: QObject *eventFilter; QMenu *menu; QPixmap bgnd; }; #endif cantata-2.2.0/widgets/menubutton.cpp000066400000000000000000000070501316350454000175030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "menubutton.h" #include "support/icon.h" #include "icons.h" #include #include #include #include #include #include MenuButton::MenuButton(QWidget *parent) : ToolButton(parent) { setPopupMode(QToolButton::InstantPopup); setIcon(Icons::self()->menuIcon); setToolTip(tr("Menu")); installEventFilter(this); } void MenuButton::controlState() { if (!menu()) { return; } foreach (QAction *a, menu()->actions()) { if (a->isEnabled() && a->isVisible() && !a->isSeparator()) { setEnabled(true); return; } } setEnabled(false); } void MenuButton::setAlignedMenu(QMenu *m) { QToolButton::setMenu(m); m->installEventFilter(this); } void MenuButton::addSeparator() { QAction *sep=new QAction(this); sep->setSeparator(true); addAction(sep); } bool MenuButton::eventFilter(QObject *o, QEvent *e) { if (QEvent::Show==e->type()) { if (qobject_cast(o)) { static int shadowAdjust = -1; if (-1==shadowAdjust) { // Kvantum style adds its own shadows, which makes the menu appear in the wrong place. // However, Kvatum sill set a property on the style object to indicate the size of // the shadows. Use this to adjust positioning. QProxyStyle *proxy=qobject_cast(style()); QStyle *check=proxy && proxy->baseStyle() ? proxy->baseStyle() : style(); QList shadows = check ? check->property("menu_shadow").value >() : QList(); shadowAdjust = 4 == shadows.length() ? shadows.at(2) : 0; if (shadowAdjust<0 || shadowAdjust>18) { shadowAdjust = 0; } } QMenu *mnu=static_cast(o); QPoint p=parentWidget()->mapToGlobal(pos()); int newPos=isRightToLeft() ? p.x() : ((p.x()+width()+shadowAdjust)-mnu->width()); if (newPos<0) { newPos=0; } else { QDesktopWidget *dw=QApplication::desktop(); if (dw) { QRect geo=dw->availableGeometry(this); int maxWidth=geo.x()+geo.width(); if (maxWidth>0 && (newPos+mnu->width())>maxWidth) { newPos=maxWidth-mnu->width(); } } } mnu->move(newPos, mnu->y()); } else if (o==this) { setMinimumWidth(height()); removeEventFilter(this); } } return ToolButton::eventFilter(o, e); } cantata-2.2.0/widgets/menubutton.h000066400000000000000000000022501316350454000171450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MENUBUTTON_H #define MENUBUTTON_H #include "toolbutton.h" class MenuButton : public ToolButton { Q_OBJECT public: explicit MenuButton(QWidget *parent = 0); void controlState(); void setAlignedMenu(QMenu *m); void addSeparator(); private: bool eventFilter(QObject *o, QEvent *e); }; #endif // MENUBUTTON_H cantata-2.2.0/widgets/messageoverlay.cpp000066400000000000000000000110361316350454000203300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "messageoverlay.h" #include "toolbutton.h" #include "support/monoicon.h" #include "support/utils.h" #include #include #include #include MessageOverlay::MessageOverlay(QObject *p) : QWidget(0) , timer(0) #ifdef Q_OS_MAC , closeOnLeft(true) #else , closeOnLeft(Utils::Unity==Utils::currentDe()) #endif { Q_UNUSED(p) spacing=fontMetrics().height(); setVisible(false); setMinimumHeight(spacing*2); setMaximumHeight(spacing*2); cancelButton=new ToolButton(this); Icon::init(cancelButton); cancelButton->setToolTip(tr("Cancel")); cancelButton->setIcon(MonoIcon::icon(FontAwesome::close, MonoIcon::constRed, MonoIcon::constRed)); cancelButton->adjustSize(); connect(cancelButton, SIGNAL(clicked()), SIGNAL(cancel())); } void MessageOverlay::setWidget(QWidget *widget) { setParent(widget); widget->installEventFilter(this); } void MessageOverlay::setText(const QString &txt, int timeout, bool allowCancel) { if (txt==text) { return; } text=txt; cancelButton->setVisible(allowCancel); setAttribute(Qt::WA_TransparentForMouseEvents, !allowCancel); setVisible(!text.isEmpty()); if (!text.isEmpty()) { setSizeAndPosition(); update(); if (-1!=timeout) { if (!timer) { timer=new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); } timer->start(timeout); } } } void MessageOverlay::paintEvent(QPaintEvent *) { QPainter p(this); QRect r(rect()); QRectF rf(r.x()+0.5, r.y()+0.5, r.width()-1, r.height()-1); QColor borderCol=palette().color(QPalette::Highlight).darker(120); QColor col=palette().color(QPalette::Window); QPainterPath path=Utils::buildPath(rf, r.height()/4.0); col.setAlphaF(0.8); p.setRenderHint(QPainter::Antialiasing, true); p.fillPath(path, col); p.setPen(QPen(borderCol, qMax(2, r.height()/16))); p.drawPath(path); int pad=r.height()/4; if (cancelButton->isVisible()) { if (Qt::LeftToRight==layoutDirection() && !closeOnLeft) { rf.adjust(pad, pad, -((pad*2)+cancelButton->width()), -pad); } else { rf.adjust(((pad*2)+cancelButton->width()), pad, -pad, -pad); } } else { rf.adjust(pad, pad, -pad, -pad); } QFont fnt(QApplication::font()); fnt.setBold(true); QFontMetrics fm(fnt); col=palette().color(QPalette::WindowText); p.setPen(col); p.setFont(fnt); p.drawText(rf, fm.elidedText(text, Qt::ElideRight, r.width(), QPalette::WindowText), QTextOption(Qt::LeftToRight==layoutDirection() ? Qt::AlignLeft : Qt::AlignRight)); p.end(); } void MessageOverlay::timeout() { setVisible(false); text=QString(); } bool MessageOverlay::eventFilter(QObject *o, QEvent *e) { if (o==parentWidget() && isVisible() && QEvent::Resize==e->type()) { setSizeAndPosition(); } return QObject::eventFilter(o, e); } void MessageOverlay::setSizeAndPosition() { int currentWidth=width(); int desiredWidth=parentWidget()->width()-(spacing*2); QPoint currentPos=pos(); QPoint desiredPos=QPoint(spacing, parentWidget()->height()-(height()+spacing)); if (currentWidth!=desiredWidth) { int pad=height()/4; resize(desiredWidth, height()); cancelButton->move(QPoint(Qt::LeftToRight==layoutDirection() && !closeOnLeft ? desiredWidth-(cancelButton->width()+pad) : pad, (height()-cancelButton->height())/2)); } if (currentPos!=desiredPos) { move(desiredPos); } } cantata-2.2.0/widgets/messageoverlay.h000066400000000000000000000027571316350454000200070ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MESSAGE_OVERLAY_H #define MESSAGE_OVERLAY_H #include class ToolButton; class QTimer; class MessageOverlay : public QWidget { Q_OBJECT public: MessageOverlay(QObject *p); virtual ~MessageOverlay() { } void setWidget(QWidget *widget); void setText(const QString &txt, int timeout=-1, bool allowCancel=true); void paintEvent(QPaintEvent *); Q_SIGNALS: void cancel(); private Q_SLOTS: void timeout(); private: bool eventFilter(QObject *o, QEvent *e); void setSizeAndPosition(); private: int spacing; QString text; ToolButton *cancelButton; QTimer *timer; bool closeOnLeft; }; #endif cantata-2.2.0/widgets/mirrormenu.cpp000066400000000000000000000050061316350454000175010ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mirrormenu.h" #include MirrorMenu::MirrorMenu(QWidget *p) : QMenu(p) { } void MirrorMenu::addAction(QAction *act) { QMenu::addAction(act); updateMenus(); } QAction * MirrorMenu::addAction(const QString &text) { QAction *act=QMenu::addAction(text); updateMenus(); return act; } QAction * MirrorMenu::addAction(const QIcon &icon, const QString &text) { QAction *act=QMenu::addAction(icon, text); updateMenus(); return act; } QAction * MirrorMenu::addAction(const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut) { QAction *act=QMenu::addAction(text, receiver, member, shortcut); updateMenus(); return act; } QAction * MirrorMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut) { QAction *act=QMenu::addAction(icon, text, receiver, member, shortcut); updateMenus(); return act; } void MirrorMenu::removeAction(QAction *act) { QMenu::removeAction(act); updateMenus(); } void MirrorMenu::clear() { QMenu::clear(); updateMenus(); } QMenu * MirrorMenu::duplicate(QWidget *p) { QMenu *menu=new QMenu(p); menus.append(menu); updateMenu(menu); connect(menu, SIGNAL(destroyed(QObject*)), this, SLOT(menuDestroyed(QObject*))); return menu; } void MirrorMenu::updateMenus() { foreach (QMenu *m, menus) { updateMenu(m); } } void MirrorMenu::updateMenu(QMenu *menu) { menu->clear(); menu->addActions(actions()); } void MirrorMenu::menuDestroyed(QObject *obj) { if (qobject_cast(obj)) { menus.removeAll(static_cast(obj)); } } cantata-2.2.0/widgets/mirrormenu.h000066400000000000000000000032321316350454000171450ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MIRROR_MENU_H #define MIRROR_MENU_H #include #include class MirrorMenu : public QMenu { Q_OBJECT public: MirrorMenu(QWidget *p); void addAction(QAction *act); QAction * addAction(const QString &text); QAction * addAction(const QIcon &icon, const QString &text); QAction * addAction(const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut = 0); QAction * addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut = 0); void removeAction(QAction *act); void clear(); QMenu * duplicate(QWidget *p); private: void updateMenus(); void updateMenu(QMenu *menu); private Q_SLOTS: void menuDestroyed(QObject *obj); private: QList menus; }; #endif cantata-2.2.0/widgets/multipagewidget.cpp000066400000000000000000000217031316350454000204770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "multipagewidget.h" #include "support/icon.h" #include "support/utils.h" #include "support/squeezedtextlabel.h" #include "support/proxystyle.h" #include "support/configuration.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include "listview.h" #include "sizewidget.h" #include "singlepagewidget.h" #include "toolbutton.h" #include #include #include #include #include class SelectorButton : public ToolButton { public: SelectorButton(const QString &t, const QString &s, const Icon &icn, QWidget *p) : ToolButton(p) { QGridLayout *layout=new QGridLayout(this); icon=new QLabel(this); mainText=new SqueezedTextLabel(this); subText=new SqueezedTextLabel(this); subText->setFont(Utils::smallFont(mainText->font())); layout->setSpacing(2); int textSize=mainText->sizeHint().height()+subText->sizeHint().height()+layout->spacing(); textSize+=6; int size=textSize; if (size<72) { size=Utils::scaleForDpi(32); } QPalette pal=mainText->palette(); QColor col(mainText->palette().windowText().color()); col.setAlphaF(0.5); pal.setColor(QPalette::ButtonText, col); subText->setPalette(pal); icon->setFixedSize(size, size); layout->addWidget(icon, 0, 0, 2, 1); layout->addItem(new QSpacerItem(Utils::layoutSpacing(this), 2, QSizePolicy::Fixed, QSizePolicy::Fixed), 0, 1); layout->addWidget(mainText, 0, 2, 1, 1); layout->addWidget(subText, 1, 2, 1, 1); mainText->setAlignment(Qt::AlignBottom); subText->setAlignment(Qt::AlignTop); icon->setAlignment(Qt::AlignCenter); double dpr=DEVICE_PIXEL_RATIO(); QPixmap pix=icn.getScaledPixmap(icon->width()*dpr, icon->height()*dpr, 96*dpr); pix.setDevicePixelRatio(dpr); icon->setPixmap(pix); setAutoRaise(true); setLayout(layout); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); mainText->setText(t); subText->setText(s); layout->setMargin(qMin(layout->margin(), 8)); setMinimumHeight(qMax(textSize, size)+(layout->margin()*2)); updateToolTip(); setFocusPolicy(Qt::TabFocus); } void setSubText(const QString &str) { subText->setText(str); updateToolTip(); } void updateToolTip() { setToolTip(""+mainText->fullText()+"
    "+subText->fullText()); } void paintEvent(QPaintEvent *e) { QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); if (opt.state&QStyle::State_Sunken || opt.state&QStyle::State_MouseOver) { #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #else QColor col = qApp->palette().highlight().color(); #endif col.setAlphaF(opt.state&QStyle::State_Sunken ? 0.5 : 0.2); p.fillRect(opt.rect, col); } opt.state&=~(QStyle::State_Sunken|QStyle::State_MouseOver|QStyle::State_Raised|QStyle::State_On); opt.state|=QStyle::State_AutoRaise; p.drawComplexControl(QStyle::CC_ToolButton, opt); } private: SqueezedTextLabel *mainText; SqueezedTextLabel *subText; QLabel *icon; }; MultiPageWidget::MultiPageWidget(QWidget *p) : StackedPageWidget(p) { mainPage=new QWidget(this); QVBoxLayout *mainLayout=new QVBoxLayout(mainPage); infoLabel=new QLabel(mainPage); sizer=new SizeWidget(mainPage); QScrollArea *scroll = new QScrollArea(this); view = new QWidget(scroll); QVBoxLayout *layout = new QVBoxLayout(view); scroll->setWidget(view); scroll->setWidgetResizable(true); view->setBackgroundRole(QPalette::Base); layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); view->setLayout(layout); scroll->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); mainPage->setLayout(mainLayout); mainLayout->addWidget(scroll); mainLayout->setMargin(0); mainLayout->setSpacing(0); mainLayout->addWidget(infoLabel); infoLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mainLayout->addWidget(sizer); layout->setSpacing(0); layout->setSizeConstraint(QLayout::SetMinimumSize); layout->setMargin(qMin(layout->margin(), 4)); #ifdef Q_OS_MAC // TODO: This feels a bt of a hack... mainPage->setContentsMargins(-3, 0, -3, 0); #endif addWidget(mainPage); } MultiPageWidget::~MultiPageWidget() { } static const char *constCurrentPageKey="currentPage"; void MultiPageWidget::load(Configuration &config) { QString p=config.get(constCurrentPageKey, QString()); if (!p.isEmpty()) { QMap::ConstIterator it=entries.find(p); if (it!=entries.constEnd()) { setCurrentWidget(it.value().page); } } } void MultiPageWidget::save(Configuration &config) const { QString p; QWidget *cw=currentWidget(); QMap::ConstIterator it=entries.constBegin(); QMap::ConstIterator end=entries.constEnd(); for (; it!=end; ++it) { if (it.value().page==cw) { p=it.key(); break; } } config.set(constCurrentPageKey, p); } void MultiPageWidget::setInfoText(const QString &text) { infoLabel->setText(text); } void MultiPageWidget::addPage(const QString &name, const QString &icon, const QString &text, const QString &subText, QWidget *widget) { Icon i; i.addFile(":"+icon); addPage(name, i, text, subText, widget); } void MultiPageWidget::addPage(const QString &name, const Icon &icon, const QString &text, const QString &subText, QWidget *widget) { if (entries.contains(name)) { return; } Entry e(new SelectorButton(text, subText, icon, view), widget); static_cast(view->layout())->insertWidget(view->layout()->count()-1, e.btn); addWidget(widget); entries.insert(name, e); connect(e.btn, SIGNAL(clicked()), SLOT(setPage())); infoLabel->setVisible(false); if (qobject_cast(widget)) { connect(static_cast(widget), SIGNAL(close()), this, SLOT(showMainView())); } else if (qobject_cast(widget)) { connect(static_cast(widget), SIGNAL(close()), this, SLOT(showMainView())); } } void MultiPageWidget::removePage(const QString &name) { QMap::Iterator it=entries.find(name); if (it==entries.end()) { return; } if (it.value().page==currentWidget()) { setCurrentWidget(mainPage); } static_cast(view->layout())->removeWidget(it.value().btn); it.value().btn->deleteLater(); entries.erase(it); } void MultiPageWidget::updatePageSubText(const QString &name, const QString &text) { QMap::Iterator it=entries.find(name); if (it==entries.end()) { return; } it.value().btn->setSubText(text); } void MultiPageWidget::showMainView() { setCurrentWidget(mainPage); } void MultiPageWidget::setPage() { QToolButton *btn=qobject_cast(sender()); if (!btn) { return; } QMap::ConstIterator it=entries.constBegin(); QMap::ConstIterator end=entries.constEnd(); for (; it!=end; ++it) { if (it.value().btn==btn) { setCurrentWidget(it.value().page); return; } } } void MultiPageWidget::sortItems() { QList keys=entries.keys(); qSort(keys); infoLabel->setVisible(0==entries.count()); QVBoxLayout *layout=static_cast(view->layout()); QMap::ConstIterator it=entries.constBegin(); QMap::ConstIterator end=entries.constEnd(); for (; it!=end; ++it) { layout->removeWidget(it.value().btn); } foreach (const QString &key, keys) { layout->insertWidget(view->layout()->count()-1, entries[key].btn); } } cantata-2.2.0/widgets/multipagewidget.h000066400000000000000000000042041316350454000201410ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MULTI_PAGE_WIDGET_H #define MULTI_PAGE_WIDGET_H #include "stackedpagewidget.h" #include "mpd-interface/song.h" #include "gui/page.h" #include class Icon; class SelectorButton; class SizeWidget; class QLabel; class Configuration; class MultiPageWidget : public StackedPageWidget { Q_OBJECT struct Entry { Entry(SelectorButton *b=0, QWidget *p=0) : btn(b), page(p) { } SelectorButton *btn; QWidget *page; }; public: MultiPageWidget(QWidget *p); virtual ~MultiPageWidget(); void load(Configuration &config); void save(Configuration &config) const; void setInfoText(const QString &text); void addPage(const QString &name, const QString &icon, const QString &text, const QString &subText, QWidget *widget); void addPage(const QString &name, const Icon &icon, const QString &text, const QString &subText, QWidget *widget); void removePage(const QString &name); void sortItems(); bool onMainPage() const { return mainPage==currentWidget(); } public Q_SLOTS: void updatePageSubText(const QString &name, const QString &text); void showMainView(); private Q_SLOTS: void setPage(); private: QWidget *mainPage; QWidget *view; QLabel *infoLabel; SizeWidget *sizer; QMap entries; }; #endif cantata-2.2.0/widgets/notelabel.cpp000066400000000000000000000047461316350454000172610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "notelabel.h" #include "support/utils.h" #include #include static void init(QLabel *label) { static const int constMinFontSize=9; label->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop); label->setWordWrap(true); label->setTextInteractionFlags(Qt::NoTextInteraction); if (label->font().pointSize()>constMinFontSize) { label->setFont(Utils::smallFont(label->font())); } } static QLabel * init(QWidget *p, bool url) { int layoutSpacing=Utils::layoutSpacing(p); int spacing=p->fontMetrics().height()*(Utils::limitedHeight(p) ? 0.25 : 1.0)-layoutSpacing; if (spacingsetMargin(0); l->setSpacing(0); QLabel *label; if (url) { label=new UrlLabel(p); } else { label=new StateLabel(p); } init(label); l->addItem(new QSpacerItem(2, spacing, QSizePolicy::Fixed, QSizePolicy::Fixed)); l->addWidget(label); return label; } void NoteLabel::setText(QLabel *l, const QString &text) { l->setText(tr("NOTE: %1").arg(text)); } NoteLabel::NoteLabel(QWidget *parent) : QWidget(parent) { label=static_cast(init(this, false)); } UrlNoteLabel::UrlNoteLabel(QWidget *parent) : QWidget(parent) { label=static_cast(init(this, true)); connect(label, SIGNAL(leftClickedUrl()), this, SIGNAL(leftClickedUrl())); } PlainNoteLabel::PlainNoteLabel(QWidget *parent) : StateLabel(parent) { init(this); } PlainUrlNoteLabel::PlainUrlNoteLabel(QWidget *parent) : UrlLabel(parent) { init(this); } cantata-2.2.0/widgets/notelabel.h000066400000000000000000000040771316350454000167230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NOTELABEL_H #define NOTELABEL_H #include #include "statelabel.h" #include "support/urllabel.h" class NoteLabel : public QWidget { Q_OBJECT public: static void setText(QLabel *l, const QString &text); NoteLabel(QWidget *parent=0); void setText(const QString &text) { setText(label, text); } void appendText(const QString &text) { label->setText(label->text()+text); } QString text() const { return label->text(); } void setOn(bool o) { label->setOn(o); } private: StateLabel *label; }; class UrlNoteLabel : public QWidget { Q_OBJECT public: UrlNoteLabel(QWidget *parent=0); void setText(const QString &text) { NoteLabel::setText(label, text); } void appendText(const QString &text) { label->setText(label->text()+text); } QString text() const { return label->text(); } Q_SIGNALS: void leftClickedUrl(); private: UrlLabel *label; }; class PlainNoteLabel : public StateLabel { public: PlainNoteLabel(QWidget *parent=0); void setText(const QString &text) { NoteLabel::setText(this, text); } }; class PlainUrlNoteLabel : public UrlLabel { public: PlainUrlNoteLabel(QWidget *parent=0); void setText(const QString &text) { NoteLabel::setText(this, text); } }; #endif cantata-2.2.0/widgets/nowplayingwidget.cpp000066400000000000000000000330141316350454000206750ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "nowplayingwidget.h" #include "ratingwidget.h" #include "volumeslider.h" #include "mpd-interface/song.h" #include "gui/settings.h" #include "mpd-interface/mpdconnection.h" #include "models/playqueuemodel.h" #include "support/utils.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include #include #include #include #include #include #include #include #include #include static const int constPollMpd = 2; // Poll every 2 seconds when playing class PosSliderProxyStyle : public QProxyStyle { public: PosSliderProxyStyle() : QProxyStyle() { setBaseStyle(qApp->style()); } int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const { if (QStyle::SH_Slider_AbsoluteSetButtons==stylehint) { return Qt::LeftButton|QProxyStyle::styleHint(stylehint, opt, widget, returnData); } else { return QProxyStyle::styleHint(stylehint, opt, widget, returnData); } } }; class TimeLabel : public QLabel { public: TimeLabel(QWidget *p, QSlider *s) : QLabel(p) , slider(s) , pressed(false) , showRemaining(Settings::self()->showTimeRemaining()) { setAttribute(Qt::WA_Hover, true); setAlignment((isRightToLeft() ? Qt::AlignLeft : Qt::AlignRight)|Qt::AlignVCenter); // For some reason setting this here does not work! // setStyleSheet(QLatin1String("QLabel:hover {color:palette(highlight);}")); } void setRange(int min, int max) { QLabel::setEnabled(min!=max); if (!isEnabled()) { setText(QLatin1String(" ")); } } void updateTime() { if (isEnabled()) { int value=showRemaining ? slider->maximum()-slider->value() : slider->maximum(); QString prefix=showRemaining && value ? QLatin1String("-") : QString(); if (isRightToLeft()) { setText(QString("%1 / %2").arg(prefix+Utils::formatTime(value), Utils::formatTime(slider->value()))); } else { setText(QString("%1 / %2").arg(Utils::formatTime(slider->value()), prefix+Utils::formatTime(value))); } } else { setText(QLatin1String(" ")); } } void saveConfig() { Settings::self()->saveShowTimeRemaining(showRemaining); } bool event(QEvent *e) { switch (e->type()) { case QEvent::MouseButtonPress: if (isEnabled() && Qt::NoModifier==static_cast(e)->modifiers() && Qt::LeftButton==static_cast(e)->button()) { pressed=true; } break; case QEvent::MouseButtonRelease: if (isEnabled() && pressed) { showRemaining=!showRemaining; updateTime(); } pressed=false; break; case QEvent::HoverEnter: if (isEnabled()) { #ifdef Q_OS_MAC setStyleSheet(QString("QLabel{color:%1;}").arg(OSXStyle::self()->viewPalette().highlight().color().name())); #else setStyleSheet(QLatin1String("QLabel{color:palette(highlight);}")); #endif } break; case QEvent::HoverLeave: if (isEnabled()) { setStyleSheet(QString("QLabel{color:%1;}").arg(((NowPlayingWidget *)parentWidget())->textColor().name())); } default: break; } return QLabel::event(e); } protected: QSlider *slider; bool pressed; bool showRemaining; }; PosSlider::PosSlider(QWidget *p) : QSlider(p) { setPageStep(0); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); setFocusPolicy(Qt::NoFocus); setStyle(new PosSliderProxyStyle()); int h=qMax((int)(fontMetrics().height()*0.5), 8); setMinimumHeight(h); setMaximumHeight(h); setMouseTracking(true); } void PosSlider::updateStyleSheet(const QColor &col) { int lineWidth=maximumHeight()>12 ? 2 : 1; QString boderFormat=QLatin1String("QSlider::groove:horizontal { border: %1px solid rgba(%2, %3, %4, %5); " "background: solid rgba(%2, %3, %4, %6); " "border-radius: %7px } "); QString fillFormat=QLatin1String("QSlider::")+QLatin1String(isRightToLeft() ? "add" : "sub")+ QLatin1String("-page:horizontal {border: %1px solid rgb(%3, %4, %5); " "background: solid rgb(%3, %4, %5); " "border-radius: %1px; margin: %2px} ")+ QLatin1String("QSlider::")+QLatin1String(isRightToLeft() ? "add" : "sub")+ QLatin1String("-page:horizontal:disabled {border: 0px; background: solid rgba(0, 0, 0, 0)}"); #ifdef Q_OS_MAC QColor fillColor=OSXStyle::self()->viewPalette().highlight().color(); #else QColor fillColor=qApp->palette().highlight().color(); #endif int alpha=col.value()<32 ? 96 : 64; setStyleSheet(boderFormat.arg(lineWidth).arg(col.red()).arg(col.green()).arg(col.blue()).arg(alpha) .arg(alpha/4).arg(lineWidth*2)+ fillFormat.arg(lineWidth).arg(lineWidth*2).arg(fillColor.red()).arg(fillColor.green()).arg(fillColor.blue())); } void PosSlider::mouseMoveEvent(QMouseEvent *e) { if (maximum()!=minimum()) { qreal pc = (qreal)e->pos().x()/(qreal)width(); QPoint pos(e->pos().x(), height()); QToolTip::showText(mapToGlobal(pos), Utils::formatTime(maximum()*pc), this, rect()); } QSlider::mouseMoveEvent(e); } void PosSlider::wheelEvent(QWheelEvent *ev) { if (!isEnabled()) { return; } static const int constStep=5; int numDegrees = ev->delta() / 8; int numSteps = numDegrees / 15; int val=value(); if (numSteps > 0) { int max=maximum(); if (val!=max) { for (int i = 0; i < numSteps; ++i) { val+=constStep; if (val>max) { val=max; break; } } } } else { int min=minimum(); if (val!=min) { for (int i = 0; i > numSteps; --i) { val-=constStep; if (valfont(); QFont small=Utils::smallFont(f); f.setBold(true); track->setFont(f); artist->setFont(small); time->setFont(small); slider->setOrientation(Qt::Horizontal); QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, this); QBoxLayout *topLayout=new QBoxLayout(QBoxLayout::LeftToRight, 0); QBoxLayout *botLayout=new QBoxLayout(QBoxLayout::LeftToRight, 0); int space=Utils::layoutSpacing(this); int pad=qMax(space, Utils::scaleForDpi(8)); #ifdef Q_OS_MAC layout->setContentsMargins(pad, 0, pad, 0); #else layout->setContentsMargins(pad, space, pad, space); #endif layout->setSpacing(space/2); topLayout->setMargin(0); botLayout->setMargin(0); topLayout->setSpacing(space/2); botLayout->setSpacing(space/2); topLayout->addWidget(track); topLayout->addWidget(ratingWidget); layout->addLayout(topLayout); botLayout->addWidget(artist); botLayout->addWidget(time); layout->addLayout(botLayout); layout->addItem(new QSpacerItem(1, space/4, QSizePolicy::Fixed, QSizePolicy::Fixed)); layout->addWidget(slider); connect(slider, SIGNAL(sliderPressed()), this, SLOT(pressed())); connect(slider, SIGNAL(sliderReleased()), this, SLOT(released())); connect(slider, SIGNAL(positionSet()), this, SIGNAL(sliderReleased())); connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateTimes())); connect(this, SIGNAL(mpdPoll()), MPDConnection::self(), SLOT(getStatus())); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); clearTimes(); update(Song()); connect(ratingWidget, SIGNAL(valueChanged(int)), SLOT(setRating(int))); connect(this, SIGNAL(setRating(QString,quint8)), MPDConnection::self(), SLOT(setRating(QString,quint8))); connect(PlayQueueModel::self(), SIGNAL(currentSongRating(QString,quint8)), this, SLOT(rating(QString,quint8))); } void NowPlayingWidget::update(const Song &song) { QString name=song.name(); currentSongFile=song.file; ratingWidget->setEnabled(!song.isEmpty() && Song::Standard==song.type); ratingWidget->setValue(0); if (song.isEmpty()) { track->setText(" "); artist->setText(" "); } else if (song.isStream() && !song.isCantataStream() && !song.isCdda() && !song.isDlnaStream()) { track->setText(name.isEmpty() ? Song::unknown() : name); if (song.artist.isEmpty() && song.title.isEmpty() && !name.isEmpty()) { artist->setText(tr("(Stream)")); } else { artist->setText(song.artist.isEmpty() ? song.title : song.artistSong()); } } else { if (song.title.isEmpty() && song.artist.isEmpty() && (!name.isEmpty() || !song.file.isEmpty())) { track->setText(name.isEmpty() ? song.file : name); } else { track->setText(song.title+(song.origYear>0 && song.origYear!=song.year ? QLatin1String(" (")+QString::number(song.origYear)+QLatin1Char(')') : QString())); } if (song.album.isEmpty() && song.artist.isEmpty()) { artist->setText(track->fullText().isEmpty() ? QString() : Song::unknown()); } else if (song.album.isEmpty()) { artist->setText(song.artist); } else { // Artist here is always artist, and not album artist or composer artist->setText(song.artist+QString(" – ")+song.displayAlbum(false)); } } } void NowPlayingWidget::startTimer() { if (!timer) { timer=new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()), this, SLOT(updatePos())); } startTime.restart(); lastVal=value(); timer->start(); pollCount=0; } void NowPlayingWidget::stopTimer() { if (timer) { timer->stop(); } pollCount=0; } void NowPlayingWidget::setValue(int v) { startTime.restart(); lastVal=v; slider->setValue(v); updateTimes(); } void NowPlayingWidget::setRange(int min, int max) { slider->setRange(min, max); time->setRange(min, max); updateTimes(); } void NowPlayingWidget::clearTimes() { stopTimer(); lastVal=0; slider->setRange(0, 0); time->setRange(0, 0); time->updateTime(); } int NowPlayingWidget::value() const { return slider->value(); } void NowPlayingWidget::readConfig() { ratingWidget->setVisible(Settings::self()->showRatingWidget()); } void NowPlayingWidget::saveConfig() { time->saveConfig(); } void NowPlayingWidget::rating(const QString &file, quint8 r) { if (file==currentSongFile) { ratingWidget->setValue(r); } } void NowPlayingWidget::updateTimes() { if (slider->value()<172800 && slider->value() != slider->maximum()) { time->updateTime(); } } void NowPlayingWidget::updatePos() { int elapsed=(startTime.elapsed()/1000.0)+0.5; slider->setValue(lastVal+elapsed); MPDStatus::self()->setGuessedElapsed(lastVal+elapsed); if (++pollCount>=constPollMpd) { pollCount=0; emit mpdPoll(); } } void NowPlayingWidget::pressed() { if (timer) { timer->stop(); } } void NowPlayingWidget::released() { if (timer) { timer->start(); } emit sliderReleased(); } void NowPlayingWidget::setRating(int v) { emit setRating(currentSongFile, v); } void NowPlayingWidget::initColors() { ensurePolished(); QToolButton btn(this); btn.ensurePolished(); track->setPalette(btn.palette()); artist->setPalette(btn.palette()); time->setPalette(btn.palette()); slider->updateStyleSheet(track->palette().windowText().color()); ratingWidget->setColor(Utils::clampColor(track->palette().text().color())); } cantata-2.2.0/widgets/nowplayingwidget.h000066400000000000000000000047701316350454000203510ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NOWPLAYING_WIDGET_H #define NOWPLAYING_WIDGET_H #include #include #include #include "support/squeezedtextlabel.h" class QTimer; class QLabel; class TimeLabel; class RatingWidget; struct Song; class PosSlider : public QSlider { Q_OBJECT public: PosSlider(QWidget *p); virtual ~PosSlider() { } void updateStyleSheet(const QColor &col); void mouseMoveEvent(QMouseEvent *e); void wheelEvent(QWheelEvent *ev); void setRange(int min, int max); Q_SIGNALS: void positionSet(); }; class NowPlayingWidget : public QWidget { Q_OBJECT public: NowPlayingWidget(QWidget *p); virtual ~NowPlayingWidget() { } void update(const Song &song); void startTimer(); void stopTimer(); void setValue(int v); void setRange(int min, int max); void clearTimes(); int value() const; void readConfig(); void saveConfig(); void setEnabled(bool e) { slider->setEnabled(e); } bool isEnabled() const { return slider->isEnabled(); } void initColors(); QColor textColor() const { return track->palette().windowText().color(); } Q_SIGNALS: void sliderReleased(); void mpdPoll(); void setRating(const QString &file, quint8 r); public Q_SLOTS: void rating(const QString &file, quint8 r); private Q_SLOTS: void updateTimes(); void updatePos(); void pressed(); void released(); void setRating(int v); private: SqueezedTextLabel *track; SqueezedTextLabel *artist; TimeLabel *time; PosSlider *slider; RatingWidget *ratingWidget; QTimer *timer; QTime startTime; QString currentSongFile; int lastVal; int pollCount; }; #endif cantata-2.2.0/widgets/playqueueview.cpp000066400000000000000000000334461316350454000202200ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playqueueview.h" #include "models/playqueuemodel.h" #include "gui/covers.h" #include "gui/currentcover.h" #include "groupedview.h" #include "treeview.h" #include "gui/settings.h" #include "mpd-interface/mpdstatus.h" #include "support/spinner.h" #include "messageoverlay.h" #include "icons.h" #include "support/gtkstyle.h" #include "support/proxystyle.h" #include "support/actioncollection.h" #include "support/action.h" #include "models/roles.h" #include #include #include #include // Exported by QtGui void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); PlayQueueTreeView::PlayQueueTreeView(PlayQueueView *parent) : TableView(QLatin1String("playQueue"), parent, true) , view(parent) { setIndentation(0); setItemsExpandable(false); setExpandsOnDoubleClick(false); setRootIsDecorated(false); } void PlayQueueTreeView::paintEvent(QPaintEvent *e) { view->drawBackdrop(viewport(), size()); TreeView::paintEvent(e); } PlayQueueGroupedView::PlayQueueGroupedView(PlayQueueView *parent) : GroupedView(parent, true) , view(parent) { } PlayQueueGroupedView::~PlayQueueGroupedView() { } void PlayQueueGroupedView::paintEvent(QPaintEvent *e) { view->drawBackdrop(viewport(), size()); GroupedView::paintEvent(e); } PlayQueueView::PlayQueueView(QWidget *parent) : QStackedWidget(parent) , mode(ItemView::Mode_Count) , groupedView(0) , treeView(0) , spinner(0) , msgOverlay(0) , backgroundImageType(BI_None) , fadeValue(1.0) , backgroundOpacity(15) , backgroundBlur(0) { removeFromAction = new Action(Icons::self()->removeIcon, tr("Remove"), this); setMode(ItemView::Mode_GroupedTree); animator.setPropertyName("fade"); animator.setTargetObject(this); connect(CurrentCover::self(), SIGNAL(coverImage(QImage)), this, SLOT(setImage(QImage))); } PlayQueueView::~PlayQueueView() { } void PlayQueueView::readConfig() { int origOpacity=backgroundOpacity; int origBlur=backgroundBlur; QString origCustomBackgroundFile=customBackgroundFile; BackgroundImage origType=backgroundImageType; setAutoExpand(Settings::self()->playQueueAutoExpand()); setStartClosed(Settings::self()->playQueueStartClosed()); backgroundImageType=(BackgroundImage)Settings::self()->playQueueBackground(); backgroundOpacity=Settings::self()->playQueueBackgroundOpacity(); backgroundBlur=Settings::self()->playQueueBackgroundBlur(); customBackgroundFile=Settings::self()->playQueueBackgroundFile(); setMode((ItemView::Mode)Settings::self()->playQueueView()); switch (backgroundImageType) { case BI_None: if (origType!=backgroundImageType) { updatePalette(); previousBackground=QPixmap(); curentCover=QImage(); curentBackground=QPixmap(); view()->viewport()->update(); setImage(QImage()); } break; case BI_Cover: if (BI_None==origType) { updatePalette(); } if ((origType!=backgroundImageType || backgroundOpacity!=origOpacity || backgroundBlur!=origBlur)) { setImage(CurrentCover::self()->isValid() ? CurrentCover::self()->image() : QImage()); } break; case BI_Custom: if (BI_None==origType) { updatePalette(); } if (origType!=backgroundImageType || backgroundOpacity!=origOpacity || backgroundBlur!=origBlur || origCustomBackgroundFile!=customBackgroundFile) { setImage(QImage(customBackgroundFile)); } break; } } void PlayQueueView::saveConfig() { if (treeView==currentWidget()) { treeView->saveHeader(); } } void PlayQueueView::setMode(ItemView::Mode m) { if (m==mode || (ItemView::Mode_GroupedTree!=m && ItemView::Mode_Table!=m)) { return; } if (ItemView::Mode_Table==mode) { treeView->saveHeader(); } switch (m) { case ItemView::Mode_GroupedTree: if (!groupedView) { groupedView=new PlayQueueGroupedView(this); groupedView->setContextMenuPolicy(Qt::ActionsContextMenu); groupedView->setIndentation(0); groupedView->setItemsExpandable(false); groupedView->setExpandsOnDoubleClick(false); groupedView->installFilter(new KeyEventHandler(groupedView, removeFromAction)); addWidget(groupedView); connect(groupedView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool))); connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &))); updatePalette(); #ifdef Q_OS_MAC groupedView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif groupedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Top); } break; case ItemView::Mode_Table: if (!treeView) { treeView=new PlayQueueTreeView(this); treeView->setContextMenuPolicy(Qt::ActionsContextMenu); treeView->installFilter(new KeyEventHandler(treeView, removeFromAction)); treeView->initHeader(); addWidget(treeView); connect(treeView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool))); connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &))); updatePalette(); #ifdef Q_OS_MAC treeView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif treeView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Top); } default: break; } QAbstractItemModel *model=0; QList actions; if (ItemView::Mode_Count!=mode) { QAbstractItemView *v=view(); model=v->model(); v->setModel(0); actions=v->actions(); } mode=m; QAbstractItemView *v=view(); v->setModel(model); if (!actions.isEmpty() && v->actions().isEmpty()) { v->addActions(actions); } if (ItemView::Mode_Table==mode) { treeView->initHeader(); } setCurrentWidget(static_cast(view())); if (spinner) { spinner->setWidget(view()); if (spinner->isActive()) { spinner->start(); } } if (msgOverlay) { msgOverlay->setWidget(view()); } } void PlayQueueView::setAutoExpand(bool ae) { groupedView->setAutoExpand(ae); } bool PlayQueueView::isAutoExpand() const { return groupedView->isAutoExpand(); } void PlayQueueView::setStartClosed(bool sc) { groupedView->setStartClosed(sc); } bool PlayQueueView::isStartClosed() const { return groupedView->isStartClosed(); } void PlayQueueView::setFilterActive(bool f) { if (ItemView::Mode_GroupedTree==mode) { groupedView->setFilterActive(f); } } void PlayQueueView::updateRows(qint32 row, quint16 curAlbum, bool scroll, bool forceScroll) { if (ItemView::Mode_GroupedTree==mode) { groupedView->updateRows(row, curAlbum, scroll, QModelIndex(), forceScroll); } } void PlayQueueView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) { view()->scrollTo(index, hint); } QModelIndex PlayQueueView::indexAt(const QPoint &point) { return view()->indexAt(point); } void PlayQueueView::addAction(QAction *a) { view()->addAction(a); } void PlayQueueView::setFocus() { currentWidget()->setFocus(); } bool PlayQueueView::hasFocus() { return currentWidget()->hasFocus(); } bool PlayQueueView::haveSelectedItems() { switch (mode) { default: case ItemView::Mode_GroupedTree: return groupedView->haveSelectedItems(); case ItemView::Mode_Table: return treeView->haveSelectedItems(); } } bool PlayQueueView::haveUnSelectedItems() { switch (mode) { default: case ItemView::Mode_GroupedTree: return groupedView->haveUnSelectedItems(); case ItemView::Mode_Table: return treeView->haveUnSelectedItems(); } } void PlayQueueView::clearSelection() { if (groupedView && groupedView->selectionModel()) { groupedView->selectionModel()->clear(); } if (treeView && treeView->selectionModel()) { treeView->selectionModel()->clear(); } } QAbstractItemView * PlayQueueView::view() const { switch (mode) { default: case ItemView::Mode_GroupedTree: return (QAbstractItemView *)groupedView; case ItemView::Mode_Table: return (QAbstractItemView *)treeView; } } bool PlayQueueView::hasFocus() const { return view()->hasFocus(); } QModelIndexList PlayQueueView::selectedIndexes(bool sorted) const { switch (mode) { default: case ItemView::Mode_GroupedTree: return groupedView->selectedIndexes(sorted); case ItemView::Mode_Table: return treeView->selectedIndexes(sorted); } } QList PlayQueueView::selectedSongs() const { const QModelIndexList selected = selectedIndexes(); QList songs; foreach (const QModelIndex &idx, selected) { Song song=idx.data(Cantata::Role_Song).value(); if (!song.file.isEmpty() && !song.file.contains(":/") && !song.file.startsWith('/')) { songs.append(song); } } return songs; } void PlayQueueView::showSpinner() { if (!spinner) { spinner=new Spinner(this); } spinner->setWidget(view()); spinner->start(); } void PlayQueueView::hideSpinner() { if (spinner) { spinner->stop(); } } void PlayQueueView::setFade(float value) { if (fadeValue!=value) { fadeValue = value; if (qFuzzyCompare(fadeValue, qreal(1.0))) { previousBackground=QPixmap(); } view()->viewport()->update(); } } void PlayQueueView::updatePalette() { QPalette pal=palette(); if (BI_None!=backgroundImageType) { pal.setColor(QPalette::Base, Qt::transparent); } if (groupedView) { #ifndef Q_OS_MAC groupedView->setPalette(pal); #endif groupedView->viewport()->setPalette(pal); } if (treeView) { #ifndef Q_OS_MAC treeView->setPalette(pal); #endif treeView->viewport()->setPalette(pal); } } void PlayQueueView::setImage(const QImage &img) { if (BI_None==backgroundImageType || (sender() && BI_Custom==backgroundImageType)) { return; } previousBackground=curentBackground; if (img.isNull() || QImage::Format_ARGB32==img.format()) { curentCover = img; } else { curentCover = img.convertToFormat(QImage::Format_ARGB32); } if (!curentCover.isNull()) { if (backgroundOpacity<100) { curentCover=TreeView::setOpacity(curentCover, (backgroundOpacity*1.0)/100.0); } if (backgroundBlur>0) { QImage blurred(curentCover.size(), QImage::Format_ARGB32_Premultiplied); blurred.fill(Qt::transparent); QPainter painter(&blurred); qt_blurImage(&painter, curentCover, backgroundBlur, true, false); painter.end(); curentCover = blurred; } } curentBackground=QPixmap(); animator.stop(); if (BI_Custom==backgroundImageType || !isVisible()) { setFade(1.0); update(); } else { fadeValue=0.0; animator.setDuration(250); animator.setEndValue(1.0); animator.start(); } } void PlayQueueView::streamFetchStatus(const QString &msg) { if (!msgOverlay) { msgOverlay=new MessageOverlay(this); msgOverlay->setWidget(view()); connect(msgOverlay, SIGNAL(cancel()), SIGNAL(cancelStreamFetch())); connect(msgOverlay, SIGNAL(cancel()), SLOT(hideSpinner())); } msgOverlay->setText(msg); } void PlayQueueView::searchActive(bool a) { view()->setProperty(ProxyStyle::constModifyFrameProp, a ? 0 : ProxyStyle::VF_Top); } void PlayQueueView::drawBackdrop(QWidget *widget, const QSize &size) { if (BI_None==backgroundImageType) { return; } QPainter p(widget); p.fillRect(0, 0, size.width(), size.height(), QApplication::palette().color(topLevelWidget()->isActiveWindow() ? QPalette::Active : QPalette::Inactive, QPalette::Base)); if (!curentCover.isNull() || !previousBackground.isNull()) { if (!curentCover.isNull() && (size!=lastBgndSize || curentBackground.isNull())) { curentBackground = QPixmap::fromImage(curentCover.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); lastBgndSize=size; } if (!previousBackground.isNull()) { if (!qFuzzyCompare(fadeValue, qreal(0.0))) { p.setOpacity(1.0-fadeValue); } p.drawPixmap((size.width()-previousBackground.width())/2, (size.height()-previousBackground.height())/2, previousBackground); } if (!curentBackground.isNull()) { p.setOpacity(fadeValue); p.drawPixmap((size.width()-curentBackground.width())/2, (size.height()-curentBackground.height())/2, curentBackground); } } } cantata-2.2.0/widgets/playqueueview.h000066400000000000000000000102271316350454000176550ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYQUEUEVIEW_H #define PLAYQUEUEVIEW_H #include #include #include #include #include #include #include "tableview.h" #include "listview.h" #include "groupedview.h" #include "mpd-interface/song.h" #include "itemview.h" class QAbstractItemModel; class QAction; class Action; class QItemSelectionModel; class QModelIndex; class Spinner; class PlayQueueView; class MessageOverlay; class PlayQueueTreeView : public TableView { public: PlayQueueTreeView(PlayQueueView *p); virtual ~PlayQueueTreeView() { } void paintEvent(QPaintEvent *e); private: PlayQueueView *view; }; class PlayQueueGroupedView : public GroupedView { public: PlayQueueGroupedView(PlayQueueView *p); virtual ~PlayQueueGroupedView(); void paintEvent(QPaintEvent *e); private: PlayQueueView *view; }; class PlayQueueView : public QStackedWidget { Q_OBJECT Q_PROPERTY(float fade READ fade WRITE setFade) public: enum BackgroundImage { BI_None, BI_Cover, BI_Custom }; PlayQueueView(QWidget *parent=0); virtual ~PlayQueueView(); void readConfig(); void saveConfig(); void saveHeader(); void setMode(ItemView::Mode m); bool isGrouped() const { return ItemView::Mode_GroupedTree==mode; } void setAutoExpand(bool ae); bool isAutoExpand() const; void setStartClosed(bool sc); bool isStartClosed() const; void setFilterActive(bool f); void updateRows(qint32 row, quint16 curAlbum, bool scroll, bool forceScroll=false); void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint); QModelIndex indexAt(const QPoint &point); void setModel(QAbstractItemModel *m) { view()->setModel(m); } void addAction(QAction *a); void setFocus(); bool hasFocus(); QAbstractItemModel * model() { return view()->model(); } bool haveSelectedItems(); bool haveUnSelectedItems(); void setCurrentIndex(const QModelIndex &idx) { view()->setCurrentIndex(idx); } void clearSelection(); QAbstractItemView * view() const; bool hasFocus() const; QModelIndexList selectedIndexes(bool sorted=true) const; QList selectedSongs() const; float fade() { return fadeValue; } void setFade(float value); void updatePalette(); Action * removeFromAct() { return removeFromAction; } public Q_SLOTS: void showSpinner(); void hideSpinner(); void setImage(const QImage &img); void streamFetchStatus(const QString &msg); void searchActive(bool a); Q_SIGNALS: void itemsSelected(bool); void doubleClicked(const QModelIndex &); void cancelStreamFetch(); void focusSearch(const QString &text); private: void drawBackdrop(QWidget *widget, const QSize &size); private: Action *removeFromAction; ItemView::Mode mode; PlayQueueGroupedView *groupedView; PlayQueueTreeView *treeView; Spinner *spinner; MessageOverlay *msgOverlay; BackgroundImage backgroundImageType; QPropertyAnimation animator; QImage curentCover; QPixmap curentBackground; QPixmap previousBackground; QSize lastBgndSize; double fadeValue; int backgroundOpacity; int backgroundBlur; QString customBackgroundFile; friend class PlayQueueGroupedView; friend class PlayQueueTreeView; }; #endif cantata-2.2.0/widgets/ratingwidget.cpp000066400000000000000000000123461316350454000177770ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ratingwidget.h" #include "support/icon.h" #include "mpd-interface/song.h" #include #include #include #include #include #include static bool allowHalfStars=true; // TODO: Make this configurable??? static const int constNumStars=Song::Rating_Max/Song::Rating_Step; static const int constBorder=2; RatingPainter::RatingPainter(int s) : starSz(s) , pixmapSize((starSz*constNumStars)+(constBorder*(constNumStars-1)), starSz) , col(QApplication::palette().text().color()) { } void RatingPainter::paint(QPainter *p, const QRect &r, int rating) { if (rating<0 || rating>Song::Rating_Max) { return; } double pixelRatio=p && p->device() ? p->device()->DEVICE_PIXEL_RATIO() : 1.0; if (!isNull() && !Utils::equal(pixelRatio, pixmaps[0].DEVICE_PIXEL_RATIO())) { pixmaps[0]=QPixmap(); } if (isNull()) { QSvgRenderer renderer; QFile f(":stars.svg"); QByteArray bytes; if (f.open(QIODevice::ReadOnly)) { bytes=f.readAll(); } if (!bytes.isEmpty()) { bytes.replace("#000", col.name().toLatin1()); if (pixelRatio>1.25) { bytes.replace("stroke-width=\"3\"", "stroke-width=\"6\""); } } renderer.load(bytes); int pixSize=starSz*pixelRatio; for (int p=0; p<2; ++p) { pixmaps[p]=QPixmap(pixSize, pixSize); pixmaps[p].fill(Qt::transparent); QPainter painter(&(pixmaps[p])); renderer.render(&painter, 1==p ? "on" : "off", QRectF(0, 0, pixSize, pixSize)); } pixmaps[2]=QPixmap(pixSize, pixSize); pixmaps[2].fill(Qt::transparent); QPainter painter(&(pixmaps[2])); int halfSz=(pixSize/2.0)+0.5; painter.drawPixmap(0, 0, pixmaps[1], 0, 0, halfSz, pixSize); painter.drawPixmap(halfSz, 0, pixmaps[0], halfSz, 0, pixSize-halfSz, pixSize); painter.end(); for (int p=0; p<3; ++p) { pixmaps[p].setDevicePixelRatio(pixelRatio); } } int fullStars=rating/Song::Rating_Step; bool half=allowHalfStars && rating%Song::Rating_Step; QSize layoutSize = pixmaps[0].size() / pixmaps[0].DEVICE_PIXEL_RATIO(); QRect pr(r.x(), r.y()+(r.height()-layoutSize.width())/2, layoutSize.width(), layoutSize.height()); for (int i=0; idrawPixmap(pr, pixmaps[half && i==fullStars ? 2 : ipos()); emit valueChanged(val); } void RatingWidget::mouseMoveEvent(QMouseEvent *e) { Q_UNUSED(e) hoverVal = valueForPos(e->pos()); update(); } void RatingWidget::leaveEvent(QEvent *e) { Q_UNUSED(e) hoverVal = -1; update(); } QRect RatingWidget::contentsRect() const { const QRect &r=rect(); const int width = rp.size().width(); const int x = r.x() + (r.width() - width) / 2; return QRect(x, r.y(), width, r.height()); } int RatingWidget::valueForPos(const QPoint &pos) const { const QRect contents = contentsRect(); const double raw = double(pos.x() - contents.left()) / contents.width(); if (!allowHalfStars) { int v=(raw*Song::Rating_Max); if (v%Song::Rating_Step) { v++; } return v; } return (raw*Song::Rating_Max)+0.5; } cantata-2.2.0/widgets/ratingwidget.h000066400000000000000000000041021316350454000174330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef RATINGWIDGET_H #define RATINGWIDGET_H #include #include class RatingPainter { public: RatingPainter(int s); void paint(QPainter *p, const QRect &r, int rating); int starSize() const { return starSz; } void setColor(const QColor &c); const QColor & color() const { return col; } const QSize & size() const { return pixmapSize; } bool isNull() const { return pixmaps[0].isNull(); } private: int starSz; QSize pixmapSize; QColor col; QPixmap pixmaps[3]; }; class RatingWidget : public QWidget { Q_OBJECT public: RatingWidget(QWidget *parent = 0); QSize sizeHint() const { return rp.size(); } int value() const { return val; } void setValue(int v); void setColor(const QColor &c); void setShowZeroForNull(bool s) { showZeroForNull=s; } Q_SIGNALS: void valueChanged(int v); protected: void paintEvent(QPaintEvent *e); void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void leaveEvent(QEvent *e); private: QRect contentsRect() const; int valueForPos(const QPoint &pos) const; QColor getColor() const; private: RatingPainter rp; int val; int hoverVal; bool showZeroForNull; }; #endif cantata-2.2.0/widgets/searchwidget.cpp000066400000000000000000000125301316350454000177530ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "searchwidget.h" #include "support/monoicon.h" #include "support/utils.h" #include "toolbutton.h" #include #include #include #include class EscKeyEventHandler : public QObject { public: EscKeyEventHandler(SearchWidget *v) : QObject(v), view(v) { } protected: bool eventFilter(QObject *obj, QEvent *event) { if (view->hasFocus() && QEvent::KeyRelease==event->type()) { QKeyEvent *keyEvent=static_cast(event); if (Qt::Key_Escape==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) { view->close(); return true; } } return QObject::eventFilter(obj, event); } private: SearchWidget *view; }; SearchWidget::SearchWidget(QWidget *p) : QWidget(p) , cat(0) , widgetIsActive(false) { QHBoxLayout *l=new QHBoxLayout(this); int spacing=qMin(2, Utils::layoutSpacing(this)); #ifdef Q_OS_MAC l->setSpacing(2); l->setContentsMargins(0, spacing, spacing, spacing); bool closeOnLeft=true; #else l->setSpacing(0); l->setContentsMargins(0, spacing, 0, spacing); bool closeOnLeft=Utils::Unity==Utils::currentDe(); #endif edit=new LineEdit(this); edit->setPlaceholderText(tr("Search...")); closeButton=new ToolButton(this); closeButton->setToolTip(tr("Close Search Bar")+QLatin1String(" (")+QKeySequence(Qt::Key_Escape).toString()+QLatin1Char(')')); if (closeOnLeft) { l->addWidget(closeButton); l->addWidget(edit); } else { l->addWidget(edit); l->addWidget(closeButton); } closeButton->setIcon(MonoIcon::icon(FontAwesome::close, MonoIcon::constRed, MonoIcon::constRed)); Icon::init(closeButton); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); connect(edit, SIGNAL(textChanged(QString)), SIGNAL(textChanged(QString))); connect(edit, SIGNAL(returnPressed()), SIGNAL(returnPressed())); installEventFilter(new EscKeyEventHandler(this));; setTabOrder(edit, closeButton); } void SearchWidget::setPermanent() { show(); setFocus(); closeButton->setVisible(false); closeButton->deleteLater(); closeButton=0; layout()->setSpacing(0); } void SearchWidget::setCategories(const QList &categories) { QString currentCat; if (!cat) { cat=new SelectorLabel(this); QHBoxLayout *l=static_cast(layout()); l->insertWidget(0, cat); l->setSpacing(qMin(4, Utils::layoutSpacing(this))); connect(cat, SIGNAL(activated(int)), SIGNAL(returnPressed())); connect(cat, SIGNAL(activated(int)), this, SLOT(categoryActivated(int))); setTabOrder(cat, edit); cat->setFixedHeight(edit->height()); } else { currentCat=category(); if (!currentCat.isEmpty()) { cat->blockSignals(true); } } cat->clear(); foreach (const Category &c, categories) { cat->addItem(c.text, c.field, c.toolTip); } if (!currentCat.isEmpty()) { for (int i=0; icount(); ++i) { if (cat->itemData(i)==currentCat) { cat->setCurrentIndex(i); cat->blockSignals(false); setToolTip(cat->action(i)->toolTip()); return; } } } cat->blockSignals(false); cat->setCurrentIndex(0); } void SearchWidget::setCategory(const QString &id) { if (!cat || id.isEmpty()) { return; } for (int i=0; icount(); ++i) { if (cat->itemData(i)==id) { cat->setCurrentIndex(i); setToolTip(cat->action(i)->toolTip()); return; } } } void SearchWidget::toggle() { if (isVisible()) { close(); } else { activate(); } } void SearchWidget::activate(const QString &text) { bool wasActive=widgetIsActive; widgetIsActive=true; if (!text.isEmpty()) { edit->setText(text); } else { edit->selectAll(); } show(); setFocus(); if (wasActive!=widgetIsActive) { emit active(widgetIsActive); } } void SearchWidget::close() { if (!closeButton) { return; } bool wasActive=widgetIsActive; widgetIsActive=false; setVisible(false); edit->setText(QString()); if (wasActive!=widgetIsActive) { emit active(widgetIsActive); } } void SearchWidget::categoryActivated(int c) { QAction *a=cat->action(c); if (a) { setToolTip(a->toolTip()); } } cantata-2.2.0/widgets/searchwidget.h000066400000000000000000000046151316350454000174250ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SEARRCHWIDGET_H #define SEARRCHWIDGET_H #include "support/lineedit.h" #include "toolbutton.h" #include "support/squeezedtextlabel.h" #include "selectorlabel.h" #include #include #include class Icon; class SearchWidget : public QWidget { Q_OBJECT public: struct Category { Category(const QString &txt=QString(), const QString &f=QString(), const QString &tt=QString()) : text(txt), field(f), toolTip(tt) { } QString text; QString field; QString toolTip; }; SearchWidget(QWidget *p); virtual ~SearchWidget() { } void setText(const QString &t) { edit->setText(t); } QString text() const { return edit->text(); } QString category() const { return cat ? cat->itemData(cat->currentIndex()) : QString(); } void setFocus() { edit->setFocus(); } bool hasFocus() const { return edit->hasFocus() || (closeButton && closeButton->hasFocus()); } bool isActive() const { return widgetIsActive; } void setPermanent(); void setCategories(const QList &categories); void setCategory(const QString &id); Q_SIGNALS: void textChanged(const QString &); void returnPressed(); void active(bool); public Q_SLOTS: void toggle(); void clear() { edit->clear(); } void activate(const QString &text=QString()); void show() { setVisible(true); } void close(); private Q_SLOTS: void categoryActivated(int c); private: SelectorLabel *cat; LineEdit *edit; ToolButton *closeButton; bool widgetIsActive; }; #endif cantata-2.2.0/widgets/selectorlabel.cpp000066400000000000000000000077531316350454000201350ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "selectorlabel.h" #include "support/osxstyle.h" #include "support/utils.h" #include #include #include SelectorLabel::SelectorLabel(QWidget *p) : QLabel(p) , current(0) , useArrow(false) , menu(0) { setAttribute(Qt::WA_Hover, true); menu=new QMenu(this); #ifdef Q_OS_MAC // Mac text seems to be 2px too high. margin-top fixes this setStyleSheet(QString("QLabel { margin-top: 2px} QLabel:hover {color:%1;}").arg(OSXStyle::self()->viewPalette().highlight().color().name())); #else setStyleSheet(QLatin1String("QLabel:hover {color:palette(highlight);}")); #endif //setMargin(Utils::scaleForDpi(2)); } static QString addMarkup(const QString &s, bool arrow) { return QLatin1String("")+s+(arrow ? QLatin1String(" ")+QChar(0x25BE) : QString())+QLatin1String(""); } void SelectorLabel::addItem(const QString &text, const QString &data, const QString &tt) { QAction *act=menu->addAction(text, this, SLOT(itemSelected())); act->setData(data); if (!tt.isEmpty()) { act->setToolTip(tt); } setText(addMarkup(text, useArrow)); current=menu->actions().count(); } void SelectorLabel::itemSelected() { QAction *act=qobject_cast(sender()); if (act) { setCurrentIndex(menu->actions().indexOf(act)); } } bool SelectorLabel::event(QEvent *e) { if (!menu) { return QLabel::event(e); } QList actions=menu->actions(); switch (e->type()) { case QEvent::MouseButtonPress: if (Qt::NoModifier==static_cast(e)->modifiers() && Qt::LeftButton==static_cast(e)->button()) { menu->exec(mapToGlobal(QPoint(0, 0))); update(); } break; case QEvent::Wheel: { int numDegrees = static_cast(e)->delta() / 8; int numSteps = numDegrees / 15; int newIndex = current; if (numSteps > 0) { for (int i = 0; i < numSteps; ++i) { newIndex++; if (newIndex>=actions.count()) { newIndex=0; } } } else { for (int i = 0; i > numSteps; --i) { newIndex--; if (newIndex<0) { newIndex=actions.count()-1; } } } setCurrentIndex(newIndex); break; } default: break; } return QLabel::event(e); } void SelectorLabel::setCurrentIndex(int v) { if (!menu || v<0 || v==current) { return; } QList actions=menu->actions(); if (v>=actions.count()) { return; } current=v; setText(addMarkup(Utils::strippedText(actions.at(current)->text()), useArrow)); emit activated(current); } QString SelectorLabel::itemData(int index) const { QAction *act=action(index); return act ? act->data().toString() : QString(); } QAction * SelectorLabel::action(int index) const { if (!menu) { return 0; } QList actions=menu->actions(); if (index>=actions.count()) { return 0; } return actions.at(index); } cantata-2.2.0/widgets/selectorlabel.h000066400000000000000000000031621316350454000175700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SELECTOR_LABEL_H #define SELECTOR_LABEL_H #include #include #include class QMenu; class SelectorLabel : public QLabel { Q_OBJECT public: SelectorLabel(QWidget *p); void setUseArrow(bool a) { useArrow=a; } void clear() { if (menu) menu->clear(); } void addItem(const QString &text, const QString &data, const QString &tt=QString()); bool event(QEvent *e); int currentIndex() const { return current; } void setCurrentIndex(int v); QString itemData(int index) const; QAction * action(int index) const; int count() const { return menu ? menu->actions().count() : 0; } Q_SIGNALS: void activated(int); private Q_SLOTS: void itemSelected(); private: int current; bool useArrow; QMenu *menu; }; #endif cantata-2.2.0/widgets/servicestatuslabel.cpp000066400000000000000000000043041316350454000212060ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "servicestatuslabel.h" #include #include #include #include ServiceStatusLabel::ServiceStatusLabel(QWidget *p) : QLabel(p) , pressed(false) { QFont f(font()); f.setBold(true); setFont(f); } void ServiceStatusLabel::setText(const QString &txt, const QString &name) { QLabel::setText(txt); onTooltip=tr("Logged into %1").arg(name); offTooltip=tr("NOT logged into %1").arg(name); } void ServiceStatusLabel::setStatus(bool on) { setVisible(true); setToolTip(on ? onTooltip : offTooltip); QString col=on ? palette().highlight().color().name() : palette().color(QPalette::Disabled, QPalette::WindowText).name(); int margin=style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this); if (margin<2) { margin=2; } setStyleSheet(QString("QLabel { color : %1; border-radius: %4px; border: 2px solid %2; margin: %3px}") .arg(col).arg(col).arg(margin).arg(margin*2)); } void ServiceStatusLabel::mousePressEvent(QMouseEvent *ev) { QLabel::mousePressEvent(ev); pressed=true; } void ServiceStatusLabel::mouseReleaseEvent(QMouseEvent *ev) { QLabel::mouseReleaseEvent(ev); if (pressed) { pressed=false; if (this==QApplication::widgetAt(QCursor::pos())) { emit clicked(); } } } cantata-2.2.0/widgets/servicestatuslabel.h000066400000000000000000000025411316350454000206540ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SERVICE_STATUS_LABEL_H #define SERVICE_STATUS_LABEL_H #include class ServiceStatusLabel : public QLabel { Q_OBJECT public: ServiceStatusLabel(QWidget *p); virtual ~ServiceStatusLabel() { } void setText(const QString &txt, const QString &name); void setStatus(bool on); Q_SIGNALS: void clicked(); private: void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); private: bool pressed; QString onTooltip; QString offTooltip; }; #endif cantata-2.2.0/widgets/singlepagewidget.cpp000066400000000000000000000172511316350454000206310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "singlepagewidget.h" #include "widgets/sizewidget.h" #include "widgets/spacerwidget.h" #include "widgets/toolbutton.h" #include "widgets/icons.h" #include "gui/stdactions.h" #include "mpd-interface/mpdconnection.h" #include #include static QString viewTypeString(ItemView::Mode mode) { switch (mode) { default: case ItemView::Mode_BasicTree: return QObject::tr("Basic Tree (No Icons)"); case ItemView::Mode_SimpleTree: return QObject::tr("Simple Tree"); case ItemView::Mode_DetailedTree: return QObject::tr("Detailed Tree"); case ItemView::Mode_GroupedTree: return QObject::tr("Grouped Albums"); case ItemView::Mode_List: return QObject::tr("List"); case ItemView::Mode_IconTop: return QObject::tr("Grid"); case ItemView::Mode_Table: return QObject::tr("Table"); } } SinglePageWidget::SinglePageWidget(QWidget *p) : QWidget(p) , btnFlags(0) , refreshAction(0) { QGridLayout *layout=new QGridLayout(this); view=new ItemView(this); layout->addWidget(view, 1, 0, 1, 5); // layout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred), 2, 1, 1, 1); layout->addWidget(new SizeWidget(this), 2, 2, 1, 1); // layout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred), 2, 3, 1, 1); layout->setMargin(0); layout->setSpacing(0); connect(view, SIGNAL(searchItems()), this, SIGNAL(searchItems())); connect(view, SIGNAL(itemsSelected(bool)), this, SLOT(controlActions())); connect(this, SIGNAL(add(const QStringList &, int, quint8, bool)), MPDConnection::self(), SLOT(add(const QStringList &, int, quint8, bool))); connect(this, SIGNAL(addSongsToPlaylist(const QString &, const QStringList &)), MPDConnection::self(), SLOT(addToPlaylist(const QString &, const QStringList &))); } void SinglePageWidget::addWidget(QWidget *w) { static_cast(layout())->addWidget(w, 0, 0, 1, 5); } void SinglePageWidget::init(int flags, const QList &leftXtra, const QList &rightXtra) { if (0!=btnFlags) { return; } btnFlags=flags; QList left=leftXtra; QList right=rightXtra; if (!right.isEmpty() && (flags&(AppendToPlayQueue|ReplacePlayQueue))) { right << new SpacerWidget(this); } if (flags&ReplacePlayQueue) { view->addAction(StdActions::self()->replacePlayQueueAction); } if (flags&AppendToPlayQueue) { ToolButton *addToPlayQueue=new ToolButton(this); addToPlayQueue->setDefaultAction(StdActions::self()->appendToPlayQueueAction); right.append(addToPlayQueue); view->addAction(StdActions::self()->addToPlayQueueMenuAction); } if (flags&ReplacePlayQueue) { ToolButton *replacePlayQueue=new ToolButton(this); replacePlayQueue->setDefaultAction(StdActions::self()->replacePlayQueueAction); right.append(replacePlayQueue); } if (flags&Refresh) { ToolButton *refreshButton=new ToolButton(this); refreshAction=new Action(Icons::self()->reloadIcon, tr("Refresh"), this); refreshButton->setDefaultAction(refreshAction); connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh())); left.append(refreshButton); } connect(this, SIGNAL(searchItems()), this, SLOT(doSearch())); if (!left.isEmpty()) { QHBoxLayout *ll=new QHBoxLayout(); foreach (QWidget *b, left) { ll->addWidget(b); } static_cast(layout())->addItem(ll, 2, 0, 1, 1); } if (!right.isEmpty()) { QHBoxLayout *rl=new QHBoxLayout(); foreach (QWidget *b, right) { rl->addWidget(b); } static_cast(layout())->addItem(rl, 2, 4, 1, 1); } } void SinglePageWidget::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { // Always get tracks and playlists - this way error message can be shown. #902 QStringList files=selectedFiles(true); if (!files.isEmpty()) { if (name.isEmpty()) { emit add(files, action, priorty, decreasePriority); } else { emit addSongsToPlaylist(name, files); } view->clearSelection(); } } void SinglePageWidget::showEvent(QShowEvent *e) { QWidget::showEvent(e); controlActions(); view->focusView(); } void SinglePageWidget::hideEvent(QHideEvent *e) { QWidget::hideEvent(e); controlActions(); } const char * SinglePageWidget::constValProp="val"; QList SinglePageWidget::createActions(const QList &values,int currentVal, QWidget *parent, const char *slot) { QList actions; QActionGroup *group=new QActionGroup(parent); foreach (const MenuItem &v, values) { QAction *act=new QAction(v.first, parent); connect(act, SIGNAL(toggled(bool)), parent, slot); act->setActionGroup(group); act->setProperty(constValProp, v.second); act->setCheckable(true); act->setChecked(v.second==currentVal); actions.append(act); } return actions; } Action * SinglePageWidget::createMenuGroup(const QString &name, const QList actions, QWidget *parent) { Action *action=new Action(name, parent); QMenu *menu=new QMenu(parent); menu->addActions(actions); action->setMenu(menu); return action; } Action * SinglePageWidget::createMenuGroup(const QString &name, const QList &values,int currentVal, QWidget *parent, const char *slot) { return createMenuGroup(name, createActions(values, currentVal, parent, slot), parent); } QList SinglePageWidget::createViewActions(QList modes) { QList > vals; foreach (ItemView::Mode m, modes) { vals.append(MenuItem(viewTypeString(m), m)); } return createActions(vals, view->viewMode(), this, SLOT(viewModeSelected())); } Action * SinglePageWidget::createViewMenu(QList modes) { return createMenuGroup(tr("View"), createViewActions(modes), this); } void SinglePageWidget::setView(int v) { view->setMode((ItemView::Mode)v); } void SinglePageWidget::viewModeSelected() { QAction *act=qobject_cast(sender()); if (act) { setView(act->property(constValProp).toInt()); } } void SinglePageWidget::focusSearch() { view->focusSearch(); } void SinglePageWidget::controlActions() { QModelIndexList selected=view->selectedIndexes(false); if (btnFlags&ReplacePlayQueue) { StdActions::self()->replacePlayQueueAction->setEnabled(!selected.isEmpty()); } if (btnFlags&AppendToPlayQueue) { StdActions::self()->appendToPlayQueueAction->setEnabled(!selected.isEmpty()); } StdActions::self()->addRandomAlbumToPlayQueueAction->setVisible(false); } cantata-2.2.0/widgets/singlepagewidget.h000066400000000000000000000067161316350454000203020ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SINGLE_PAGE_WIDGET_H #define SINGLE_PAGE_WIDGET_H #include #include "mpd-interface/song.h" #include "gui/page.h" #include "widgets/itemview.h" #include "mpd-interface/mpdconnection.h" class Action; class SinglePageWidget : public QWidget, public Page { Q_OBJECT public: enum { ReplacePlayQueue = 0x01, AppendToPlayQueue = 0x02, Refresh = 0x04, All = AppendToPlayQueue|ReplacePlayQueue|Refresh }; typedef QPair MenuItem; static const char *constValProp; static QList createActions(const QList &values,int currentVal, QWidget *parent, const char *slot); static Action * createMenuGroup(const QString &name, const QList actions, QWidget *parent); static Action * createMenuGroup(const QString &name, const QList &values, int currentVal, QWidget *parent, const char *slot); SinglePageWidget(QWidget *p); virtual ~SinglePageWidget() { } void addWidget(QWidget *w); virtual void setView(int v); ItemView::Mode viewMode() const { return view->viewMode(); } void focusSearch(); void init(int flags=All, const QList &leftXtra=QList(), const QList &rightXtra=QList()); virtual QStringList selectedFiles(bool allowPlaylists=false) const { Q_UNUSED(allowPlaylists); return QStringList(); } virtual QList selectedSongs(bool allowPlaylists=false) const { Q_UNUSED(allowPlaylists); return QList(); } virtual void addSelectionToPlaylist(const QString &name=QString(), int action=MPDConnection::Append, quint8 priorty=0, bool decreasePriority=false); virtual Song coverRequest() const { return Song(); } #ifdef ENABLE_DEVICES_SUPPORT virtual void addSelectionToDevice(const QString &udi) { Q_UNUSED(udi); } virtual void deleteSongs() { } #endif void showEvent(QShowEvent *e); void hideEvent(QHideEvent *e); QList createViewActions(QList modes); Action * createViewMenu(QList modes); public Q_SLOTS: virtual void doSearch() { } virtual void refresh() { } virtual void controlActions(); Q_SIGNALS: void close(); void searchItems(); // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) void add(const QStringList &files, int action, quint8 priorty, bool decreasePriority); void addSongsToPlaylist(const QString &name, const QStringList &files); private Q_SLOTS: void viewModeSelected(); protected: int btnFlags; ItemView *view; Action *refreshAction; }; #endif cantata-2.2.0/widgets/sizegrip.cpp000066400000000000000000000024121316350454000171340ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sizegrip.h" #include #include SizeGrip::SizeGrip(QWidget *parent) : QWidget(parent) { QBoxLayout *l=new QBoxLayout(QBoxLayout::TopToBottom, this); l->addItem(new QSpacerItem(0, 0, QSizePolicy::Maximum, QSizePolicy::Preferred)); QSizeGrip *grip=new QSizeGrip(this); l->addWidget(grip); l->setMargin(0); l->setSpacing(0); l->setAlignment(Qt::AlignBottom|Qt::AlignRight); } cantata-2.2.0/widgets/sizegrip.h000066400000000000000000000017571316350454000166140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SIZEGRIP_H #define SIZEGRIP_H #include class SizeGrip : public QWidget { public: explicit SizeGrip(QWidget *parent = 0); }; #endif cantata-2.2.0/widgets/sizewidget.cpp000066400000000000000000000025261316350454000174640ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sizewidget.h" #include "toolbutton.h" #include "support/combobox.h" #include SizeWidget::SizeWidget(QWidget *parent) : QWidget(parent) { QHBoxLayout *lay=new QHBoxLayout(this); ComboBox *combo=new ComboBox(this); ToolButton *btn=new ToolButton(this); lay->setMargin(0); lay->setSpacing(0); combo->setFixedWidth(0); btn->setFixedWidth(0); lay->addWidget(combo); lay->addWidget(btn); } void SizeWidget::paintEvent(QPaintEvent *e) { Q_UNUSED(e) } cantata-2.2.0/widgets/sizewidget.h000066400000000000000000000020341316350454000171230ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SIZEWIDGET_H #define SIZEWIDGET_H #include class SizeWidget : public QWidget { public: explicit SizeWidget(QWidget *parent = 0); void paintEvent(QPaintEvent *e); }; #endif cantata-2.2.0/widgets/songdialog.cpp000066400000000000000000000042701316350454000174320ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "songdialog.h" #include "support/messagebox.h" #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void SongDialog::enableDebug() { debugEnabled=true; } static const int constNumToCheck=10; bool SongDialog::songsOk(const QList &songs, const QString &base, bool isMpd) { QWidget *wid=isVisible() ? this : parentWidget(); int checked=0; foreach (const Song &s, songs) { QString file=s.filePath(); DBUG << "Checking dir:" << base << " song:" << file << " file:" << QString(base+file); if (!QFile::exists(base+file)) { DBUG << QString(base+file) << "does not exist"; if (isMpd) { MessageBox::error(wid, tr("Cannot access song files!\n\n" "Please check Cantata's \"Music folder\" setting, and MPD's \"music_directory\" setting.")); } else { MessageBox::error(wid, tr("Cannot access song files!\n\n" "Please check that the device is still attached.")); } deleteLater(); return false; } if (++checked>constNumToCheck) { break; } } DBUG << "Checked" << checked << "files"; return true; } cantata-2.2.0/widgets/songdialog.h000066400000000000000000000024401316350454000170740ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SONG_DIALOG_H #define SONG_DIALOG_H #include "support/dialog.h" #include "mpd-interface/song.h" class SongDialog : public Dialog { Q_OBJECT public: static void enableDebug(); SongDialog(QWidget *parent, const QString &name=QString(), const QSize &defSize=QSize()) : Dialog(parent, name, defSize) { } virtual ~SongDialog() { } protected: bool songsOk(const QList &songs, const QString &base, bool isMpd); }; #endif cantata-2.2.0/widgets/spacerwidget.cpp000066400000000000000000000022761316350454000177710ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "spacerwidget.h" #include "toolbutton.h" #include #include static int fixedWidth=0; SpacerWidget::SpacerWidget(QWidget *parent) : QWidget(parent) { if (0==fixedWidth) { ToolButton tb(parent); tb.ensurePolished(); fixedWidth=tb.sizeHint().width()*0.75; } setFixedWidth(fixedWidth); } cantata-2.2.0/widgets/spacerwidget.h000066400000000000000000000020531316350454000174270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SPACER_WIDGET_H #define SPACER_WIDGET_H #include class SpacerWidget : public QWidget { public: SpacerWidget(QWidget *parent = 0); virtual ~SpacerWidget() { } }; #endif // SPACER_WIDGET_H cantata-2.2.0/widgets/stackedpagewidget.cpp000066400000000000000000000070041316350454000207610ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "stackedpagewidget.h" #include "support/icon.h" #include "support/utils.h" #include "support/squeezedtextlabel.h" #include "support/proxystyle.h" #include "listview.h" #include "sizewidget.h" #include "singlepagewidget.h" #include #include #include #include #include StackedPageWidget::StackedPageWidget(QWidget *p) : QStackedWidget(p) { } StackedPageWidget::~StackedPageWidget() { } void StackedPageWidget::setView(int v) { for (int i=0; i(widget(i))) { static_cast(widget(i))->setView(v); } else if (dynamic_cast(widget(i))) { static_cast(widget(i))->setView(v); } } } void StackedPageWidget::focusSearch() { QWidget *w=currentWidget(); if (dynamic_cast(w)) { static_cast(w)->focusSearch(); } else if (dynamic_cast(w)) { static_cast(w)->focusSearch(); } } QStringList StackedPageWidget::selectedFiles(bool allowPlaylists) const { QWidget *w=currentWidget(); if (dynamic_cast(w)) { return static_cast(w)->selectedFiles(allowPlaylists); } if (dynamic_cast(w)) { return static_cast(w)->selectedFiles(allowPlaylists); } return QStringList(); } QList StackedPageWidget::selectedSongs(bool allowPlaylists) const { QWidget *w=currentWidget(); if (dynamic_cast(w)) { return static_cast(w)->selectedSongs(allowPlaylists); } if (dynamic_cast(w)) { return static_cast(w)->selectedSongs(allowPlaylists); } return QList(); } void StackedPageWidget::addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority) { QWidget *w=currentWidget(); if (dynamic_cast(w)) { return static_cast(w)->addSelectionToPlaylist(name, action, priorty, decreasePriority); } if (dynamic_cast(w)) { return static_cast(w)->addSelectionToPlaylist(name, action, priorty, decreasePriority); } } void StackedPageWidget::removeItems() { QWidget *w=currentWidget(); if (dynamic_cast(w)) { static_cast(w)->removeItems(); } else if (dynamic_cast(w)) { static_cast(w)->removeItems(); } } cantata-2.2.0/widgets/stackedpagewidget.h000066400000000000000000000030361316350454000204270ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STACKED_PAGE_WIDGET_H #define STACKED_PAGE_WIDGET_H #include #include #include "mpd-interface/song.h" #include "gui/page.h" class Icon; class SelectorButton; class SizeWidget; class QLabel; class StackedPageWidget : public QStackedWidget, public Page { Q_OBJECT public: StackedPageWidget(QWidget *p); virtual ~StackedPageWidget(); void setView(int v); void focusSearch(); QStringList selectedFiles(bool allowPlaylists) const; QList selectedSongs(bool allowPlaylists) const; void addSelectionToPlaylist(const QString &name, int action, quint8 priorty, bool decreasePriority); void removeItems(); Q_SIGNALS: void close(); }; #endif cantata-2.2.0/widgets/statelabel.h000066400000000000000000000023611316350454000170700ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef STATELABEL_H #define STATELABEL_H #include "support/buddylabel.h" class StateLabel : public BuddyLabel { public: StateLabel(QWidget *parent=0) : BuddyLabel(parent) , on(false) { } void setOn(bool o) { if (o!=on) { setStyleSheet(o ? QLatin1String("QLabel { color : red; }") : QString()); on=o; } } private: bool on; }; #endif cantata-2.2.0/widgets/stretchheaderview.cpp000066400000000000000000000210701316350454000210210ustar00rootroot00000000000000/* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #include "stretchheaderview.h" //#include "core/logging.h" #include #include #include #include const int StretchHeaderView::kMinimumColumnWidth = 10; const int StretchHeaderView::kMagicNumber = 0x502c951f; static const int constPrevMagicNumber=0x502c950f; StretchHeaderView::StretchHeaderView(Qt::Orientation orientation, QWidget* parent) : QHeaderView(orientation, parent), stretch_enabled_(false), in_mouse_move_event_(false) { connect(this, SIGNAL(sectionResized(int,int,int)), SLOT(SectionResized(int,int,int))); } void StretchHeaderView::setModel(QAbstractItemModel* model) { QHeaderView::setModel(model); if (stretch_enabled_) { column_widths_.resize(count()); std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count()); } } void StretchHeaderView::NormaliseWidths(const QList& sections) { if (!stretch_enabled_) return; const ColumnWidthType total_sum = std::accumulate(column_widths_.begin(), column_widths_.end(), 0.0); ColumnWidthType selected_sum = total_sum; if (!sections.isEmpty()) { selected_sum = 0.0; for (int i=0 ; i& sections) { if (!stretch_enabled_) return; ColumnWidthType total_w = 0.0; for (int i=0 ; i 0.5) pixels ++; total_w += w; if (!sections.isEmpty() && !sections.contains(i)) continue; if (pixels == 0 && !isSectionHidden(i)) hideSection(i); else if (pixels != 0 && isSectionHidden(i)) { showSection(i); AssertMinimalColumnWidth(i); } if (pixels != 0) resizeSection(i, pixels); } } void StretchHeaderView::HideSection(int logical) { // Would this hide the last section? bool all_hidden = true; for (int i=0 ; i 0) { all_hidden = false; break; } } if (all_hidden) { return; } if (!stretch_enabled_) { hideSection(logical); return; } column_widths_[logical] = 0.0; NormaliseWidths(); UpdateWidths(); } void StretchHeaderView::ShowSection(int logical) { if (!stretch_enabled_) { showSection(logical); return; } // How many sections are visible already? int visible_count = 0; for (int i=0 ; i logical_sections_to_resize; for (int i=0 ; i visual) logical_sections_to_resize << i; } // Resize just those columns if (!logical_sections_to_resize.isEmpty()) { in_mouse_move_event_ = false; UpdateWidths(logical_sections_to_resize); NormaliseWidths(logical_sections_to_resize); in_mouse_move_event_ = true; } } } void StretchHeaderView::ToggleStretchEnabled() { SetStretchEnabled(!is_stretch_enabled()); } void StretchHeaderView::SetStretchEnabled(bool enabled) { stretch_enabled_ = enabled; if (enabled) { // Initialise the list of widths from the current state of the widget column_widths_.resize(count()); for (int i=0 ; i other_columns; for (int i=0 ; i> magic_number; if ((magic_number != kMagicNumber && magic_number!=constPrevMagicNumber) || s.atEnd()) { return false; } QList pixel_widths; QList visual_indices; QList alignments; int sort_indicator_order = Qt::AscendingOrder; int sort_indicator_section = 0; int version = 0; s >> stretch_enabled_; s >> pixel_widths; s >> visual_indices; if (magic_number == kMagicNumber) { s >> alignments; s >> version; } s >> column_widths_; s >> sort_indicator_order; s >> sort_indicator_section; setSortIndicator(sort_indicator_section, Qt::SortOrder(sort_indicator_order)); const int persisted_column_count = qMin(qMin(visual_indices.count(), pixel_widths.count()), column_widths_.count()); QAbstractItemModel *m=model(); // Set column visible state, visual indices and, if we're not in stretch mode, // pixel widths. for (int i=0 ; isetHeaderData(i, Qt::Horizontal, alignments[i], Qt::TextAlignmentRole); } } // Have we added more columns since the last time? while (column_widths_.count() < count()) { column_widths_ << 0; } if (stretch_enabled_) { // In stretch mode, we've already set the proportional column widths so apply // them now. UpdateWidths(); } emit StretchEnabledChanged(stretch_enabled_); return true; } QByteArray StretchHeaderView::SaveState() const { QByteArray ret; QDataStream s(&ret, QIODevice::WriteOnly); QList pixel_widths; QList visual_indices; QList alignments; QAbstractItemModel *m=model(); for (int i=0 ; iheaderData(i, Qt::Horizontal, Qt::TextAlignmentRole).toInt() : -1); } s.setVersion(QDataStream::Qt_4_6); s << kMagicNumber; s << stretch_enabled_; s << pixel_widths; s << visual_indices; s << alignments; s << 0; // For future use... s << column_widths_; s << int(sortIndicatorOrder()); s << sortIndicatorSection(); return ret; } cantata-2.2.0/widgets/stretchheaderview.h000066400000000000000000000062621316350454000204740ustar00rootroot00000000000000/* This file is part of Clementine. Copyright 2010, David Sansome Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that 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 Clementine. If not, see . */ #ifndef STRETCHHEADERVIEW_H #define STRETCHHEADERVIEW_H #include class StretchHeaderView : public QHeaderView { Q_OBJECT public: StretchHeaderView(Qt::Orientation orientation, QWidget* parent = 0); typedef double ColumnWidthType; static const int kMinimumColumnWidth; static const int kMagicNumber; void setModel(QAbstractItemModel* model); // Serialises the proportional and actual column widths. Use these instead // of QHeaderView::restoreState and QHeaderView::saveState to persist the // proportional values directly and avoid floating point errors over time. bool RestoreState(const QByteArray& data); QByteArray SaveState() const; // Hides a section and resizes all other sections to fill the gap. Does // nothing if you try to hide the last section. void HideSection(int logical); // Shows a section and resizes all other sections to make room. void ShowSection(int logical); // Calls either HideSection or ShowSection. void SetSectionHidden(int logical, bool hidden); // Sets the width of the given column and resizes other columns appropriately. // width is the proportion of the entire width from 0.0 to 1.0. void SetColumnWidth(int logical, ColumnWidthType width); bool is_stretch_enabled() const { return stretch_enabled_; } public slots: // Changes the stretch mode. Enabling stretch mode will initialise the // proportional column widths from the current state of the header. void ToggleStretchEnabled(); void SetStretchEnabled(bool enabled); signals: // Emitted when the stretch mode is changed. void StretchEnabledChanged(bool enabled); protected: // QWidget void mouseMoveEvent(QMouseEvent* e); void resizeEvent(QResizeEvent* event); private: // If the width of the given column is less than a sensible threshold, resize // it to make it bigger. Workaround for a QHeaderView oddity that means a // column can be visible but with a width of 0. void AssertMinimalColumnWidth(int logical); // Scales column_widths_ values so the total is 1.0. void NormaliseWidths(const QList& sections = QList()); // Resizes the actual columns to make them match the proportional values // in column_widths_. void UpdateWidths(const QList& sections = QList()); private slots: void SectionResized(int logical, int old_size, int new_size); private: bool stretch_enabled_; QVector column_widths_; bool in_mouse_move_event_; }; #endif // STRETCHHEADERVIEW_H cantata-2.2.0/widgets/tableview.cpp000066400000000000000000000217301316350454000172660ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tableview.h" #include "stretchheaderview.h" #include "basicitemdelegate.h" #include "ratingwidget.h" #include "support/configuration.h" #include "models/roles.h" #include "mpd-interface/song.h" #include #include #include #include #include class TableViewItemDelegate : public BasicItemDelegate { public: TableViewItemDelegate(QObject *p, int rc) : BasicItemDelegate(p), ratingCol(rc), ratingPainter(0) { } virtual ~TableViewItemDelegate() { delete ratingPainter; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } QStyleOptionViewItem v4((QStyleOptionViewItem &)option); if (QStyleOptionViewItem::Beginning==v4.viewItemPosition) { v4.icon=index.data(Cantata::Role_Decoration).value(); if (!v4.icon.isNull()) { v4.features |= QStyleOptionViewItem::HasDecoration; v4.decorationSize=v4.icon.actualSize(option.decorationSize, QIcon::Normal, QIcon::Off); } } BasicItemDelegate::paint(painter, v4, index); if (index.column()==ratingCol) { Song song=index.data(Cantata::Role_SongWithRating).value(); if (song.rating>0 && song.rating<=Song::Rating_Max) { const QRect &r=option.rect; if (!ratingPainter) { ratingPainter=new RatingPainter(r.height()-4); ratingPainter->setColor(option.palette.color(QPalette::Active, QPalette::Text)); } painter->save(); painter->setOpacity(painter->opacity()*0.75); painter->setClipRect(r); const QSize &ratingSize=ratingPainter->size(); QRect ratingRect(r.x()+(r.width()-ratingSize.width())/2, r.y()+(r.height()-ratingSize.height())/2, ratingSize.width(), ratingSize.height()); ratingPainter->paint(painter, ratingRect, song.rating); painter->restore(); } } } int ratingCol; mutable RatingPainter *ratingPainter; }; TableView::TableView(const QString &cfgGroup, QWidget *parent, bool menuAlwaysAllowed) : TreeView(parent, menuAlwaysAllowed) , menu(0) , configGroup(cfgGroup) , menuIsForCol(-1) { setContextMenuPolicy(Qt::CustomContextMenu); setAcceptDrops(true); setDragDropOverwriteMode(false); setDragDropMode(QAbstractItemView::DragDrop); setSelectionMode(QAbstractItemView::ExtendedSelection); setDropIndicatorShown(true); setUniformRowHeights(true); setAttribute(Qt::WA_MouseTracking, true); StretchHeaderView *hdr=new StretchHeaderView(Qt::Horizontal, this); setHeader(hdr); connect(hdr, SIGNAL(StretchEnabledChanged(bool)), SLOT(stretchToggled(bool))); } void TableView::setModel(QAbstractItemModel *m) { if (dynamic_cast(itemDelegate())) { itemDelegate()->deleteLater(); } bool ok=false; int col=m ? m->data(QModelIndex(), Cantata::Role_RatingCol).toInt(&ok) : -1; if (ok && col>=0) { setItemDelegate(new TableViewItemDelegate(this, col)); } TreeView::setModel(m); } static const QLatin1String constHeaderKey("tableHeader"); void TableView::initHeader() { if (!model()) { return; } StretchHeaderView *hdr=qobject_cast(header()); QList hideable; QList initiallyHidden; if (!menu) { hdr->SetStretchEnabled(true); stretchToggled(true); hdr->setContextMenuPolicy(Qt::CustomContextMenu); for (int i=0; icolumnCount(); ++i) { hdr->SetColumnWidth(i, model()->headerData(i, Qt::Horizontal, Cantata::Role_Width).toDouble()); if (model()->headerData(i, Qt::Horizontal, Cantata::Role_Hideable).toBool()) { hideable.append(i); } if (model()->headerData(i, Qt::Horizontal, Cantata::Role_InitiallyHidden).toBool()) { initiallyHidden.append(i); } } hdr->setSectionsMovable(true); connect(hdr, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); } //Restore state Configuration config(configGroup); QByteArray state=config.get(constHeaderKey, QByteArray()); if (state.isEmpty()) { foreach (int i, initiallyHidden) { hdr->HideSection(i); } } else { hdr->RestoreState(state); } if (!menu) { menu = new QMenu(this); QAction *stretch=new QAction(tr("Stretch Columns To Fit Window"), this); stretch->setCheckable(true); stretch->setChecked(hdr->is_stretch_enabled()); connect(stretch, SIGNAL(toggled(bool)), hdr, SLOT(SetStretchEnabled(bool))); menu->addAction(stretch); QMenu *alignmentMenu = new QMenu(menu); QActionGroup *alignGroup = new QActionGroup(alignmentMenu); alignLeftAction = new QAction(tr("Left"), alignGroup); alignCenterAction = new QAction(tr("Center"), alignGroup); alignRightAction = new QAction(tr("Right"), alignGroup); alignLeftAction->setCheckable(true); alignCenterAction->setCheckable(true); alignRightAction->setCheckable(true); alignmentMenu->addActions(alignGroup->actions()); connect(alignGroup, SIGNAL(triggered(QAction*)), SLOT(alignmentChanged())); alignAction=new QAction(tr("Alignment"), menu); alignAction->setMenu(alignmentMenu); menu->addAction(alignAction); menu->addSeparator(); foreach (int col, hideable) { QAction *act=new QAction(model()->headerData(col, Qt::Horizontal, Cantata::Role_ContextMenuText).toString(), menu); act->setCheckable(true); act->setChecked(!hdr->isSectionHidden(col)); menu->addAction(act); act->setData(col); connect(act, SIGNAL(toggled(bool)), this, SLOT(toggleHeaderItem(bool))); } } } void TableView::saveHeader() { if (menu && model()) { Configuration(configGroup).set(constHeaderKey, qobject_cast(header())->SaveState()); } } void TableView::scrollTo(const QModelIndex &index, ScrollHint hint) { QHeaderView *hdr=header(); // scrollTo does not work if column hidden, so find first one that is not. if (hdr && hdr->isSectionHidden(index.column())) { for (int i=0; icolumnCount(); ++i) { if (!hdr->isSectionHidden(i)) { TreeView::scrollTo(model()->index(index.row(), i, index.parent()), hint); return; } } } TreeView::scrollTo(index, hint); } void TableView::showMenu(const QPoint &pos) { menuIsForCol=header()->logicalIndexAt(pos); alignAction->setEnabled(-1!=menuIsForCol); if (-1!=menuIsForCol) { Qt::Alignment al = (Qt::AlignmentFlag)model()->headerData(menuIsForCol, Qt::Horizontal, Qt::TextAlignmentRole).toInt(); if (al&Qt::AlignLeft) { alignLeftAction->setChecked(true); } else if (al&Qt::AlignHCenter) { alignCenterAction->setChecked(true); } else if (al&Qt::AlignRight) { alignRightAction->setChecked(true); } } menu->exec(mapToGlobal(pos)); } void TableView::toggleHeaderItem(bool visible) { QAction *act=qobject_cast(sender()); if (act) { int index=act->data().toInt(); if (-1!=index) { qobject_cast(header())->SetSectionHidden(index, !visible); } } } void TableView::stretchToggled(bool e) { setHorizontalScrollBarPolicy(e ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAsNeeded); } void TableView::alignmentChanged() { if (-1!=menuIsForCol) { int al=alignLeftAction->isChecked() ? Qt::AlignLeft : alignRightAction->isChecked() ? Qt::AlignRight : Qt::AlignHCenter; if (model()->setHeaderData(menuIsForCol, Qt::Horizontal, al, Qt::TextAlignmentRole)) { header()->reset(); } } } cantata-2.2.0/widgets/tableview.h000066400000000000000000000031161316350454000167310ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TABLEVIEW_H #define TABLEVIEW_H #include "treeview.h" class TableView : public TreeView { Q_OBJECT public: TableView(const QString &cfgGroup, QWidget *parent=0, bool menuAlwaysAllowed=false); virtual ~TableView() { } void setModel(QAbstractItemModel *m); void initHeader(); void saveHeader(); virtual void scrollTo(const QModelIndex &index, ScrollHint hint); private Q_SLOTS: void showMenu(const QPoint &pos); void toggleHeaderItem(bool visible); void stretchToggled(bool e); void alignmentChanged(); protected: QMenu *menu; QString configGroup; int menuIsForCol; QAction *alignAction; QAction *alignLeftAction; QAction *alignCenterAction; QAction *alignRightAction; }; #endif cantata-2.2.0/widgets/tagspinbox.cpp000066400000000000000000000030601316350454000174560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tagspinbox.h" #include #include static QString variousText; QString TagSpinBox::variousStr() { return variousText; } TagSpinBox::TagSpinBox(QWidget *parent) : EmptySpinBox(parent) , isVarious(false) { if (variousText.isEmpty()) { variousText=tr("(Various)"); } } QSize TagSpinBox::sizeHint() const { TagSpinBox *that=const_cast(this); that->setSpecialValueText(variousText); QSize sz=EmptySpinBox::sizeHint(); that->setSpecialValueText(QString()); return sz; } void TagSpinBox::setVarious(bool v) { if (v==isVarious) { return; } isVarious=v; lineEdit()->setPlaceholderText(isVarious ? variousText : QString()); } cantata-2.2.0/widgets/tagspinbox.h000066400000000000000000000021741316350454000171300ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TAGSPINBOX_H #define TAGSPINBOX_H #include "emptyspinbox.h" class TagSpinBox : public EmptySpinBox { Q_OBJECT public: static QString variousStr(); TagSpinBox(QWidget *parent); QSize sizeHint() const; void setVarious(bool v); private: bool isVarious; }; #endif cantata-2.2.0/widgets/textbrowser.cpp000066400000000000000000000042231316350454000176720ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "textbrowser.h" #include #include TextBrowser::TextBrowser(QWidget *p) : QTextBrowser(p) { origZoomValue=font().pointSize(); } // QTextEdit/QTextBrowser seems to do FastTransformation when scaling images, and this looks bad. QVariant TextBrowser::loadResource(int type, const QUrl &name) { if (QTextDocument::ImageResource==type) { if ((name.scheme().isEmpty() || QLatin1String("file")==name.scheme())) { QImage img; img.load(name.path()); if (!img.isNull()) { return img.scaled(picSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } } else if (QLatin1String("data")==name.scheme()) { QByteArray encoded=name.toEncoded(); static const QString constStart("data:image/png;base64,"); encoded=QByteArray::fromBase64(encoded.mid(constStart.length())); QImage img; img.loadFromData(encoded); if (!img.isNull()) { return img.scaled(picSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } } } return QTextBrowser::loadResource(type, name); } void TextBrowser::setPal(const QPalette &pal) { setPalette(pal); verticalScrollBar()->setPalette(pal); horizontalScrollBar()->setPalette(pal); } cantata-2.2.0/widgets/textbrowser.h000066400000000000000000000025321316350454000173400ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TEXTBROWSER_H #define TEXTBROWSER_H #include class TextBrowser : public QTextBrowser { public: TextBrowser(QWidget *p); QVariant loadResource(int type, const QUrl &name); void setZoom(int diff) { if (diff) zoomIn(diff); } int zoom() const { return font().pointSize()-origZoomValue; } void setPicSize(const QSize &p) { pSize=p; } QSize picSize() const { return pSize; } void setPal(const QPalette &pal); private: int origZoomValue; QSize pSize; }; #endif cantata-2.2.0/widgets/thinsplitterhandle.cpp000066400000000000000000000051731316350454000212140ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "thinsplitterhandle.h" #include "support/utils.h" #include #include ThinSplitterHandle::ThinSplitterHandle(Qt::Orientation orientation, QSplitter *parent) : QSplitterHandle(orientation, parent) , highlightUnderMouse(false) , underMouse(false) { sz=Utils::scaleForDpi(4); updateMask(); setAttribute(Qt::WA_MouseNoMask, true); } void ThinSplitterHandle::resizeEvent(QResizeEvent *event) { updateMask(); if (event->size()!=size()) { QSplitterHandle::resizeEvent(event); } } void ThinSplitterHandle::paintEvent(QPaintEvent *event) { if (underMouse) { QColor col(palette().highlight().color()); QPainter p(this); int width=Utils::scaleForDpi(2); QRect r=event->rect(); r=QRect(r.x()+((r.width()-width)/2), r.y(), width, r.height()); col.setAlphaF(0.5); p.fillRect(r, col); col.setAlphaF(0.1); p.fillRect(r.adjusted(-(width/2), 0, width/2, 0), col); } } bool ThinSplitterHandle::event(QEvent *event) { if (highlightUnderMouse) { switch(event->type()) { case QEvent::Enter: case QEvent::HoverEnter: underMouse = true; update(); break; case QEvent::ContextMenu: case QEvent::Leave: case QEvent::HoverLeave: underMouse = false; update(); break; default: break; } } return QWidget::event(event); } void ThinSplitterHandle::updateMask() { if (Qt::Horizontal==orientation()) { setContentsMargins(sz, 0, sz, 0); setMask(QRegion(contentsRect().adjusted(-sz, 0, sz, 0))); } else { setContentsMargins(0, sz, 0, sz); setMask(QRegion(contentsRect().adjusted(0, -sz, 0, sz))); } } cantata-2.2.0/widgets/thinsplitterhandle.h000066400000000000000000000026571316350454000206650ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef THIN_SPLITTER_HANDLE_H #define THIN_SPLITTER_HANDLE_H #include class ThinSplitterHandle : public QSplitterHandle { public: ThinSplitterHandle(Qt::Orientation orientation, QSplitter *parent); virtual ~ThinSplitterHandle() { } void resizeEvent(QResizeEvent *event); void paintEvent(QPaintEvent *event); bool event(QEvent *event); QSize sizeHint() const { return QSize(0, 0); } void setHighlightUnderMouse() { highlightUnderMouse=true; } private: void updateMask(); private: int sz; bool highlightUnderMouse; bool underMouse; }; #endif cantata-2.2.0/widgets/titlewidget.cpp000066400000000000000000000213541316350454000176330ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "titlewidget.h" #include "support/squeezedtextlabel.h" #include "support/utils.h" #include "support/icon.h" #include "gui/stdactions.h" #include "toolbutton.h" #ifdef Q_OS_MAC #include "support/osxstyle.h" #endif #include "gui/covers.h" #include #include #include #include #include #include #include #include static int twHeight=-1; TitleWidget::TitleWidget(QWidget *p) : QWidget(p) , pressed(false) , underMouse(false) , controls(0) { QGridLayout *layout=new QGridLayout(this); QVBoxLayout *textLayout=new QVBoxLayout(0); image=new QLabel(this); mainText=new SqueezedTextLabel(this); subText=new SqueezedTextLabel(this); QLabel *chevron=new QLabel(QChar(Qt::RightToLeft==layoutDirection() ? 0x203A : 0x2039), this); QFont f=mainText->font(); subText->setFont(Utils::smallFont(f)); mainText->setFont(f); if (f.pixelSize()>0) { f.setPixelSize(f.pixelSize()*2); } else { f.setPointSizeF(f.pointSizeF()*2); } QPalette pal=mainText->palette(); QColor col(mainText->palette().windowText().color()); col.setAlphaF(0.5); pal.setColor(QPalette::WindowText, col); subText->setPalette(pal); chevron->setFont(f); int spacing=Utils::layoutSpacing(this); mainText->ensurePolished(); subText->ensurePolished(); int size=mainText->sizeHint().height()+subText->sizeHint().height()+spacing; if (size<72) { size=Icon::stdSize(size); } int pad=Utils::scaleForDpi(6); size=qMax(qMax(size, QFontMetrics(mainText->font()).height()+QFontMetrics(subText->font()).height()+spacing), Utils::scaleForDpi(40))+pad; image->setFixedSize(size, size); setToolTip(tr("Click to go back")); spacing=qMin(4, spacing-1); layout->addItem(new QSpacerItem(spacing, spacing), 0, 0, 2, 1); layout->addWidget(chevron, 0, 1, 2, 1); layout->addWidget(image, 0, 2, 2, 1); textLayout->addWidget(mainText); textLayout->addWidget(subText); layout->addItem(textLayout, 0, 3, 2, 1); mainText->installEventFilter(this); subText->installEventFilter(this); image->installEventFilter(this); installEventFilter(this); setAttribute(Qt::WA_Hover); connect(Covers::self(), SIGNAL(cover(Song,QImage,QString)), this, SLOT(coverRetrieved(Song,QImage,QString))); connect(Covers::self(), SIGNAL(coverUpdated(Song,QImage,QString)), this, SLOT(coverRetrieved(Song,QImage,QString))); connect(Covers::self(), SIGNAL(artistImage(Song,QImage,QString)), this, SLOT(coverRetrieved(Song,QImage,QString))); layout->setMargin(0); layout->setSpacing(spacing); textLayout->setMargin(0); textLayout->setSpacing(spacing); mainText->setAlignment(Qt::AlignBottom); subText->setAlignment(Qt::AlignTop); image->setAlignment(Qt::AlignCenter); chevron->setAlignment(Qt::AlignCenter); chevron->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); if (-1==twHeight) { ToolButton tb; twHeight=qMax((tb.iconSize().height()*2), size); } setFixedHeight(twHeight); } void TitleWidget::update(const Song &sng, const QIcon &icon, const QString &text, const QString &sub, bool showControls) { song=sng; image->setVisible(true); mainText->setText(text); subText->setText(sub); if (!showControls) { if (controls) { controls->setVisible(false); } } else { if (!controls) { controls=new QWidget(this); QVBoxLayout *l=new QVBoxLayout(controls); l->setMargin(0); l->setSpacing(0); ToolButton *add=new ToolButton(this); ToolButton *replace=new ToolButton(this); add->QAbstractButton::setIcon(StdActions::self()->appendToPlayQueueAction->icon()); replace->QAbstractButton::setIcon(StdActions::self()->replacePlayQueueAction->icon()); int size=qMax(add->iconSize().height()+6, (height()/2)-1); if (size>(height()-1)) { size--; } add->setFixedSize(QSize(size, size)); replace->setFixedSize(add->size()); add->setToolTip(tr("Add All To Play Queue")); replace->setToolTip(tr("Add All And Replace Play Queue")); l->addWidget(replace); l->addWidget(add); connect(add, SIGNAL(clicked()), this, SIGNAL(addToPlayQueue())); connect(replace, SIGNAL(clicked()), this, SIGNAL(replacePlayQueue())); static_cast(layout())->addWidget(controls, 0, 4, 2, 1); } controls->setVisible(true); } subText->setVisible(!sub.isEmpty()); mainText->setAlignment(sub.isEmpty() ? Qt::AlignVCenter : Qt::AlignBottom); if (!sng.isEmpty()) { Covers::Image cImg=Covers::self()->requestImage(sng, true); if (!cImg.img.isNull()) { setImage(cImg.img); return; } } if (icon.isNull()) { image->setVisible(false); } else { int iconPad=Utils::scaleForDpi(8); int iconSize=image->width()-iconPad; if (iconSize<44 && iconSize>=32) { iconSize=32; } double dpr=DEVICE_PIXEL_RATIO(); QPixmap pix=Icon::getScaledPixmap(icon, iconSize*dpr, iconSize*dpr, 96*dpr); pix.setDevicePixelRatio(dpr); image->setPixmap(pix); } } bool TitleWidget::eventFilter(QObject *o, QEvent *event) { switch(event->type()) { case QEvent::HoverEnter: if (isEnabled() && o==this) { /* #ifdef Q_OS_MAC setStyleSheet(QString("QLabel{color:%1;}").arg(OSXStyle::self()->viewPalette().highlight().color().name())); #else setStyleSheet(QLatin1String("QLabel{color:palette(highlight);}")); #endif */ underMouse = true; } break; case QEvent::HoverLeave: if (isEnabled() && o==this) { //setStyleSheet(QString()); underMouse = false; } break; case QEvent::MouseButtonPress: if (Qt::LeftButton==static_cast(event)->button() && Qt::NoModifier==static_cast(event)->modifiers()) { pressed=true; } break; case QEvent::MouseButtonRelease: if (pressed && Qt::LeftButton==static_cast(event)->button() && !QApplication::overrideCursor()) { actions().first()->trigger(); } pressed=false; break; default: break; } return QWidget::eventFilter(o, event); } void TitleWidget::paintEvent(QPaintEvent *e) { if (pressed || underMouse) { QPainter p(this); #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #else QColor col = palette().highlight().color(); #endif QPainterPath path = Utils::buildPath(rect().adjusted(1, 1, -1, -1), 2.0); p.setRenderHint(QPainter::Antialiasing); col.setAlphaF(pressed ? 0.5 : 0.2); p.fillPath(path, col); } QWidget::paintEvent(e); } void TitleWidget::coverRetrieved(const Song &s, const QImage &img, const QString &file) { Q_UNUSED(file); if (song.isEmpty() || img.isNull()) { return; } if (song.artistOrComposer()!=s.artistOrComposer()) { return; } if (s.isArtistImageRequest()!=song.isArtistImageRequest()) { return; } if (s.isComposerImageRequest()!=song.isComposerImageRequest()) { return; } if (!s.isComposerImageRequest() && !s.isArtistImageRequest() && s.album!=song.album) { return; } setImage(img); } void TitleWidget::setImage(const QImage &img) { double dpr=DEVICE_PIXEL_RATIO(); QPixmap pix=QPixmap::fromImage(img.scaled((image->width()-6)*dpr, (image->height()-6)*dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation)); pix.setDevicePixelRatio(dpr); image->setPixmap(pix); } cantata-2.2.0/widgets/titlewidget.h000066400000000000000000000033631316350454000173000ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TITLE_WIDGET_H #define TITLE_WIDGET_H #include #include "mpd-interface/song.h" class QImage; class Icon; class SqueezedTextLabel; class QLabel; class TitleWidget : public QWidget { Q_OBJECT public: TitleWidget(QWidget *p); virtual ~TitleWidget() { } void update(const Song &sng, const QIcon &icon, const QString &text, const QString &sub, bool showControls=false); bool eventFilter(QObject *obj, QEvent *event); void paintEvent(QPaintEvent *event); Q_SIGNALS: void clicked(); void addToPlayQueue(); void replacePlayQueue(); private Q_SLOTS: void coverRetrieved(const Song &s, const QImage &img, const QString &file); private: void setImage(const QImage &img); private: Song song; bool pressed; bool underMouse; QLabel *back; QLabel *image; QWidget *controls; SqueezedTextLabel *mainText; SqueezedTextLabel *subText; }; #endif cantata-2.2.0/widgets/toolbutton.cpp000066400000000000000000000061421316350454000175150ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "toolbutton.h" #include "support/icon.h" #include "support/gtkstyle.h" #include "config.h" #include "support/utils.h" #include #include #include #include #include ToolButton::ToolButton(QWidget *parent) : QToolButton(parent) { Icon::init(this); #ifdef Q_OS_MAC setStyleSheet("QToolButton {border: 0}"); allowMouseOver=parent && parent->objectName()!=QLatin1String("toolbar"); #endif setFocusPolicy(Qt::NoFocus); } void ToolButton::paintEvent(QPaintEvent *e) { #ifdef Q_OS_MAC bool down=isDown() || isChecked(); bool mo=false; if (allowMouseOver && !down && isEnabled()) { QStyleOptionToolButton opt; initStyleOption(&opt); mo=opt.state&QStyle::State_MouseOver && this==QApplication::widgetAt(QCursor::pos()); } if (down || mo) { QPainter p(this); QColor col(palette().color(QPalette::WindowText)); QRect r(rect()); QPainterPath path=Utils::buildPath(QRectF(r.x()+1.5, r.y()+1.5, r.width()-3, r.height()-3), 2.5); p.setRenderHint(QPainter::Antialiasing, true); col.setAlphaF(0.4); p.setPen(col); p.drawPath(path); if (down) { col.setAlphaF(0.1); p.fillPath(path, col); } } #endif Q_UNUSED(e) // Hack to work-around Qt5 sometimes leaving toolbutton in 'raised' state. QStylePainter p(this); QStyleOptionToolButton opt; initStyleOption(&opt); opt.features=QStyleOptionToolButton::None; if (opt.state&QStyle::State_MouseOver && this!=QApplication::widgetAt(QCursor::pos())) { opt.state&=~QStyle::State_MouseOver; } p.drawComplexControl(QStyle::CC_ToolButton, opt); } QSize ToolButton::sizeHint() const { if (!sh.isValid()) { ensurePolished(); sh = QToolButton::sizeHint(); if (sh.width()>sh.height()) { sh.setWidth(sh.height()); } sh=QSize(qMax(sh.width(), sh.height()), qMax(sh.width(), sh.height())); #ifdef Q_OS_MAC sh=QSize(qMax(sh.width(), 22), qMax(sh.height(), 20)); #endif } return sh; } void ToolButton::setMenu(QMenu *m) { QToolButton::setMenu(m); sh=QSize(); setPopupMode(InstantPopup); } cantata-2.2.0/widgets/toolbutton.h000066400000000000000000000023531316350454000171620ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TOOLBUTTON_H #define TOOLBUTTON_H #include #include "support/icon.h" class QMenu; class ToolButton : public QToolButton { public: explicit ToolButton(QWidget *parent = 0); QSize sizeHint() const; void setMenu(QMenu *m); void paintEvent(QPaintEvent *e); private: mutable QSize sh; #ifdef Q_OS_MAC bool allowMouseOver; #endif }; #endif // MENUBUTTON_H cantata-2.2.0/widgets/treeview.cpp000066400000000000000000000253451316350454000171440ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "treeview.h" #include "models/roles.h" #include "icons.h" #include "config.h" #include "basicitemdelegate.h" #include "support/utils.h" #include "mpd-interface/song.h" #include #include #include #include #include #include #include #include #include #include #define SINGLE_CLICK style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this) QImage TreeView::setOpacity(const QImage &orig, double opacity) { QImage img=QImage::Format_ARGB32==orig.format() ? orig : orig.convertToFormat(QImage::Format_ARGB32); uchar *bits = img.bits(); for (int i = 0; i < img.height()*img.bytesPerLine(); i+=4) { if (0!=bits[i+3]) { bits[i+3]*=opacity; } } return img; } QPixmap TreeView::createBgndPixmap(const QIcon &icon) { if (icon.isNull()) { return QPixmap(); } static int bgndSize=0; if (0==bgndSize) { bgndSize=QApplication::fontMetrics().height()*16; } QImage img=icon.pixmap(bgndSize, bgndSize).toImage(); if (img.width()!=bgndSize && img.height()!=bgndSize) { img=img.scaled(bgndSize, bgndSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } return QPixmap::fromImage(setOpacity(img, 0.075)); } static bool forceSingleClick=true; void TreeView::setForceSingleClick(bool v) { forceSingleClick=v; } bool TreeView::getForceSingleClick() { return forceSingleClick; } TreeView::TreeView(QWidget *parent, bool menuAlwaysAllowed) : QTreeView(parent) , eventFilter(0) , forceSingleColumn(false) , alwaysAllowMenu(menuAlwaysAllowed) { setDragEnabled(true); setContextMenuPolicy(Qt::NoContextMenu); // setRootIsDecorated(false); setAllColumnsShowFocus(true); setAlternatingRowColors(false); setSelectionMode(QAbstractItemView::ExtendedSelection); setSelectionBehavior(QAbstractItemView::SelectRows); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); // Treeview does not seem to need WA_MouseTracking set, even with QGtkStyle items still // highlight under mouse. And enabling WA_MouseTracking here seems to cause drag-n-drop // errors if an item is dragged onto playqueue whilst playqueue has a selected item! // BUG:145 //setAttribute(Qt::WA_MouseTracking); if (SINGLE_CLICK) { connect(this, SIGNAL(activated(const QModelIndex &)), this, SIGNAL(itemActivated(const QModelIndex &))); } } TreeView::~TreeView() { } void TreeView::setPageDefaults() { sortByColumn(0, Qt::AscendingOrder); setHeaderHidden(true); setDragDropMode(QAbstractItemView::DragOnly); setSortingEnabled(true); setAnimated(true); forceSingleColumn=true; } void TreeView::setExpandOnClick() { connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemWasClicked(const QModelIndex &))); } void TreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QTreeView::selectionChanged(selected, deselected); bool haveSelection=haveSelectedItems(); if (!alwaysAllowMenu) { setContextMenuPolicy(haveSelection ? Qt::ActionsContextMenu : Qt::NoContextMenu); } emit itemsSelected(haveSelection); } bool TreeView::haveSelectedItems() const { // Dont need the sorted type of 'selectedIndexes' here... return selectionModel() && selectionModel()->selectedIndexes().count()>0; } bool TreeView::haveUnSelectedItems() const { // Dont need the sorted type of 'selectedIndexes' here... return selectionModel() && model() && selectionModel()->selectedIndexes().count()!=model()->rowCount(); } void TreeView::drag(Qt::DropActions supportedActions, QAbstractItemView *view, const QModelIndexList &items) { if (items.count() > 0) { QMimeData *data = view->model()->mimeData(items); if (!data) { return; } QDrag *drag = new QDrag(view); drag->setMimeData(data); int pixSize=Icon::stdSize(Utils::scaleForDpi(32)); drag->setPixmap(Icon("audio-x-generic").pixmap(pixSize, pixSize)); drag->start(supportedActions); } } void TreeView::mouseReleaseEvent(QMouseEvent *event) { if (Qt::NoModifier==event->modifiers() && Qt::LeftButton==event->button()) { QTreeView::mouseReleaseEvent(event); } } QModelIndexList TreeView::selectedIndexes(bool sorted) const { if (!selectionModel()) { return QModelIndexList(); } if (sorted) { return sortIndexes(selectionModel()->selectedIndexes()); } else if (model() && model()->columnCount()>1) { QModelIndexList list=selectionModel()->selectedIndexes(); QModelIndexList sel; foreach (const QModelIndex &idx, list) { if (0==idx.column()) { sel.append(idx); } } return sel; } return selectionModel()->selectedIndexes(); } struct Index : public QModelIndex { Index(const QModelIndex &i) : QModelIndex(i) { QModelIndex idx=i; while (idx.isValid()) { rows.prepend(idx.row()); idx=idx.parent(); } count=rows.count(); } bool operator<(const Index &rhs) const { int toCompare=qMax(count, rhs.count); for (int i=0; iright) { return false; } } return false; } QList rows; int count; }; QModelIndexList TreeView::sortIndexes(const QModelIndexList &list) { if (list.isEmpty()) { return list; } // QModelIndex::operator< sorts on row first - but this messes things up if rows // have different parents. Therefore, we use the sort above - so that the hierarchy is preserved. // First, create the list of 'Index' items to be sorted... QList toSort; foreach (const QModelIndex &i, list) { if (0==i.column()) { toSort.append(Index(i)); } } // Call qSort on these - this will use operator< qSort(toSort); // Now convert the QList into a QModelIndexList QModelIndexList sorted; foreach (const Index &i, toSort) { sorted.append(i); } return sorted; } void TreeView::expandAll(const QModelIndex &idx, bool singleLevelOnly) { quint32 count=model()->rowCount(idx); for (quint32 i=0; iindex(i, 0, idx), singleLevelOnly); } } void TreeView::collapseToLevel(int level, const QModelIndex &idx) { quint32 count=model()->rowCount(idx); if (level) { for (quint32 i=0; iindex(i, 0, idx)); } } else { for (quint32 i=0; iindex(i, 0, idx)); } } } void TreeView::expand(const QModelIndex &idx, bool singleOnly) { if (idx.isValid()) { setExpanded(idx, true); if (!singleOnly) { quint32 count=model()->rowCount(idx); for (quint32 i=0; irowCount(idx); for (quint32 i=0; imapToGlobal(QPoint(rect.x(), rect.y()))); int itemIndentation = rect.x() - visualRect(rootIndex()).x(); rect = QRect(header()->sectionViewportPosition(0) + itemIndentation, rect.y(), style()->pixelMetric(QStyle::PM_IndicatorWidth), rect.height()); return rect.contains(QCursor::pos()); } void TreeView::setUseSimpleDelegate() { setItemDelegate(new BasicItemDelegate(this)); } void TreeView::setBackgroundImage(const QIcon &icon) { QPalette pal=parentWidget()->palette(); if (!icon.isNull()) { pal.setColor(QPalette::Base, Qt::transparent); } #ifndef Q_OS_MAC setPalette(pal); #endif viewport()->setPalette(pal); bgnd=createBgndPixmap(icon); } void TreeView::paintEvent(QPaintEvent *e) { if (!bgnd.isNull()) { QPainter p(viewport()); QSize sz=size(); p.fillRect(0, 0, sz.width(), sz.height(), QApplication::palette().color(QPalette::Base)); p.drawPixmap((sz.width()-bgnd.width())/2, (sz.height()-bgnd.height())/2, bgnd); } QTreeView::paintEvent(e); } void TreeView::setModel(QAbstractItemModel *m) { QAbstractItemModel *old=model(); QTreeView::setModel(m); if (forceSingleColumn && m) { int columnCount=m->columnCount(); if (columnCount>1) { QHeaderView *hdr=header(); for (int i=1; isetSectionHidden(i, true); } } } if (old) { disconnect(old, SIGNAL(layoutChanged()), this, SLOT(correctSelection())); } if (m && old!=m) { connect(m, SIGNAL(layoutChanged()), this, SLOT(correctSelection())); } } // Workaround for https://bugreports.qt-project.org/browse/QTBUG-18009 void TreeView::correctSelection() { if (!selectionModel()) { return; } QItemSelection s = selectionModel()->selection(); setCurrentIndex(currentIndex()); selectionModel()->select(s, QItemSelectionModel::SelectCurrent); } //void TreeView::itemWasActivated(const QModelIndex &index) //{ // if (!forceSingleClick) { // setExpanded(index, !isExpanded(index)); // } //} void TreeView::itemWasClicked(const QModelIndex &index) { if (forceSingleClick) { setExpanded(index, !isExpanded(index)); } } cantata-2.2.0/widgets/treeview.h000066400000000000000000000061151316350454000166030ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TREEVIEW_H #define TREEVIEW_H #include #include #include class QIcon; class TreeView : public QTreeView { Q_OBJECT public: static QImage setOpacity(const QImage &orig, double opacity=0.15); static QPixmap createBgndPixmap(const QIcon &icon); static void setForceSingleClick(bool v); static bool getForceSingleClick(); static QModelIndexList sortIndexes(const QModelIndexList &list); static void drag(Qt::DropActions supportedActions, QAbstractItemView *view, const QModelIndexList &items); TreeView(QWidget *parent=0, bool menuAlwaysAllowed=false); virtual ~TreeView(); void setPageDefaults(); void setExpandOnClick(); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); bool haveSelectedItems() const; bool haveUnSelectedItems() const; void startDrag(Qt::DropActions supportedActions) { drag(supportedActions, this, selectedIndexes()); } void mouseReleaseEvent(QMouseEvent *event); QModelIndexList selectedIndexes() const { return selectedIndexes(true); } QModelIndexList selectedIndexes(bool sorted) const; void expandAll(const QModelIndex &idx=QModelIndex(), bool singleLevelOnly=false); void collapseToLevel(int level, const QModelIndex &idx=QModelIndex()); virtual void expand(const QModelIndex &idx, bool singleOnly=false); virtual void collapse(const QModelIndex &idx, bool singleOnly=false); virtual void setModel(QAbstractItemModel *m); bool checkBoxClicked(const QModelIndex &idx) const; void setUseSimpleDelegate(); void setBackgroundImage(const QIcon &icon); void paintEvent(QPaintEvent *e); void setForceSingleColumn(bool f) { forceSingleColumn=f; } void installFilter(QObject *f) { eventFilter=f; installEventFilter(f); } QObject * filter() const { return eventFilter; } private Q_SLOTS: void correctSelection(); // void itemWasActivated(const QModelIndex &index); void itemWasClicked(const QModelIndex &index); Q_SIGNALS: void itemsSelected(bool); void itemActivated(const QModelIndex &index); // Only emitted if view is set to single-click private: QObject *eventFilter; bool forceSingleColumn; bool alwaysAllowMenu; QPixmap bgnd; }; #endif cantata-2.2.0/widgets/volumeslider.cpp000066400000000000000000000211671316350454000200220ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "volumeslider.h" #include "mpd-interface/mpdconnection.h" #include "mpd-interface/mpdstatus.h" #include "support/action.h" #include "support/actioncollection.h" #include "gui/stdactions.h" #include "support/utils.h" #include "gui/settings.h" #include #include #include #include #include #include #include #include #include class VolumeSliderProxyStyle : public QProxyStyle { public: VolumeSliderProxyStyle() : QProxyStyle() { setBaseStyle(qApp->style()); } int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const { if (SH_Slider_AbsoluteSetButtons==stylehint) { return Qt::LeftButton|QProxyStyle::styleHint(stylehint, opt, widget, returnData); } else { return QProxyStyle::styleHint(stylehint, opt, widget, returnData); } } }; static int widthStep=4; static int constHeightStep=2; VolumeSlider::VolumeSlider(QWidget *p) : QSlider(p) , lineWidth(0) , down(false) , fadingStop(false) , muteAction(0) , menu(0) { widthStep=4; setRange(0, 100); setPageStep(Settings::self()->volumeStep()); lineWidth=Utils::scaleForDpi(1); int w=lineWidth*widthStep*19; int h=lineWidth*constHeightStep*10; setFixedHeight(h+1); setFixedWidth(w); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setOrientation(Qt::Horizontal); setFocusPolicy(Qt::NoFocus); setStyle(new VolumeSliderProxyStyle()); setStyleSheet(QString("QSlider::groove:horizontal {border: 0px;} " "QSlider::sub-page:horizontal {border: 0px;} " "QSlider::handle:horizontal {width: 0px; height:0px; margin:0;}")); textCol=Utils::clampColor(palette().color(QPalette::Active, QPalette::Text)); generatePixmaps(); } void VolumeSlider::initActions() { if (muteAction) { return; } muteAction = ActionCollection::get()->createAction("mute", tr("Mute")); addAction(muteAction); connect(muteAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(toggleMute())); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateMpdStatus())); connect(StdActions::self()->increaseVolumeAction, SIGNAL(triggered()), this, SLOT(increaseVolume())); connect(StdActions::self()->decreaseVolumeAction, SIGNAL(triggered()), this, SLOT(decreaseVolume())); connect(this, SIGNAL(valueChanged(int)), MPDConnection::self(), SLOT(setVolume(int))); addAction(StdActions::self()->increaseVolumeAction); addAction(StdActions::self()->decreaseVolumeAction); } void VolumeSlider::setColor(QColor col) { col=Utils::clampColor(col); if (col!=textCol) { textCol=col; generatePixmaps(); } } void VolumeSlider::paintEvent(QPaintEvent *) { bool reverse=isRightToLeft(); QPainter p(this); bool muted=MPDConnection::self()->isMuted(); if (muted || !isEnabled()) { p.setOpacity(0.25); } p.drawPixmap(0, 0, pixmaps[0]); #if 1 int steps=(value()/10.0)+0.5; if (steps>0) { if (steps<10) { int wStep=widthStep*lineWidth; p.setClipRect(reverse ? QRect(width()-((steps*wStep*2)-wStep), 0, width(), height()) : QRect(0, 0, (steps*wStep*2)-wStep, height())); p.setClipping(true); } p.drawPixmap(0, 0, pixmaps[1]); if (steps<10) { p.setClipping(false); } } #else // Partial filling of each block? if (value()>0) { if (value()<100) { int fillWidth=(width()*(0.01*value()))+0.5; p.setClipRect(reverse ? QRect(width()-fillWidth, 0, width(), height()) : QRect(0, 0, fillWidth, height())); p.setClipping(true); } p.drawPixmap(0, 0, *(pixmaps[1])); if (value()<100) { p.setClipping(false); } } #endif if (!muted) { p.setOpacity(p.opacity()*0.75); p.setPen(textCol); QFont f(font()); f.setPixelSize(qMax(height()/2.5, 8.0)); p.setFont(f); QRect r=rect(); bool rtl=isRightToLeft(); if (rtl) { r.setX(widthStep*lineWidth*12); } else { r.setWidth(widthStep*lineWidth*7); } p.drawText(r, Qt::AlignRight, QString("%1%").arg(value())); } } void VolumeSlider::mousePressEvent(QMouseEvent *ev) { if (Qt::MiddleButton==ev->buttons()) { down=true; } else { QSlider::mousePressEvent(ev); } } void VolumeSlider::mouseReleaseEvent(QMouseEvent *ev) { if (down) { down=false; muteAction->trigger(); update(); } else { QSlider::mouseReleaseEvent(ev); } } void VolumeSlider::contextMenuEvent(QContextMenuEvent *ev) { static const char *constValProp="val"; if (!menu) { menu=new QMenu(this); muteMenuAction=menu->addAction(tr("Mute")); muteMenuAction->setProperty(constValProp, -1); for (int i=0; i<11; ++i) { menu->addAction(QString("%1%").arg(i*10))->setProperty(constValProp, i*10); } } muteMenuAction->setText(MPDConnection::self()->isMuted() ? tr("Unmute") : tr("Mute")); QAction *ret = menu->exec(mapToGlobal(ev->pos())); if (ret) { int val=ret->property(constValProp).toInt(); if (-1==val) { muteAction->trigger(); } else { setValue(val); } } } void VolumeSlider::wheelEvent(QWheelEvent *ev) { int numDegrees = ev->delta() / 8; int numSteps = numDegrees / 15; if (numSteps > 0) { for (int i = 0; i < numSteps; ++i) { increaseVolume(); } } else { for (int i = 0; i > numSteps; --i) { decreaseVolume(); } } } void VolumeSlider::updateMpdStatus() { if (fadingStop) { return; } int volume=MPDStatus::self()->volume(); blockSignals(true); if (volume<0) { setValue(0); } else { int unmuteVolume=-1; if (0==volume) { unmuteVolume=MPDConnection::self()->unmuteVolume(); if (unmuteVolume>0) { volume=unmuteVolume; } } setEnabled(true); setToolTip(unmuteVolume>0 ? tr("Volume %1% (Muted)").arg(volume) : tr("Volume %1%").arg(volume)); setValue(volume); } setEnabled(volume>=0); setVisible(volume>=0); update(); muteAction->setEnabled(isEnabled()); StdActions::self()->increaseVolumeAction->setEnabled(isEnabled()); StdActions::self()->decreaseVolumeAction->setEnabled(isEnabled()); blockSignals(false); } void VolumeSlider::increaseVolume() { triggerAction(QAbstractSlider::SliderPageStepAdd); } void VolumeSlider::decreaseVolume() { triggerAction(QAbstractSlider::SliderPageStepSub); } void VolumeSlider::generatePixmaps() { pixmaps[0]=generatePixmap(false); pixmaps[1]=generatePixmap(true); } QPixmap VolumeSlider::generatePixmap(bool filled) { bool reverse=isRightToLeft(); QPixmap pix(size()); pix.fill(Qt::transparent); QPainter p(&pix); p.setPen(textCol); for (int i=0; i<10; ++i) { int barHeight=(lineWidth*constHeightStep)*(i+1); QRect r(reverse ? pix.width()-(widthStep+(i*lineWidth*widthStep*2)) : i*lineWidth*widthStep*2, pix.height()-(barHeight+1), (lineWidth*widthStep)-1, barHeight); if (filled) { p.fillRect(r.adjusted(1, 1, 0, 0), textCol); } else if (lineWidth>1) { p.drawRect(r); p.drawRect(r.adjusted(1, 1, -1, -1)); } else { p.drawRect(r); } } return pix; } cantata-2.2.0/widgets/volumeslider.h000066400000000000000000000035001316350454000174560ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef VOLUMESLIDER_H #define VOLUMESLIDER_H #include #include class QPixmap; class QMenu; class Action; class QAction; class VolumeSlider : public QSlider { Q_OBJECT public: static QColor clampColor(const QColor &col); VolumeSlider(QWidget *p=0); virtual ~VolumeSlider() { } void initActions(); void setFadingStop(bool f) { fadingStop=f; } void setColor(QColor col); void paintEvent(QPaintEvent *ev); void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); void contextMenuEvent(QContextMenuEvent *ev); void wheelEvent(QWheelEvent *ev); private Q_SLOTS: void updateMpdStatus(); void increaseVolume(); void decreaseVolume(); private: void generatePixmaps(); QPixmap generatePixmap(bool filled); private: int lineWidth; bool down; bool fadingStop; QColor textCol; QPixmap pixmaps[2]; Action *muteAction; QAction *muteMenuAction; QMenu *menu; }; #endif cantata-2.2.0/widgets/wizardpage.cpp000066400000000000000000000031731316350454000174420ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "wizardpage.h" #include "support/icon.h" #include "support/utils.h" #include #include void WizardPage::setBackground(const Icon &i) { int size=fontMetrics().height()*10; size=((int)(size/4))*4; pix=i.pixmap(size, QIcon::Disabled); if (pix.width() * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef WIZARDPAGE_H #define WIZARDPAGE_H #include #include class Icon; class WizardPage : public QWizardPage { public: WizardPage(QWidget *parent = 0) : QWizardPage(parent) { } virtual ~WizardPage() { } void setBackground(const Icon &i); void paintEvent(QPaintEvent *e); private: QPixmap pix; }; #endif cantata-2.2.0/windows/000077500000000000000000000000001316350454000146215ustar00rootroot00000000000000cantata-2.2.0/windows/CMakeLists.txt000066400000000000000000000063041316350454000173640ustar00rootroot00000000000000set(WINDOWS_APP_NAME Cantata) set(WINDOWS_COMPANY_NAME Cantata) set(WINDOWS_URL "http://${PROJECT_URL}") if (CANTATA_SSL_LIBS) set(EXTRA_WIN_LIBS ${EXTRA_WIN_LIBS} ${CANTATA_SSL_LIBS}) set(CANTATA_SSL_WIN_NSIS_INSTALL "file \"libeay32.dll\"\n file \"ssleay32.dll\"") endif (CANTATA_SSL_LIBS) FILE(TO_CMAKE_PATH $ENV{QTDIR} QT_DIR) set(QT_BINARY_DIR ${QT_DIR}/bin) set(QT_PLUGINS_DIR ${QT_DIR}/plugins) set(QT_TRANSLATIONS_DIR ${QT_DIR}/translations) if (ENABLE_PROXY_CONFIG) set(CANTATA_PROXY_ICON_INSTALL "file \"icons\\cantata\\svg\\preferences-system-network.svg\"") else () set(CANTATA_PROXY_ICON_INSTALL "") endif () if (ENABLE_DEVICES_SUPPORT) set(CANTATA_DEVICES_ICON_INSTALL "file \"icons\\cantata\\svg\\drive-removable-media-usb-pendrive.svg\"\n file \"icons\\cantata\\svg\\multimedia-player.svg\"") if (ENABLE_REMOTE_DEVICES) set(CANTATA_REMOTE_DEVICES_ICON_INSTALL "file \"icons\\cantata\\svg\\folder-network.svg\"\n file \"icons\\cantata\\svg\\folder-samba.svg\"") else () set(CANTATA_REMOTE_DEVICES_ICON_INSTALL "") endif () else () set(CANTATA_DEVICES_ICON_INSTALL "") set(CANTATA_REMOTE_DEVICES_ICON_INSTALL "") endif () configure_file(cantata.nsi.cmake ${CMAKE_CURRENT_BINARY_DIR}/cantata.nsi) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cantata.nsi cantata.ico LICENSE.txt "Cantata README.txt" "Qt License (LGPL V2).txt" "TagLib README.txt" DESTINATION ${CMAKE_INSTALL_PREFIX}) # Qt image formats... install(FILES ${QT_PLUGINS_DIR}/imageformats/qjpeg.dll ${QT_PLUGINS_DIR}/imageformats/qsvg.dll DESTINATION ${CMAKE_INSTALL_PREFIX}/imageformats/) # Qt iconengines... install(FILES ${QT_PLUGINS_DIR}/iconengines/qsvgicon.dll DESTINATION ${CMAKE_INSTALL_PREFIX}/iconengines/) # Qt windows platform plugin... install(FILES ${QT_PLUGINS_DIR}/platforms/qwindows.dll DESTINATION ${CMAKE_INSTALL_PREFIX}/platforms/) # Qt SQL driver... install(FILES ${QT_PLUGINS_DIR}/sqldrivers/qsqlite.dll DESTINATION ${CMAKE_INSTALL_PREFIX}/sqldrivers/) # Qt MultiMedia install(FILES ${QT_PLUGINS_DIR}/mediaservice/dsengine.dll DESTINATION ${CMAKE_INSTALL_PREFIX}/mediaservice/) # Qt, taglib, and zlib libraries... set(EXTRA_WIN_LIBS ${EXTRA_WIN_LIBS} ${QT_BINARY_DIR}/Qt5Core.dll ${QT_BINARY_DIR}/Qt5Gui.dll ${QT_BINARY_DIR}/Qt5Network.dll ${QT_BINARY_DIR}/Qt5Svg.dll ${QT_BINARY_DIR}/Qt5Xml.dll ${QT_BINARY_DIR}/Qt5Widgets.dll ${QT_BINARY_DIR}/Qt5WinExtras.dll ${QT_BINARY_DIR}/Qt5Sql.dll ${QT_BINARY_DIR}/libgcc_s_dw2-1.dll ${QT_BINARY_DIR}/libstdc++-6.dll ${QT_BINARY_DIR}/libwinpthread-1.dll ${ZLIB_INCLUDE_DIR}/../bin/libz-1.dll ${TAGLIB_INCLUDES}/../bin/libtag.dll) if (ENABLE_HTTP_STREAM_PLAYBACK) set(EXTRA_WIN_LIBS ${EXTRA_WIN_LIBS} ${QT_BINARY_DIR}/Qt5Multimedia.dll) endif() # Qt translation files... file(GLOB qt_trans ${QT_TRANSLATIONS_DIR}/qt_*.qm) foreach(qm ${qt_trans}) if (NOT ${qm} MATCHES "(${QT_TRANSLATIONS_DIR}/qt_help*)") list(APPEND qt_translations "${qm}") endif () endforeach() install(FILES ${qt_translations} DESTINATION ${CMAKE_INSTALL_PREFIX}/translations/) install(FILES ${EXTRA_WIN_LIBS} DESTINATION ${CMAKE_INSTALL_PREFIX}) install(FILES LICENSE.txt DESTINATION ${CMAKE_INSTALL_PREFIX} RENAME "Cantata License (GPL V3).txt") cantata-2.2.0/windows/Cantata README.txt000066400000000000000000000022611316350454000176540ustar00rootroot00000000000000Introduction ============ Cantata is a GUI client for MPD. Its developed on Linux, using the Qt libraries. This release of Cantata for Windows is built without ffmpeg, mpg123, or Phonon libraries. Therefore the following features will be missing: ReplayGain calculation - requires ffmpeg and/or mpg123 Playback of MPD HTTP stream output - requires Phonon Dynamic Playlists ================= Cantata uses a perl helper script to facilitate dynamic playlists. This script is packaged with the Cantata source tarball (see http://code.google.com/p/cantata/wiki/Downloads) This script may be run in local or server mode. In local mode, Cantata itself starts and stops the script, and controls the loading of playlists. This mode is currently only available for Linux builds. In server mode, it is expected that the script is started and stopped with MPD on the host Linux machine. Cantata can then be configured to talk to this script via a simple HTTP interface. In this way, windows builds may use the dyamic playlist functionality. More information on how to install this script, etc, is available in the README file of the Cantata source tarball. cantata-2.2.0/windows/LICENSE.txt000066400000000000000000001057551316350454000164610ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . cantata-2.2.0/windows/Qt License (LGPL V2).txt000066400000000000000000000622771316350454000204370ustar00rootroot00000000000000 GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 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. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! cantata-2.2.0/windows/TagLib README.txt000066400000000000000000000003611316350454000174420ustar00rootroot00000000000000Cantata is compiled against TagLib 1.8.0, with ASF and MP4 support enabled. TagLib is distributed under the GNU Lesser General Public License (LGPL) and Mozilla Public License (MPL) See http://taglib.github.com/ for more information. cantata-2.2.0/windows/cantata.ico000066400000000000000000003361061316350454000167410ustar00rootroot00000000000000 hf  00 %v@@ (B; (F} 6n(  bbb"___```````````````___#UUU___```ffffff```aaaUUUaaaaaaaaa``````aaaaaaaaaccc$```]c+f/ѵ___```(```fff^$CCCCνfff___aaaΐl7CCCCġ``````쨨ȧ}NTM q``````c+cΰJ ___```Ґq?UCŢ```___fffRwFpJ fffaaa___+```cGc+```^^^.___aaaȧraaa```mmm```aaaaaa```mmmmmmaaa```ffffff```___```bbb/```````````````___^^^1( @ vvUUU___>``````___`````````aaa```^^^AUUU]]] ___{```````````````````````````````````````bbb aaag````````````````````````````````````````````````aaalUUUjjj ```````````````wwwvvv``````````````````aaa````````````{{{````````````bbb"jjj `````````bbbbbb`````````ZZZ`````````jjjjjj`````````___k``````bbbbbb`````````zbbb `````````ß}yIi3tCt`````````^^^`````````}}}dDCCCCCDÞ|``````aaa`````````rCCCCCCCCCջ`````````UUU aaaG``````vvvYCCCCCCCCCk6www`````````P`````````b*CCCCCCCCCH``````______``````dzGCCCCCCCCC``````___`````````i3CCCCCCCC``````````````````ѵƤ˫k6CCġl``````````````````{LCCCCP``````````````````_CCϾrCCl8``````````````````sCCŰDCC`````````___``````ǦCCԺCCE``````______S``````yyyѵCCͯ|MCC{Lxxx``````___[]]] ___``````íCCǤœyCCY`````````````````````ϾCCUCCxH``````aaa````````````CCCHˬ``````___]]])___``````cccCCf/ddd``````aaa````````````kkkZȧjjj`````````bbb ^^^&`````````cccbbb`````````___+aaa2````````````````````````^^^9^^^```````````````xxxxxx```````````````ccc,fff ``````````````````````````````````````````````````````[[[ccc,```````````````````````````````````````aaabbb/ccc```b```___```````````````___```efff??(0` $11^^^1```j___aaa___``````````````````m```5UUUUUU```E`````````````````````````````````````````````___```JUUU___^```````````````````````````````````````````````````````````````offf aaaL````````````````````````````````````````````````````````````````````````___^fffaaa````````````````````````bbbzzzzzzbbb```````````````````````````^^^bbb/`````````````````````gggggg``````````````````___aaa7```@``````````````````llllll``````````````````___K^^^A```````````````aaaaaa````````````___```M^^^1```````````````lllkkk```````````````aaa:``````````````````uuuvvv```````````````\\\aaa````````````uuuuuu```````````````___S````````````lllkkk````````````aaaafff ___`````````aaaœyWU[ǥμaaa`````````___``````p````````````YICCCCCCC^$ջ````````````___~UUU````````````mmmK CCCCCCCCCCCqlll````````````jjj ^^^Q````````````GCCCCCCCCCCCCCѵ````````````aaa_````````````gggxHCCCCCCCCCCCCCCUggg````````````UUU````````````SCCCCCCCCCCCCCCCμ```````````````^^^A````````````ZCCCCCCCCCCCCCCC˫````````````___N```z`````````bbbß}CCCCCCCCCCCCCCCß}bbb`````````aaa___`````````{{{}NCCCCCCCCCCCCCCrzzz`````````aaa````````````zICCCCCCCCCCCCg````````````````````````Ġ~`(CCCCFr@[!CCCW````````````````````````ZCCCwGȵ[!Pl````````````````````````mCCCl8c,CCC`'````````````````````````ŢCCCb*ŢCCCCm9````````````````````````ΰCCCWeCCCCȴ````````````````````````{{{׿CCCM DCCCozzz````````````___`````````bbb̺CCCDECCC^bbb```````````````M````````````CCCCͻCCCCß}```````````````Zfff````````````CCCCi4CCCD````````````^^^````````````gggCCCClCCCC`ggg```````````````e````````````J CCCœyCCCCvE````````````___sfff````````````nnn[!CCCn:CCCDulll``````````````````````````````k6CCCCCCU̺``````````````````___`````````aaaq>CCCCC{Laaa````````````]]]!```u````````````mmm|MCCCNдlll```````````````fff```````````````vvvlCCzKuuu```````````````UUU ccc$```````````````uuuƲQԺwww```````````````^^^1___S```````````````mmmlll```````````````aaaa```h```````````````aaaaaa``````````````````z```j``````````````````nnnmmm````````````````````````X`````````````````````gggggg`````````````````````aaadccc,aaa````````````````````````bbb|||{{{bbb```````````````````````````bbb4UUU ````````````````````````````````````````````````````````````````````````___aaabbb ___#````````````````````````````````````````````````````````````______```(^^^```z`````````````````````````````````````````````````````` ```%___f___````````````````````````aaa```j]]])????(@ @]]]!___N```u___```___````````````___```x^^^Q^^^&]]] aaaY``````````````````````````````````````````````````````````fffaaa?```___```````````````````````````````````````````````````````````````aaa```Hbbb<````````````````````````````````````````````````````````````````````````````````````___^UUUbbb'___`````````````````````````````````````````````````````````````````````````````````````````````^^^9``````___``````````````````````````````````````````````````````````````````````````````````````````````````````bbb ^^^&````````````````````````````````````vvvvvv````````````````````````````````````aaa7___S___```````````````````````````{{{{{{`````````````````````````````````b```j`````````````````````````````````````````````````````````aaa|````````````````````````~~~}}}```````````````````````````aaai`````````````````````dddddd````````````````````````aaaW`````````````````````uuuuuu````````````````````````j___+```````````````````````````````````````___bbb<]]] ```````````````````````````````````````___fff___````````````````````````````````````___bbb4``````````````````vvvuuu``````````````````___K``````````````````dddĠ~wnnдddd```````````````___```aaaW``````````````````œyXCCCCCCCCK _`````````````````````j``````````````````wFCCCCCCCCCCCCEȧ~~~``````````````````UUUaaaL```````````````aaa^%CCCCCCCCCCCCCCCCd`````````````````````baaa```````````````a)CCCCCCCCCCCCCCCCCCα``````````````````^^^``````````````````ʪCCCCCCCCCCCCCCCCCCCJ ``````````````````bbb'___k```````````````{{{r@CCCCCCCCCCCCCCCCCCCC̬|||````````````````````````````````````TCCCCCCCCCCCCCCCCCCCCxH``````````````````UUU``````````````````[ CCCCCCCCCCCCCCCCCCCCS```````````````___[[[```5```````````````xxxiCCCCCCCCCCCCCCCCCCCCDvvv``````````````````J```b```````````````OCCCCCCCCCCCCCCCCCCCC```````````````___v``````````````````пL CCCCCCCCCCCCCCCCCCC```````````````___aaa```````````````l7CCCCCCCCCCCCCCCCCC```````````````aaaaaa```````````````ջa)CCCCCCCCCCCCCCCC```````````````______```````````````ß}yI_&GCH^%[ֽ^CCCCC̺````````````````````````````````````zCCCCC˸j5CCIʩ````````````````````````````````````ˬCCCCCԺͯCCCCCuD````````````````````````````````````ԺCCCCCͯ׾CCCCCC~P````````````````````````````````````ȵCCCCCǥwCCCCCC````````````````````````````````````CCCCCwUCCCCCg1```````````````aaa``````````````````CCCCChCCCCCD`````````````````````o```````````````CCCCCZICCCCC```````````````___```E```````````````xxxGCCCCzKCCCCCCvvv```````````````aaaYjjj ``````````````````TCCCCo<ͯCCCCCL ``````````````````^^^aaa```````````````c+CCCCe.UCCCCCW````````````````````````````````````{{{q>CCCCZRCCCCCG|||``````````````````]]])``````````````````RCCCCPsCCCCCCǦ`````````````````````=``````````````````fCCCCEuCCCCCCÞ|``````````````````___k```````````````aaaĠ~CCCCCfCCCCCMջ`````````````````````fff ``````````````````ϲCCCCCCCCCCl7``````````````````aaa___{``````````````````ԺCCCCCCCCDn`````````````````````fff``````````````````dddCCCCCCCWνddd``````````````````\\\``````````````````````vvv˹CCCCCCUuuu`````````````````````r___``````````````````CCCCUŰ``````````````````___fff___#`````````````````````FCCU`````````````````````aaa2```U`````````````````````wF]#˹`````````````````````___kaaa`````````````````````vvvvvv````````````````````````aaa`````````````````````eeeddd`````````````````````aaammmmmm``````````````````````````````````````````````````````bbb UUU```````````````````````````aaaaaa```````````````````````````[[[UUU`````````````````````````````````}}}}}}``````````````````````````````aaammm___^````````````````````````````````````xxxxxx`````````````````````````````````___```ccc,````````````````````````````````````````````````````````````````````````````````````````````````````````````aaa7```o``````````````````````````````````````````````````````````````````````````````````````````````````````bbbaaa````````````````````````````````````````````````````````````````````````````````````___]]]!]]]```````````````````````````````````````````````````````````````````````````___aaaUUU___F````````````````````````````````````````````````````````````___Nfff]]])```h```___````````````````````````aaa``````m^^^.???(  ]]] ___+aaaL````___s___```aaa``````aaa`````````x___caaaO^^^1ZZZccc,___caaa```````````````````````````````````````````````````````````````aaaaaa___kaaa7fff]]])```r````````````````````````````````````````````````````````````````````````````````````````````````___~^^^6^^^9``````````````````````````````````````````````````````````````````````````````````````````````````````````````````______^^^Ifff]]]!___```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````^^^1bbbD``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````aaa_UUU___N`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````ujjj ```M`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````bbb ```=``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___^^^^aaa```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___+___^````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````aaabbb ___#````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````aaa:^^^Q`````````````````````````````````````````````````````````````````````````````````mmmmmm``````````````````````````````````````````````````````````````````````````````___```mmmaaa````````````````````````````````````````````````````````````````````````yyyxxx``````````````````````````````````````````````````````````````````````````````^^^&``````````````````````````````````````````````````````````````````aaaaaa``````````````````````````````````````````````````````````````````___>^^^6___````````````````````````````````````````````````````````````zzz~~~``````````````````````````````````````````````````````````````````ZaaaL````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___v___^``````````````````````````````````````````````````````aaabbb``````````````````````````````````````````````````````aaaaaay````````````````````````````````````````````````````````````````````````````````````````````````````````````___fffaaay```````````````````````````````````````````````````ppprrr`````````````````````````````````````````````````````````]````````````````````````````````````````````````bbbbbb``````````````````````````````````````````````````````M___`````````````````````````````````````````````nnnppp```````````````````````````````````````````````````zaaa?```````````````````````````````````````````````````````````````````````````````````````````````````eaaa*`````````````````````````````````````````````bbbbbb`````````````````````````````````````````````___K]]] `````````````````````````````````````````````dddfff`````````````````````````````````````````````]]]!`````````````````````````````````````````````jjjlll`````````````````````````````````````````````UUU```b``````````````````````````````````````````pppqqq`````````````````````````````````````````````___+``````````````````````````````````````````qqqppp`````````````````````````````````````````````RUUU``````````````````````````````````````````iiikkk``````````````````````````````````````````bbb___{```````````````````````````````````````fffeee``````````````````````````````````````````^^^```````````````````````````````````````bbbbbb``````````````````````````````````````````J`````````````````````````````````````````````````````````````````````````````````___```^^^Q```````````````````````````````````````ν```````````````````````````````````````___~UUU ```````````````````````````````````````oooϾƤ|M_&GCCCCCCCIa)[ϲppp```````````````````````````````````````]]]!___n````````````````````````````````````bbbռRJ CCCCCCCCCCCCCCCCCc+Ǧbbb````````````````````````````````````___fff```````````````````````````````````````wQCCCCCCCCCCCCCCCCCCCCCCIo```````````````````````````````````````cccaaag````````````````````````````````````oooȧM CCCCCCCCCCCCCCCCCCCCCCCCCCM дppp```````````````````````````````````````UUU___````````````````````````````````````ZCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCZ```````````````````````````````````````^^^````````````````````````````````````````ʪFCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCr@````````````````````````````````````______`````````````````````````````````aaaȧCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCq>aaa````````````````````````````````````fff___;````````````````````````````````````ѵCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCg````````````````````````````````````aaaiaaa````````````````````````````````````J CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC˸aaa````````````````````````````````````jjj ````````````````````````````````````UCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC^$````````````````````````````````````^^^.aaaY````````````````````````````````````DCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCռ```````````````````````````````````````````````````````````````````````````{{{ҷCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCp={{{````````````````````````````````````UUU ````````````````````````````````````gCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCD````````````````````````````````````bbb/aaaL`````````````````````````````````aaasACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCҷbbb````````````````````````````````````x````````````````````````````````````]#CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCb````````````````````````````````````````````````````````````````````````i3CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCl7````````````````````````````````````]]] fff`````````````````````````````````aaaVCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCP````````````````````````````````````aaaB^^^Q`````````````````````````````````zzzռCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDxxx`````````````````````````````````___~````````````````````````````````````PCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`````````````````````````````````aaaaaa`````````````````````````````````дCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`````````````````````````````````___````````````````````````````````````vECCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC````````````````````````````````````fffZZZ`````````````````````````````````ooof0CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCmmm`````````````````````````````````___>___3`````````````````````````````````i4CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC````````````````````````````````````````U`````````````````````````````````VCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`````````````````````````````````aaa___s`````````````````````````````````RCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCϾ`````````````````````````````````aaaaaa`````````````````````````````````sJ CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC˸`````````````````````````````````aaa````````````````````````````````````zUCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCdz````````````````````````````````````___`````````````````````````````````ű`UCCCCCCCCCCCCCCC[ ß}ӹCCCCCCCCCCC¬````````````````````````````````````````````````````````````````````````ѵkq?_&L CCCFXn:_дCCCCCCCCCCCϲѵfZwȴ`````````````````````````````````___````````````````````````````````````CCCCCCCCCCCß}îPCCCCCX`````````````````````````````````______`````````````````````````````````DCCCCCCCCCCcпFCCCCCCCCzJ````````````````````````````````````````````````````````````````````````QCCCCCCCCCC{Lm9CCCCCCCCCCX````````````````````````````````````````````````````````````````````````_&CCCCCCCCCCp=FCCCCCCCCCCCO````````````````````````````````````___`````````````````````````````````n:CCCCCCCCCCf/RCCCCCCCCCCCCP````````````````````````````````````````````````````````````````````````|MCCCCCCCCCC[ xCCCCCCCCCCCCCc,````````````````````````````````````aaa`````````````````````````````````aCCCCCCCCCCPUCCCCCCCCCCCCCŢ`````````````````````````````````aaa````````````````````````````````````tCCCCCCCCCCFŢCCCCCCCCCCCCJ ````````````````````````````````````````````````````````````````````````ȧCCCCCCCCCCCiCCCCCCCCCCCCƤ```````````````````````````````````````e`````````````````````````````````ѵCCCCCCCCCCC^$CCCCCCCCCCCa)````````````````````````````````````aaaG`````````````````````````````````įCCCCCCCCCCCдCCCCCCCCCCCC`````````````````````````````````___sbbb'`````````````````````````````````oooCCCCCCCCCCCDCCCCCCCCCCCdzmmm`````````````````````````````````___S````````````````````````````````````CCCCCCCCCCC̺XCCCCCCCCCCCͮ````````````````````````````````````]]])````````````````````````````````````CCCCCCCCCCC[!CCCCCCCCCCCɩ````````````````````````````````````___`````````````````````````````````DCCCCCCCCCCҷHCCCCCCCCCCC̭```````````````````````````````````````o`````````````````````````````````{{{PCCCCCCCCCCˬCCCCCCCCCCCCíxxx`````````````````````````````````___^^^6`````````````````````````````````aaa^%CCCCCCCCCCġnCCCCCCCCCCCC```````````````````````````````````````bfff````````````````````````````````````l8CCCCCCCCCCrL CCCCCCCCCCCU````````````````````````````````````bbb'````````````````````````````````````zKCCCCCCCCCCccCCCCCCCCCCCCl````````````````````````````````````___s`````````````````````````````````aaa_CCCCCCCCCCT˸DCCCCCCCCCCCDbbb`````````````````````````````````______+````````````````````````````````````sCCCCCCCCCCwFXCCCCCCCCCCCCT````````````````````````````````````aaaW___`````````````````````````````````{{{ǦCCCCCCCCCCl7q>CCCCCCCCCCCCK {{{````````````````````````````````````ZZZ```````````````````````````````````````дCCCCCCCCCCa)^CCCCCCCCCCCCCд````````````````````````````````````___aaa2````````````````````````````````````íCCCCCCCCCCVuCCCCCCCCCCCCCf````````````````````````````````````___^```````````````````````````````````````CCCCCCCCCCL xCCCCCCCCCCCCCUaaa````````````````````````````````````[[[aaaq````````````````````````````````````CCCCCCCCCCDzCCCCCCCCCCCCCm````````````````````````````````````___fff````````````````````````````````````aaaDCCCCCCCCCCqCCCCCCCCCCCCI̭aaa````````````````````````````````````bbb<aaa````````````````````````````````````VCCCCCCCCCCzJCCCCCCCCCCCCa)``````````````````````````````````````````-```````````````````````````````````````b*CCCCCCCCCCCCCCCCCCCCCC\```````````````````````````````````````aaa\```````````````````````````````````````pppn:CCCCCCCCCCCCCCCCCCCCJ Ҷqqq````````````````````````````````````___fffbbb<```````````````````````````````````````sACCCCCCCCCCCCCCCCCCCj5```````````````````````````````````````___k```````````````````````````````````````bbbxHCCCCCCCCCCCCCCCCCFwbbb```````````````````````````````````````mmmaaa:```````````````````````````````````````qqq}OCCCCCCCCCCCCCCCC\"qqq``````````````````````````````````````````e``````````````````````````````````````````WCCCCCCCCCCCCCCDk``````````````````````````````````````````UUU]]]!``````````````````````````````````````````cCCCCCCCCCCCCC[ μ`````````````````````````````````````````````8``````````````````````````````````````````bbbrCCCCCCCCCCCDmbbb```````````````````````````````````````aaaUUU``````````````````````````````````````````fffɩCCCCCCCCCC[!Ͼggg``````````````````````````````````````````fffbbb<``````````````````````````````````````````iiiCCCCCCCCDpkkk``````````````````````````````````````````aaadaaa``````````````````````````````````````````qqqCCCCCCC\"пppp`````````````````````````````````````````````mmm`````````````````````````````````````````````pppPCCCCDfqqq``````````````````````````````````````````___^^^___+`````````````````````````````````````````````jjj\CCC]#lll``````````````````````````````````````````______;```X`````````````````````````````````````````````dddͻCJ ȧfff````````````````````````````````````````````````x````````````````````````````````````````````````bbbbbb````````````````````````````````````````````````UUUaaa``````````````````````````````````````````````````````````````````````````````````````````````````````bbb ```````````````````````````````````````````````````qqqqqq```````````````````````````````````````````````````\\\bbb```````````````````````````````````````````````````bbbbbb``````````````````````````````````````````````````````%aaa``````````````````````````````````````````````````````pppsss``````````````````````````````````````````````````````^^^9^^^&```````````````````````````````````````````````````````````````````````````````````````````````````````````````___aaaB\\\`````````````````````````````````````````````````````````aaabbb`````````````````````````````````````````````````````````^^^9ZZZ````````````````````````````````````````````````````````````aaaaaa````````````````````````````````````````````````````````````bbb'fff aaa```````````````````````````````````````````````````````````````zzz``````````````````````````````````````````````````````````````````aaafff`````````````````````````````````````````````````````````````````````bbbbbb`````````````````````````````````````````````````````````````````````ccc___^```````````````````````````````````````````````````````````````````````````zzzzzz``````````````````````````````````````````````````````````````````````````````UUU```(````````````````````````````````````````````````````````````````````````````````````nnnnnn````````````````````````````````````````````````````````````````````````````````````aaaG```aaa```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````bbb```U``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````aaaqUUU]]] `````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___^^^bbb/````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````^^^Iaaaa```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````z```mmmaaal``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````UUU___^```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````ZZZ___C```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````Ufffaaa___n```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````___aaa|bbb"aaa___k```````````````````````````````````````````````````````````````````````````````````````````````````````````````waaa*```aaa?___{````````````````````````````````````````````````````````````````````````````````````aaa```J[[[^^^```Haaat````````````___```````````````````````````___```___``````z^^^Q___#??????????PNG  IHDR\rf6IDATxw\TW0 6Pbhj D# jHb)n6o›-ͻe€ItC6]c$.0 3(i;~>~b\<W#tM)E0{(\JJJ9JI%J!'O}E9h*SJ)0@_DkrI[WWWe-Nƨ ,8ISJ"Z4(pRS+V(@E SQ @( .%dJ @7.f,hQmeS )))SN&,wln<#ZP[A1h4)hM G"_X@ _F5 EkbdI2--h1j"%%E{ĉD3Yz_t:]fjjjh1jn!I#K67$m2@||&((@B)-&,#m$i!y\ZSQ_7ԉCQ ϟY,.mUQ.2{F^ZZ&bjp^6B|8d2-Dp.?,|_J+E Q\Errrj}3DQ8b2T;/RJ}!RJ_4LEh =EkQJ3!o~~~^-FmI.fmDb3yj&<u9HINOO?$ZOڔƙ>CA)If{yŊfzx& _E+8.Icma#@bbť**nA)Bh6k+>e)))ڒW@#Z/!СPCC#(($ iZ_:`X` lF}}=Q[[*TTT/^m$KKK;.ZX`AwYp)OBAΝѣG޽; jjinnFee%Μ9RԩS(//uc&$-$&&'d&Z'۷/^z]yj+ł0}nEx3**唔YwzHLLL&|@'Zt:`СAn@wQJq9 77Z}P!dbWrD pGaaa>|8O$477ȑ#šCPYY)ZeyNfffh!hѢ.6;Dkq;bȑ5jOyw8{,ߏ]v;SQJ%Izp~6m-!h-ѣGooFPJQTT;v`߾}`'{iX~=8Y>_GE ^aIIIK(o)]?&NӧS''-[uV444%IsP`4ϢuF`` OS" @M%ȒlܸׯGceYإEhLh7SL̙3{455?V^t#&LSj"1h!7Bbԩ9s&z5Hfl޼yR(򝙙gD %1xRa0s΢\EYYK߿_Qd&}D h|R:[nx衇+ZJ+`ʕ8}h)7Vlٲ (0u:Fôi IjQo@e[|7X;r~@1`4hW3`FtU(//dB~~h)@0?EHJJZD)MU@<?~eˡbǎXjV 6XD5'!!a!µ@~ / &&F>!zm݆'O*a_/"v =!$P{ .T|@?AAA(((Pʶ![E fSxm |=K.xpmO}}bȐ!(((PʖIqqq'rrrrDb/[M5qqqXd߆ر#&Ls92+..nwNN @JJTVV%!DhQNBfΜ @lb*NѣGCӡ@ !d`wvvv[!$wܫ “O>ɓ'C~)׻T5":"!шBNN~1tf^Ah 򆇇㥗^B~^PH(+6?~yv+bh0A4еkW 6 Bzp[f .E)@ر>}׿5¼&"xݍ(&ؘUٿ+BNU3f QSS#L!d`#0(!T^GKtٶ9wkrSڵرcq?^; SqSJ?0G1qDW;b>66,!^=8p l6D;f=~Ɓpi2+SLyxI"ה!m]kG BSS9BHwY=}m3cvr &߃\4Zlx)u+&<9 N)8<4iؔWy7dܕ>|8,XNyGfᇂx&R2w9<$!HHH`ޟR <4ǣF+?** >,Z-ϰ> lGݳf-4s cb[wS `Gu5}==@Gw$,, K,oGUMh;s cIj.T7 }}M6LKvmoػw/ЃݽGt``syށO-Z3|6Y#XGg4ロ{\Y/>>0΄y6`0`֬Y_VLh9ŋCdju[@ !9sЧO^4J0hڰtvLuN׹ADEExΝeA 11qZٿ̘1W6R ͇Obt>afϞ Nl?S- {_V t{U iѢE]S`4ђHL6G(P]g-),v<0}i.TE˹]w݅ݻ owΎs3.p .9h4T-wx 6w܎y`Ĉ2dP*7b=@dx0s͘g{J0h Y;8N{kZdzr *_qpeI߉ٯ UuG3s喱RbJJC}ۡPJ!~ԩũy C)pg^O@|r/{d'sK9d#?v9W63K! x\s_X*=={6ڵk+BG~!:t1w>}:ٿ#α].NޓS#e7.'M@7D1*.60nֿo,mbqhN>oR:n_(INf8Fe-u@ ں%o~=شixt) Y5j +*7'>zf*O̭@xx8FUO_nГӧ *NjV-,{a:Ww Xx>[kojq{޽{s *jl{ tZ ^1=-vj4ZeBӧz#7{ B:wqƫ8`oU!>~?YK8Mv a@RR0Lvƫ8AmYE[Mވ舎Xlh$>YJ97nA7q1cƨ>Hy @.?ɧ>ahՌ]\bve ( `„ _D<:+ռ0ɬG %$$lRg``X*رZ[*j$|/Ԏteʭ4: 0!!!h4AȲ<=ZPX%3-<2}X'4YB eg#0=ycU\C#^>V5᫝EOBLII&-э \NԵSU]Z7Nr/cGlHIIɰHNNП#F#we~/<#5{j ?ʣ*. w,,}z m7D(LgXt: *.j`;: ]qށmh!&&Ǧk1B忘_QqV<5oADo3#^NCԣ@JJR:edu|Jγɏx38++pԩ=˨%0WO>ym눉aRzeȲ2^G׮gnU\ʬ,''[^]:0' FFFH{* 0-ү_?uO(yӍBзo_a~nRܲ_>C\6a:iъO7`01sW R05nݺ1mgUFm3q3wg0` hi5R2g1Z\⚰0+K!!! @/C S]ǥC`/ft:%͝;7aaa!X&ॿoKߋۇFbD.] `:>W ;k2˂+}WM;|>;x2Bh(˫xA~nm٥xc{Ɖ[a,T+I=F(u|7bŻ'Ca{z9 T pheWU(؀)/'g\?r ӍI !0P u(mG7EY9+9!Lwǀۀ Oytx¡Fƕ5Pa\NX075si*^E_Vu"@)eo6`@I~ُ' ĺ7`]X!DT(u)za_@0\Óx̟:B%7@G^TT&c0_gh?_o?qWB$Al66Uܣxm!NF{?78>ID$N͡4Im`)59XlӢ['ֳ?AXwӯg`sѷ{vJ{Ј6Aņ꺦jYt r$!8L ?GL 8mcZXRGQSo >Vqg+Q^s鿭 CXt./"#C1_gt5nM^Oؖ{=ŨktEx`t _Óp1B9]b[idpp}ʚq{ ]wZzb7(g`,G0&'/\DљjF?2 7jeG,Gf3J\k}'P `,hذR؍p~4GFA:zZH}OwN-}^B1onvw1ud%lr9`Д$`2j0eee^s,T%y/ʲ \6 F)EII yK35.ìĉWܜ\e$13(..f OQxw. _=,EEECϫN#NrceâeJ.T8+}Ȳ|zX/|Uc>@5g1s<@>}Plƹsܿ=͇Ohld{΄+"2pAAӆ>$Zҥch ^E~~>Z~-t'_4d|G!@g"//u+K$IL Gc؞w[޶Fhp , =4k jje@gh >Kdxh ^Eaa!JZ?1> (5 e,R {Upg۴XA)]{VXa})5+UKD}PJqAal6G!ko}=שQ= b{5i JO?g[qof߳n;ܿS0)N5GnP2o>44;LCp8&Y}FȲ\kV+˼ᷢOsx/tȮܜ/{nc566_4?Yڲe D#] bۙηϬt8~\|{3tj&D;v NVQJ֯_EdޔASX{gP8Ⱥux9e2n^Y;p ;16u]:{ 馛V `Ŋ~bNelڴǍhW珅D}-+XndYvB@/Z{GƽEn۶ oFkCjfEt҇LjfڵGV3t:]: X,ؼ$x;3. /ܥps)C)MRSS+M1$u?^K-g$6`blh^A}}=6n#Tcss-&FEL?/$Z0/G<tUKpӷ`kÆ e~snŋ5J 7AP;u/^K)u71 X,{G'fDP,uXӋ5^WRܼ=û_Vk*o޼ϋOISq(?;\ܹs<:9//י2rYQUU1ct't '`_U:;DK*-[vl6/w(S_6o1Çdaƞa@r;G(v7}'3pˆM)}/++ `ٲep3j*.;%og$&w 3v7Q];UvZW:B--ń0uuuGtrNi%57f틣U( ~ł5Z_œՍ>.fnIp)ddd|g6 6O=zFBpvu뤇qLEPV#> Fo Gk$D)Ṡ\*WHyG,ǏQ఻O<:3;Qt7x[hz|̝Q{p^!?rC.@NN9`0u*++^z`䀮xmPwT5Ѳ\kh^7vI2[nyŬju!һ OnNN$ªU0x`tބwꍻFjۏ2 a`|ݥYYN?]Re\je񉉉Bh,]]{88lK?9Cq0m0 };3Ȳ7xLj_EFgU|-HHHO%9s0{lPp;Jg!zC"p( Vzk|7QJ_xϻ0xGc%I/}ǜ?(>S5(>S֪F" Fз{FۇFbPNmrON8?ڄ ߈sv;JRa#<<|7|]'_DF5ZQ`E]f Ax:t n];gjS#%%bB榧tb4WT|J)yqIٓ>$t w9O?oNEU+L)}nv~Ceey ;rXf ט4Cgffnn/_3g r HMMn?RZ!I˞G_u:ݳx݌F[VJ۠.ƕ$iIzz*xtB!MMM(,,رcժU~UcZoٳC3L=>! ɓ''(*o"2>#Em͒$=2YGOpXdgg#--JۂR n}4--GNNaÆU\ӧQ__XaURX۶m~{TTS[nӍѯa8'NCV/yUF3wީaqqg? ^5ܱc@\GG믱zjQәUejyyyMÆ ; ]Oaa!1h EfRQ>R|7\r'g>L^aUM@)(Xr%6l *~Fsa߿6N7 %%%`PM@!dYEM@#td::Ϸ2d$IspO{)?~ÇW7 bG}@y,##c-X6l}xmb|'\xs&=QɩF@XنܹпGek׮?.gw ׁ9c0#J) O?a>v-RYY;vPʑQ'A@vvv`8n~-Ʈ]={/Rm۶?YVl ywIJJzRh-DGG#)) ݺu-E`2PP}r5viT$oذaUUUرc$IB߾}B$^nǚ5k'({%IQ/ZH 2F! ILطoC '??|ۧfZZZEBFQWc4_?u\ϠA0w\DDDrϟǪUí@3tSSS+D $&&gh4g޽;Ə'B׋466b޽غu+NdSX,0L*d{3X`A,˫Qt:F &`eGΝ;qXLz*J}E q&''Ywx`"kz=0n8ĴY3QSS#Z+i49˗//-g4H6lbcc1p@OLbXPXX\>|բ% t,ˏ*e_+x&eY CbРAс,8s 󑗗#GfSxPJmWL&_Dkqv]żyt:>ZOvR#P(?RJeff5fH @wZT}oWjS3@rrr)OA ܚ\L&~BX& TDkQQ$4մ)~ڿeJRF6JxZZqBx /ZPNSJ_-DmZHHHCwĉ֢3ғvk:)Ta w&h1Q */^ohhxotGţPJJ髙EQ ܀X Dn4##h-JC5VXpaf{$5wA|/IRJZZbjpyDz֣*Vh4o._GbjN"Gj !&F˖-+-[P E./>^c/ޜGh ^^BVɲN칇j$))i,I9BE1BVe6 xMppXJi * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "thumbnailtoolbar.h" #include "gui/stdactions.h" #include "gui/settings.h" #include "widgets/icons.h" #include "support/action.h" #include "mpd-interface/mpdstatus.h" #include ThumbnailToolBar::ThumbnailToolBar(QWidget *p) : QWinThumbnailToolBar(p) { setWindow(p->windowHandle()); prevButton=createButton(StdActions::self()->prevTrackAction); playPauseButton=createButton(StdActions::self()->playPauseTrackAction); stopButton=createButton(StdActions::self()->stopPlaybackAction); nextButton=createButton(StdActions::self()->nextTrackAction); readSettings(); update(MPDStatus::self()); } void ThumbnailToolBar::readSettings() { stopButton->setVisible(Settings::self()->showStopButton()); } void ThumbnailToolBar::update(MPDStatus * const status) { playPauseButton->setEnabled(status->playlistLength()>0); switch (status->state()) { case MPDState_Playing: playPauseButton->setIcon(Icons::self()->toolbarPauseIcon); stopButton->setEnabled(true); nextButton->setEnabled(status->playlistLength()>1); prevButton->setEnabled(status->playlistLength()>1); break; case MPDState_Inactive: case MPDState_Stopped: playPauseButton->setIcon(Icons::self()->toolbarPlayIcon); stopButton->setEnabled(false); nextButton->setEnabled(false); prevButton->setEnabled(false); break; case MPDState_Paused: playPauseButton->setIcon(Icons::self()->toolbarPlayIcon); stopButton->setEnabled(0!=status->playlistLength()); nextButton->setEnabled(status->playlistLength()>1); prevButton->setEnabled(status->playlistLength()>1); default: break; } } QWinThumbnailToolButton * ThumbnailToolBar::createButton(Action *act) { QWinThumbnailToolButton *btn = new QWinThumbnailToolButton(this); btn->setToolTip(act->text()); btn->setIcon(act->icon()); btn->setEnabled(false); QObject::connect(btn, SIGNAL(clicked()), act, SIGNAL(triggered())); addButton(btn); return btn; } cantata-2.2.0/windows/thumbnailtoolbar.h000066400000000000000000000026721316350454000203470ustar00rootroot00000000000000/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef WINTHUMBNAILTOOLBAR_H #define WINTHUMBNAILTOOLBAR_H #include class QWinThumbnailToolButton; class MPDStatus; class Action; class ThumbnailToolBar : public QWinThumbnailToolBar { public: ThumbnailToolBar(QWidget *w); virtual ~ThumbnailToolBar() { } void readSettings(); void update(MPDStatus * const status); private: QWinThumbnailToolButton * createButton(Action *act); private: QWinThumbnailToolButton *prevButton; QWinThumbnailToolButton *playPauseButton; QWinThumbnailToolButton *stopButton; QWinThumbnailToolButton *nextButton; }; #endif